Skip to content

Commit 837fef4

Browse files
committed
feat: add disconnected to line chart
Signed-off-by: Gabriel Bernal <gbernal@redhat.com>
1 parent 087dfd7 commit 837fef4

2 files changed

Lines changed: 101 additions & 40 deletions

File tree

web/src/components/MetricsPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,6 +1091,7 @@ const QueryBrowserWrapper: React.FC<{
10911091
disabledSeries={disabledSeries}
10921092
queries={queryStrings}
10931093
showStackedControl
1094+
showDisconnectedControl
10941095
/>
10951096
);
10961097
};

web/src/components/query-browser.tsx

Lines changed: 100 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
import * as _ from 'lodash-es';
2-
import classNames from 'classnames';
3-
import * as React from 'react';
41
import {
52
PrometheusEndpoint,
63
PrometheusLabels,
@@ -22,28 +19,32 @@ import {
2219
import {
2320
Alert,
2421
Button,
22+
Card,
23+
CardBody,
24+
CardHeader,
2525
Checkbox,
2626
Dropdown,
2727
DropdownItem,
2828
DropdownList,
2929
EmptyState,
3030
EmptyStateBody,
3131
EmptyStateVariant,
32-
MenuToggle,
33-
MenuToggleElement,
3432
InputGroup,
35-
TextInput,
36-
Title,
3733
InputGroupItem,
38-
Card,
39-
CardHeader,
40-
Split,
41-
SplitItem,
4234
Level,
4335
LevelItem,
44-
CardBody,
36+
MenuToggle,
37+
MenuToggleElement,
38+
Split,
39+
SplitItem,
40+
TextInput,
41+
Title,
42+
Tooltip,
4543
} from '@patternfly/react-core';
4644
import { ChartLineIcon } from '@patternfly/react-icons';
45+
import classNames from 'classnames';
46+
import * as _ from 'lodash-es';
47+
import * as React from 'react';
4748
import { useTranslation } from 'react-i18next';
4849
import { useDispatch, useSelector } from 'react-redux';
4950

@@ -69,24 +70,24 @@ import { queryBrowserTheme } from './query-browser-theme';
6970
import { PrometheusAPIError, TimeRange } from './types';
7071
import { getTimeRanges } from './utils';
7172

72-
import { getLegacyObserveState, getObserveState, usePerspective } from './hooks/usePerspective';
73-
import { MonitoringState } from '../reducers/observe';
74-
import { LoadingInline } from './console/console-shared/src/components/loading/LoadingInline';
73+
import { CustomDataSource } from '@openshift-console/dynamic-plugin-sdk/lib/extensions/dashboard-data-source';
7574
import {
76-
formatPrometheusDuration,
77-
parsePrometheusDuration,
78-
} from './console/console-shared/src/datetime/prometheus';
75+
chart_area_Opacity,
76+
chart_axis_tick_Size,
77+
t_chart_global_fill_color_200,
78+
} from '@patternfly/react-tokens';
79+
import { MonitoringState } from '../reducers/observe';
7980
import withFallback from './console/console-shared/error/fallbacks/withFallback';
80-
import { CustomDataSource } from '@openshift-console/dynamic-plugin-sdk/lib/extensions/dashboard-data-source';
81+
import { LoadingInline } from './console/console-shared/src/components/loading/LoadingInline';
8182
import {
8283
QueryBrowserTooltip,
8384
valueFormatter,
8485
} from './console/console-shared/src/components/query-browser/QueryBrowserTooltip';
8586
import {
86-
chart_area_Opacity,
87-
chart_axis_tick_Size,
88-
t_chart_global_fill_color_200,
89-
} from '@patternfly/react-tokens';
87+
formatPrometheusDuration,
88+
parsePrometheusDuration,
89+
} from './console/console-shared/src/datetime/prometheus';
90+
import { getLegacyObserveState, getObserveState, usePerspective } from './hooks/usePerspective';
9091
import './query-browser.scss';
9192

