Skip to content

Commit 75ad8fa

Browse files
Merge pull request #477 from jgbernalp/add-disconnect-option-to-line-chart
OU-863: feat: add disconnected to line chart
2 parents 0442ce5 + 837fef4 commit 75ad8fa

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
@@ -1143,6 +1143,7 @@ const QueryBrowserWrapper: React.FC<{
11431143
queries={queryStrings}
11441144
units={units}
11451145
showStackedControl
1146+
showDisconnectedControl
11461147
/>
11471148
);
11481149
};

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
import { GraphUnits } from './metrics/units';
9293

@@ -416,7 +417,9 @@ const formatSeriesValues = (
416417
samples: number,
417418
span: number,
418419
defaultEmptyValue: 0 | null,
419-
): GraphDataPoint[] => {
420+
createGaps: boolean,
421+
): { points: GraphDataPoint[]; hasDisconnectedValues: boolean } => {
422+
let hasDisconnectedValues = false;
420423
const newValues = _.map(values, (v) => {
421424
const y = Number(v[1]);
422425
return {
@@ -425,19 +428,23 @@ const formatSeriesValues = (
425428
};
426429
});
427430

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

440-
return newValues;
447+
return { points: newValues, hasDisconnectedValues };
441448
};
442449

443450
// Try to limit the graph to this number of data points
@@ -582,6 +589,7 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
582589
queries,
583590
showLegend,
584591
showStackedControl = false,
592+
showDisconnectedControl = true,
585593
timespan,
586594
units,
587595
onDataChange,
@@ -627,6 +635,8 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
627635
const safeFetch = useSafeFetch();
628636

629637
const [isStacked, setIsStacked] = React.useState(isStack);
638+
const [showDisconnectedValues, setIsShowDisconnectedValues] = React.useState(false);
639+
const [isDisconnectedEnabled, setIsDisconnectedEnabled] = React.useState(true);
630640

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

@@ -738,6 +748,8 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
738748
maxSamplesForSpan,
739749
);
740750

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

798+
setIsDisconnectedEnabled(dataIsDisconnected);
799+
776800
_.each(newResults, (r, i) =>
777801
dispatch(queryBrowserPatchQuery(i, { series: r ? _.map(r, 'metric') : undefined })),
778802
);
@@ -816,6 +840,7 @@ const QueryBrowser_: React.FC<QueryBrowserProps> = ({
816840
samples,
817841
span,
818842
lastRequestTime,
843+
showDisconnectedValues,
819844
);
820845

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

0 commit comments

Comments
 (0)