instance metrics
This commit is contained in:
parent
520b178281
commit
6f48ec1d23
56
frontend/src/components/metrics-row/index.js
Normal file
56
frontend/src/components/metrics-row/index.js
Normal file
@ -0,0 +1,56 @@
|
||||
const React = require('react');
|
||||
const Styled = require('styled-components');
|
||||
|
||||
const Column = require('@ui/components/column');
|
||||
const MiniMetric = require('@ui/components/mini-metric');
|
||||
const PropTypes = require('@root/prop-types');
|
||||
const Row = require('@ui/components/row');
|
||||
|
||||
const {
|
||||
default: styled
|
||||
} = Styled;
|
||||
|
||||
const {
|
||||
MiniMetricGraph,
|
||||
MiniMetricMeta,
|
||||
MiniMetricTitle,
|
||||
MiniMetricSubtitle,
|
||||
MiniMetricView
|
||||
} = MiniMetric;
|
||||
|
||||
const StyledRow = styled(Row)`
|
||||
margin: 0;
|
||||
|
||||
& > div {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const MetricsRow = ({
|
||||
datasets = []
|
||||
}) => {
|
||||
const _datasets = datasets.map((metric, i) => (
|
||||
<Column key={i} xs={4}>
|
||||
<MiniMetricView borderless>
|
||||
<MiniMetricMeta>
|
||||
<MiniMetricTitle>Memory: 54%</MiniMetricTitle>
|
||||
<MiniMetricSubtitle>(1280/3000 MB)</MiniMetricSubtitle>
|
||||
</MiniMetricMeta>
|
||||
<MiniMetricGraph data={metric.data} />
|
||||
</MiniMetricView>
|
||||
</Column>
|
||||
));
|
||||
|
||||
return (
|
||||
<StyledRow>
|
||||
{_datasets}
|
||||
</StyledRow>
|
||||
);
|
||||
};
|
||||
|
||||
MetricsRow.propTypes = {
|
||||
datasets: React.PropTypes.arrayOf(PropTypes.dataset)
|
||||
};
|
||||
|
||||
module.exports = MetricsRow;
|
@ -1,23 +1,16 @@
|
||||
const forceArray = require('force-array');
|
||||
const React = require('react');
|
||||
const ReactRouter = require('react-router');
|
||||
const Styled = require('styled-components');
|
||||
|
||||
const Anchor = require('@ui/components/anchor');
|
||||
const Column = require('@ui/components/column');
|
||||
const List = require('@ui/components/list');
|
||||
const MiniMetric = require('@ui/components/mini-metric');
|
||||
const MetricsRow = require('@components/metrics-row');
|
||||
const PropTypes = require('@root/prop-types');
|
||||
const Row = require('@ui/components/row');
|
||||
|
||||
const {
|
||||
Link
|
||||
} = ReactRouter;
|
||||
|
||||
const {
|
||||
default: styled
|
||||
} = Styled;
|
||||
|
||||
const {
|
||||
ListItem,
|
||||
ListItemView,
|
||||
@ -31,23 +24,6 @@ const {
|
||||
ListItemHeader
|
||||
} = List;
|
||||
|
||||
const {
|
||||
MiniMetricGraph,
|
||||
MiniMetricMeta,
|
||||
MiniMetricTitle,
|
||||
MiniMetricSubtitle,
|
||||
MiniMetricView
|
||||
} = MiniMetric;
|
||||
|
||||
const MetricsRow = styled(Row)`
|
||||
margin: 0;
|
||||
|
||||
& > div {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const ServiceItem = ({
|
||||
org = '',
|
||||
project = '',
|
||||
@ -99,18 +75,6 @@ const ServiceItem = ({
|
||||
</ListItemHeader>
|
||||
);
|
||||
|
||||
const metrics = service.metrics.map((metric, i) => (
|
||||
<Column key={i} xs={4}>
|
||||
<MiniMetricView borderless>
|
||||
<MiniMetricMeta>
|
||||
<MiniMetricTitle>Memory: 54%</MiniMetricTitle>
|
||||
<MiniMetricSubtitle>(1280/3000 MB)</MiniMetricSubtitle>
|
||||
</MiniMetricMeta>
|
||||
<MiniMetricGraph data={metric.data} />
|
||||
</MiniMetricView>
|
||||
</Column>
|
||||
));
|
||||
|
||||
const view = childs.length ? (
|
||||
<ListItemGroupView>
|
||||
{childs}
|
||||
@ -123,9 +87,7 @@ const ServiceItem = ({
|
||||
{description}
|
||||
</ListItemMeta>
|
||||
<ListItemOutlet>
|
||||
<MetricsRow>
|
||||
{metrics}
|
||||
</MetricsRow>
|
||||
<MetricsRow metrics={service.metrics} />
|
||||
</ListItemOutlet>
|
||||
</ListItemView>
|
||||
);
|
||||
|
@ -1,11 +1,17 @@
|
||||
const React = require('react');
|
||||
const ReactRedux = require('react-redux');
|
||||
|
||||
const actions = require('@state/actions');
|
||||
const EmptyInstances = require('@components/empty/instances');
|
||||
const PropTypes = require('@root/prop-types');
|
||||
const List = require('@ui/components/list');
|
||||
const DatasetsRow = require('@components/metrics-row');
|
||||
const selectors = require('@state/selectors');
|
||||
|
||||
const {
|
||||
toggleInstanceCollapsed
|
||||
} = actions;
|
||||
|
||||
const {
|
||||
connect
|
||||
} = ReactRedux;
|
||||
@ -19,22 +25,29 @@ const {
|
||||
ListItemView,
|
||||
ListItemMeta,
|
||||
ListItemTitle,
|
||||
ListItemOptions
|
||||
ListItemOptions,
|
||||
ListItemOutlet
|
||||
} = List;
|
||||
|
||||
const Instances = ({
|
||||
instances = []
|
||||
instances = [],
|
||||
toggleCollapsed = () => null
|
||||
}) => {
|
||||
const onClick = (uuid) => () => toggleCollapsed(uuid);
|
||||
|
||||
const empty = instances.length ? null : (
|
||||
<EmptyInstances />
|
||||
);
|
||||
|
||||
const instanceList = instances.map((service) => (
|
||||
<ListItem collapsed key={service.uuid}>
|
||||
const instanceList = instances.map((instance) => (
|
||||
<ListItem collapsed={!instance.collapsed} key={instance.uuid} >
|
||||
<ListItemView>
|
||||
<ListItemMeta>
|
||||
<ListItemTitle>{service.name}</ListItemTitle>
|
||||
<ListItemMeta onClick={onClick(instance.uuid)}>
|
||||
<ListItemTitle>{instance.name}</ListItemTitle>
|
||||
</ListItemMeta>
|
||||
<ListItemOutlet>
|
||||
<DatasetsRow metrics={instance.metrics} />
|
||||
</ListItemOutlet>
|
||||
</ListItemView>
|
||||
<ListItemOptions>
|
||||
…
|
||||
@ -51,7 +64,8 @@ const Instances = ({
|
||||
};
|
||||
|
||||
Instances.propTypes = {
|
||||
instances: React.PropTypes.arrayOf(PropTypes.instance)
|
||||
instances: React.PropTypes.arrayOf(PropTypes.instance),
|
||||
toggleCollapsed: React.PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, {
|
||||
@ -60,6 +74,11 @@ const mapStateToProps = (state, {
|
||||
instances: instancesByServiceIdSelector(params.serviceId)(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleCollapsed: (uuid) => dispatch(toggleInstanceCollapsed(uuid))
|
||||
});
|
||||
|
||||
module.exports = connect(
|
||||
mapStateToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Instances);
|
||||
|
@ -378,72 +378,130 @@
|
||||
}]
|
||||
},
|
||||
"instances": {
|
||||
"ui": {
|
||||
"collapsed": []
|
||||
},
|
||||
"data": [{
|
||||
"uuid": "309ecd9f-ac03-474b-aff7-4bd2e743296c",
|
||||
"name": "wordpress_01",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "be227788-74f1-4e5b-a85f-b5c71cbae8d8",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "0db6db53-de6f-4378-839e-5d5b452fbaf2",
|
||||
"name": "nfs_01",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "6a0eee76-c019-413b-9d5f-44712b55b993",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "250c8a6c-7d02-49a9-8abd-e1c22773041d",
|
||||
"name": "consul",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "97c68055-db88-45c9-ad49-f26da4264777",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "2c921f3a-8bc3-4f57-9cd7-789ebae72061",
|
||||
"name": "memcache_01",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "68d3046e-8e34-4f5d-a0e5-db3795a250fd",
|
||||
"name": "memcache_02",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "2ea99763-3b44-4179-8393-d66d94961051",
|
||||
"name": "memcache_03",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "25f6bc62-63b8-4959-908e-1f6d7ff6341d",
|
||||
"name": "memcache_04",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "8be01042-0281-4a77-a357-25979e87bf3d",
|
||||
"name": "memcache_05",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "6d31aff4-de1e-4042-a983-fbd23d5c530c",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "3d652e9d-73e8-4a6f-8171-84fa83740662",
|
||||
"name": "nginx",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "081a792c-47e0-4439-924b-2efa9788ae9e",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "c3ec7633-a02b-4615-86a0-9e6faeaae94b",
|
||||
"name": "percona-primary",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "4ee4103e-1a52-4099-a48e-01588f597c70",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}, {
|
||||
"uuid": "c2b5fec2-31e2-41a7-b7fc-cd0bb1822e76",
|
||||
"name": "percona-secundary",
|
||||
"datacenter": "f018da03-41c8-4619-a36a-ab8b706160cb",
|
||||
"service": "4ee4103e-1a52-4099-a48e-01588f597c70",
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401"
|
||||
"project": "e0ea0c02-55cc-45fe-8064-3e5176a59401",
|
||||
"metrics": [
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec",
|
||||
"3e6ee79a-7453-4fc6-b9da-7ae1e41138ec"
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,20 @@ const Metric = React.PropTypes.shape({
|
||||
...BaseObject
|
||||
});
|
||||
|
||||
const Dataset = React.PropTypes.shape({
|
||||
uuid: React.PropTypes.string,
|
||||
type: React.PropTypes.string,
|
||||
data: React.PropTypes.arrayOf(
|
||||
React.PropTypes.shape({
|
||||
firstQuartile: React.PropTypes.string,
|
||||
thirdQuartile: React.PropTypes.string,
|
||||
median: React.PropTypes.string,
|
||||
max: React.PropTypes.string,
|
||||
min: React.PropTypes.string
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
const Sections = React.PropTypes.arrayOf(
|
||||
React.PropTypes.string
|
||||
);
|
||||
@ -58,5 +72,6 @@ module.exports = {
|
||||
instance: Instance,
|
||||
metric: Metric,
|
||||
// consinder renaming this to 'Types' as it could be used for any
|
||||
metricTypes: MetricTypes
|
||||
metricTypes: MetricTypes,
|
||||
dataset: Dataset
|
||||
};
|
||||
|
@ -11,5 +11,6 @@ module.exports = {
|
||||
...require('@state/thunks'),
|
||||
updateRouter: createAction(`${APP}/APP/UPDATE_ROUTER`),
|
||||
toggleHeaderTooltip: createAction(`${APP}/APP/TOGGLE_HEADER_TOOLTIP`),
|
||||
toggleServiceCollapsed: createAction(`${APP}/APP/TOGGLE_SERVICE_COLLAPSED`)
|
||||
toggleServiceCollapsed: createAction(`${APP}/APP/TOGGLE_SERVICE_COLLAPSED`),
|
||||
toggleInstanceCollapsed: createAction(`${APP}/APP/TOGGLE_INSTANCE_COLLAPSED`)
|
||||
};
|
||||
|
25
frontend/src/state/reducers/common.js
Normal file
25
frontend/src/state/reducers/common.js
Normal file
@ -0,0 +1,25 @@
|
||||
const toggleCollapsed = (state, action) => {
|
||||
const {
|
||||
ui
|
||||
} = state;
|
||||
|
||||
const {
|
||||
collapsed = []
|
||||
} = ui;
|
||||
|
||||
const _collapsed = collapsed.indexOf(action.payload) >= 0
|
||||
? collapsed.filter((uuid) => uuid !== action.payload)
|
||||
: [...collapsed, action.payload];
|
||||
|
||||
return {
|
||||
...state,
|
||||
ui: {
|
||||
...ui,
|
||||
collapsed: _collapsed
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
toggleCollapsed
|
||||
};
|
@ -1,9 +1,20 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
|
||||
const actions = require('@state/actions');
|
||||
const common = require('@state/reducers/common');
|
||||
|
||||
const {
|
||||
handleActions
|
||||
} = ReduxActions;
|
||||
|
||||
const {
|
||||
toggleInstanceCollapsed
|
||||
} = actions;
|
||||
|
||||
const {
|
||||
toggleCollapsed
|
||||
} = common;
|
||||
|
||||
module.exports = handleActions({
|
||||
'x': (state) => state // somehow handleActions needs at least one reducer
|
||||
[toggleInstanceCollapsed.toString()]: toggleCollapsed
|
||||
}, {});
|
||||
|
@ -1,6 +1,7 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
|
||||
const actions = require('@state/actions');
|
||||
const common = require('@state/reducers/common');
|
||||
|
||||
const {
|
||||
handleActions
|
||||
@ -10,14 +11,10 @@ const {
|
||||
toggleServiceCollapsed
|
||||
} = actions;
|
||||
|
||||
const {
|
||||
toggleCollapsed
|
||||
} = common;
|
||||
|
||||
module.exports = handleActions({
|
||||
[toggleServiceCollapsed.toString()]: (state, action) => ({
|
||||
...state,
|
||||
ui: {
|
||||
...state.ui,
|
||||
collapsed: state.ui.collapsed.indexOf(action.payload) >= 0
|
||||
? state.ui.collapsed.filter((uuid) => uuid !== action.payload)
|
||||
: [...state.ui.collapsed, action.payload]
|
||||
}
|
||||
})
|
||||
[toggleServiceCollapsed.toString()]: toggleCollapsed
|
||||
}, {});
|
||||
|
@ -16,6 +16,7 @@ const orgs = (state) => get(state, 'orgs.data', []);
|
||||
const projects = (state) => get(state, 'projects.data', []);
|
||||
const services = (state) => get(state, 'services.data', []);
|
||||
const collapsedServices = (state) => get(state, 'services.ui.collapsed', []);
|
||||
const collapsedInstances = (state) => get(state, 'instances.ui.collapsed', []);
|
||||
const instances = (state) => get(state, 'instances.data', []);
|
||||
const metricDatasets = (state) => get(state, 'metrics.data.datasets', []);
|
||||
|
||||
@ -46,6 +47,13 @@ const orgSections = (orgId) => createSelector(
|
||||
)
|
||||
);
|
||||
|
||||
const isCollapsed = (collapsed, uuid) => collapsed.indexOf(uuid) >= 0;
|
||||
|
||||
const datasets = (metrics, uuids) => uuids.map((uuid) => find(metrics, [
|
||||
'uuid',
|
||||
uuid
|
||||
]));
|
||||
|
||||
const servicesByProjectId = (projectId) => createSelector(
|
||||
[services, projectById(projectId), collapsedServices, metricDatasets],
|
||||
(services, project, collapsed, metrics) =>
|
||||
@ -55,31 +63,27 @@ const servicesByProjectId = (projectId) => createSelector(
|
||||
services: services.filter((s) => s.parent === service.uuid)
|
||||
}))
|
||||
.filter((s) => !s.parent)
|
||||
.map((service) => {
|
||||
const isCollapsed = (uuid) => collapsed.indexOf(uuid) >= 0;
|
||||
|
||||
const datasets = (uuids) => uuids.map((uuid) => find(metrics, [
|
||||
'uuid',
|
||||
uuid
|
||||
]));
|
||||
|
||||
return {
|
||||
.map((service) => ({
|
||||
...service,
|
||||
metrics: datasets(metrics, service.metrics),
|
||||
collapsed: isCollapsed(collapsed, service.uuid),
|
||||
services: service.services.map((service) => ({
|
||||
...service,
|
||||
metrics: datasets(service.metrics),
|
||||
collapsed: isCollapsed(service.uuid),
|
||||
services: service.services.map((service) => ({
|
||||
...service,
|
||||
metrics: datasets(service.metrics),
|
||||
collapsed: isCollapsed(service.uuid)
|
||||
}))
|
||||
};
|
||||
})
|
||||
metrics: datasets(metrics, service.metrics),
|
||||
collapsed: isCollapsed(collapsed, service.uuid)
|
||||
}))
|
||||
}))
|
||||
);
|
||||
|
||||
const instancesByServiceId = (serviceId) => createSelector(
|
||||
[instances, serviceById(serviceId)],
|
||||
(instances, service) =>
|
||||
[instances, serviceById(serviceId), collapsedInstances, metricDatasets],
|
||||
(instances, service, collapsed, metrics) =>
|
||||
instances.filter((i) => i.service === service.uuid)
|
||||
.map((instance) => ({
|
||||
...instance,
|
||||
metrics: datasets(metrics, instance.metrics),
|
||||
collapsed: isCollapsed(collapsed, instance.uuid)
|
||||
}))
|
||||
);
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user