9293
const spans = ['5m', '15m', '30m', '1h', '2h', '6h', '12h', '1d', '2d', '1w', '2w'];
@@ -415,7 +416,9 @@ const formatSeriesValues = (
415416
samples: number,
416417
span: number,
417418
defaultEmptyValue: 0 | null,
418-
): GraphDataPoint[] => {
419+
createGaps: boolean,
420+
): { points: GraphDataPoint[]; hasDisconnectedValues: boolean } => {
421+
let hasDisconnectedValues = false;
419422
const newValues = _.map(values, (v) => {
420423
const y = Number(v[1]);
421424
return {
@@ -424,19 +427,23 @@ const formatSeriesValues = (
424427
};
425428
});
426429

427-
// The data may have missing values, so we fill those gaps with nulls so that the graph correctly
430+
// The data may have missing values, if disconnected is enabled,
431+
// we fill those gaps with nulls so that the graph correctly
428432
// shows the missing values as gaps in the line
429433
const start = Number(_.get(newValues, '[0].x'));
430434
const end = Number(_.get(_.last(newValues), 'x'));
431435
const step = span / samples;
432436
_.range(start, end, step).forEach((t, i) => {
433437
const x = new Date(t);
434438
if (_.get(newValues, [i, 'x']) > x) {
435-
newValues.splice(i, 0, { x, y: null });
439+
hasDisconnectedValues = true;
440+
if (createGaps) {
441+
newValues.splice(i, 0, { x, y: null });
442+
}
436443
}
437444
});
438445

439-
return newValues;
446+
return { points: newValues, hasDisconnectedValues };
440447
};
441448

442449
// Try to limit the graph to this number of data points
@@ -581,6 +588,7 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
581588
queries,
582589
showLegend,
583590
showStackedControl = false,
591+
showDisconnectedControl = true,
584592
timespan,
585593
units,
586594
onDataChange,
@@ -626,6 +634,8 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
626634
const safeFetch = useSafeFetch();
627635

628636
const [isStacked, setIsStacked] = React.useState(isStack);
637+
const [showDisconnectedValues, setIsShowDisconnectedValues] = React.useState(false);
638+
const [isDisconnectedEnabled, setIsDisconnectedEnabled] = React.useState(true);
629639

630640
const canStack = _.sumBy(graphData, 'length') <= maxStacks;
631641

@@ -737,6 +747,8 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
737747
maxSamplesForSpan,
738748
);
739749

750+
let dataIsDisconnected = false;
751+
740752
// Change `samples` if either
741753
// - It will change by a proportion greater than `samplesLeeway`
742754
// - It will change to the upper or lower limit of its allowed range
@@ -764,14 +776,26 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
764776
);
765777
defaultEmptyValue = 0;
766778
}
767-
return [metric, formatSeriesValues(values, samples, span, defaultEmptyValue)];
779+
const { points, hasDisconnectedValues } = formatSeriesValues(
780+
values,
781+
samples,
782+
span,
783+
defaultEmptyValue,
784+
showDisconnectedValues,
785+
);
786+
787+
dataIsDisconnected = hasDisconnectedValues;
788+
789+
return [metric, points];
768790
}
769791
});
770792
},
771793
);
772794
setGraphData(newGraphData);
773795
onDataChange?.(newGraphData);
774796

797+
setIsDisconnectedEnabled(dataIsDisconnected);
798+
775799
_.each(newResults, (r, i) =>
776800
dispatch(queryBrowserPatchQuery(i, { series: r ? _.map(r, 'metric') : undefined })),
777801
);
@@ -815,6 +839,7 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
815839
samples,
816840
span,
817841
lastRequestTime,
842+
showDisconnectedValues,
818843
);
819844

820845
React.useLayoutEffect(
@@ -908,18 +933,52 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
908933
</SplitItem>
909934
<SplitItem isFilled />
910935
<SplitItem>
911-
{GraphLink && <GraphLink />}
912-
{canStack && showStackedControl && (
913-
<Checkbox
914-
id="stacked"
915-
isChecked={isStacked}
916-
data-checked-state={isStacked}
917-
label={t('Stacked')}
918-
onChange={(_e, v) =>
919-
typeof _e === 'boolean' ? setIsStacked(_e) : setIsStacked(v)
920-
}
921-
/>
922-
)}
936+
<Split hasGutter>
937+
{GraphLink && (
938+
<SplitItem>
939+
<GraphLink />
940+
</SplitItem>
941+
)}
942+
{canStack && showStackedControl && (
943+
<SplitItem>
944+
<Checkbox
945+
id="stacked"
946+
isChecked={isStacked}
947+
data-checked-state={isStacked}
948+
label={t('Stacked')}
949+
onChange={(_e, v) =>
950+
typeof _e === 'boolean' ? setIsStacked(_e) : setIsStacked(v)
951+
}
952+
/>
953+
</SplitItem>
954+
)}
955+
{showDisconnectedControl && (
956+
<SplitItem>
957+
<Tooltip
958+
content={
959+
<div>
960+
{isDisconnectedEnabled
961+
? t('Check to show gaps for missing data')
962+
: t('No gaps found in the data')}
963+
</div>
964+
}
965+
>
966+
<Checkbox
967+
id="disconnected"
968+
isChecked={isDisconnectedEnabled && showDisconnectedValues}
969+
data-checked-state={isDisconnectedEnabled && showDisconnectedValues}
970+
label={t('Disconnected')}
971+
onChange={(_e, v) =>
972+
typeof _e === 'boolean'
973+
? setIsShowDisconnectedValues(_e)
974+
: setIsShowDisconnectedValues(v)
975+
}
976+
isDisabled={!isDisconnectedEnabled}
977+
/>
978+
</Tooltip>
979+
</SplitItem>
980+
)}
981+
</Split>
923982
</SplitItem>
924983
</Split>
925984
</CardHeader>
@@ -1028,6 +1087,7 @@ export type QueryBrowserProps = {
10281087
queries: string[];
10291088
showLegend?: boolean;
10301089
showStackedControl?: boolean;
1090+
showDisconnectedControl?: boolean;
10311091
timespan?: number;
10321092
units?: string;
10331093
onDataChange?: (data: any) => void;

0 commit comments

Comments
 (0)