Skip to content

Commit 0442ce5

Browse files
Merge pull request #480 from PeterYurkovich/units-dropdown
OU-388: add units selector to the metrics page
2 parents 594b28a + 9c7a7de commit 0442ce5

12 files changed

Lines changed: 219 additions & 36 deletions

File tree

web/src/components/MetricsPage.tsx

Lines changed: 138 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ import {
4242
} from '@patternfly/react-core';
4343
import { ChartLineIcon, CompressIcon } from '@patternfly/react-icons';
4444
import {
45+
IFormatterValueType,
4546
InnerScrollContainer,
4647
ISortBy,
48+
ITransform,
4749
sortable,
4850
Table,
4951
TableGridBreakpoint,
@@ -109,6 +111,11 @@ import {
109111
t_global_spacer_sm,
110112
t_global_font_family_mono,
111113
} from '@patternfly/react-tokens';
114+
import { QueryParamProvider, StringParam, useQueryParam } from 'use-query-params';
115+
import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5';
116+
import { GraphUnits, isGraphUnit } from './metrics/units';
117+
import { SimpleSelect, SimpleSelectOption } from '@patternfly/react-templates';
118+
import { valueFormatter } from './console/console-shared/src/components/query-browser/QueryBrowserTooltip';
112119

113120
// Stores information about the currently focused query input
114121
let focusedQuery;
@@ -205,7 +212,7 @@ const devQueries = (activeNamespace: string) => {
205212
];
206213
};
207214

208-
export const PreDefinedQueriesDropdown = () => {
215+
const PreDefinedQueriesDropdown = () => {
209216
const [activeNamespace] = useActiveNamespace();
210217
const { perspective } = usePerspective();
211218

@@ -570,7 +577,12 @@ const QueryKebab: React.FC<{ index: number }> = ({ index }) => {
570577
return <KebabDropdown dropdownItems={dropdownItems} />;
571578
};
572579

573-
export const QueryTable: React.FC<QueryTableProps> = ({ index, namespace, customDatasource }) => {
580+
export const QueryTable: React.FC<QueryTableProps> = ({
581+
index,
582+
namespace,
583+
customDatasource,
584+
units,
585+
}) => {
574586
const { t } = useTranslation(process.env.I18N_NAMESPACE);
575587
const { perspective } = usePerspective();
576588

@@ -579,6 +591,7 @@ export const QueryTable: React.FC<QueryTableProps> = ({ index, namespace, custom
579591
const [page, setPage] = React.useState(1);
580592
const [perPage, setPerPage] = React.useState(50);
581593
const [sortBy, setSortBy] = React.useState<ISortBy>({});
594+
const valueFormat = valueFormatter(units);
582595

583596
const isEnabled = useSelector((state: MonitoringState) =>
584597
getLegacyObserveState(perspective, state)?.getIn([
@@ -699,16 +712,39 @@ export const QueryTable: React.FC<QueryTableProps> = ({ index, namespace, custom
699712
);
700713
}
701714

702-
const transforms = [sortable, wrappable];
715+
const transforms: ITransform[] = [sortable, wrappable];
703716

704717
const buttonCell = (labels) => ({ title: <SeriesButton index={index} labels={labels} /> });
705718

706719
let columns, rows;
707720
if (data.resultType === 'scalar') {
708-
columns = ['', { title: t('Value'), transforms }];
721+
columns = [
722+
'',
723+
{
724+
title: t('Value'),
725+
transforms,
726+
cellTransforms: [
727+
(data: IFormatterValueType) => {
728+
const val = data?.title ? data.title : data;
729+
return !Number.isNaN(Number(val)) ? valueFormat(Number(val)) : val;
730+
},
731+
],
732+
},
733+
];
709734
rows = [[buttonCell({}), _.get(result, '[1]')]];
710735
} else if (data.resultType === 'string') {
711-
columns = [{ title: t('Value'), transforms }];
736+
columns = [
737+
{
738+
title: t('Value'),
739+
transforms,
740+
cellTransforms: [
741+
(data: IFormatterValueType) => {
742+
const val = data?.title ? data.title : data;
743+
return !Number.isNaN(Number(val)) ? valueFormat(Number(val)) : val;
744+
},
745+
],
746+
},
747+
];
712748
rows = [[result?.[1]]];
713749
} else {
714750
const allLabelKeys = _.uniq(_.flatMap(result, ({ metric }) => Object.keys(metric))).sort();
@@ -719,7 +755,16 @@ export const QueryTable: React.FC<QueryTableProps> = ({ index, namespace, custom
719755
title: <span>{k === '__name__' ? t('Name') : k}</span>,
720756
transforms,
721757
})),
722-
{ title: t('Value'), transforms },
758+
{
759+
title: t('Value'),
760+
transforms,
761+
cellTransforms: [
762+
(data: IFormatterValueType) => {
763+
const val = data?.title ? data.title : data;
764+
return !Number.isNaN(Number(val)) ? valueFormat(Number(val)) : val;
765+
},
766+
],
767+
},
723768
];
724769

725770
let rowMapper;
@@ -810,7 +855,13 @@ export const QueryTable: React.FC<QueryTableProps> = ({ index, namespace, custom
810855
style={{ fontFamily: t_global_font_family_mono.var }}
811856
key={`cell-${rowIndex}-${cellIndex}`}
812857
>
813-
{typeof cell === 'string' ? cell : cell?.title}
858+
{columns[cellIndex].cellTransforms
859+
? columns[cellIndex].cellTransforms[0](
860+
typeof cell === 'string' ? cell : cell?.title,
861+
)
862+
: typeof cell === 'string'
863+
? cell
864+
: cell?.title}
814865
</Td>
815866
))}
816867
</Tr>
@@ -836,10 +887,11 @@ const PromQLExpressionInput = (props) => (
836887
/>
837888
);
838889

839-
const Query: React.FC<{ index: number; customDatasource?: CustomDataSource }> = ({
840-
index,
841-
customDatasource,
842-
}) => {
890+
const Query: React.FC<{
891+
index: number;
892+
customDatasource?: CustomDataSource;
893+
units: GraphUnits;
894+
}> = ({ index, customDatasource, units }) => {
843895
const { t } = useTranslation(process.env.I18N_NAMESPACE);
844896
const { perspective } = usePerspective();
845897

@@ -924,13 +976,6 @@ const Query: React.FC<{ index: number; customDatasource?: CustomDataSource }> =
924976

925977
// If namespace is defined getPrometheusURL() will use the
926978
// PROMETHEUS_TENANCY_BASE_PATH for the developer view
927-
const queryTable = (
928-
<QueryTable
929-
index={index}
930-
customDatasource={customDatasource}
931-
namespace={perspective === 'dev' ? activeNamespace : undefined}
932-
/>
933-
);
934979

935980
return (
936981
<DataListItem aria-labelledby={`query-item-${queryId}`} isExpanded={isExpanded}>
@@ -968,7 +1013,12 @@ const Query: React.FC<{ index: number; customDatasource?: CustomDataSource }> =
9681013
id={`query-expand-${queryId}`}
9691014
isHidden={!isExpanded}
9701015
>
971-
{queryTable}
1016+
<QueryTable
1017+
index={index}
1018+
customDatasource={customDatasource}
1019+
namespace={perspective === 'dev' ? activeNamespace : undefined}
1020+
units={units}
1021+
/>
9721022
</DataListContent>
9731023
</DataListItem>
9741024
);
@@ -978,7 +1028,8 @@ const QueryBrowserWrapper: React.FC<{
9781028
customDataSourceName: string;
9791029
customDataSource: CustomDataSource;
9801030
customDatasourceError: boolean;
981-
}> = ({ customDataSourceName, customDataSource, customDatasourceError }) => {
1031+
units: GraphUnits;
1032+
}> = ({ customDataSourceName, customDataSource, customDatasourceError, units }) => {
9821033
const { t } = useTranslation(process.env.I18N_NAMESPACE);
9831034
const { perspective } = usePerspective();
9841035

@@ -1090,6 +1141,7 @@ const QueryBrowserWrapper: React.FC<{
10901141
defaultTimespan={30 * 60 * 1000}
10911142
disabledSeries={disabledSeries}
10921143
queries={queryStrings}
1144+
units={units}
10931145
showStackedControl
10941146
/>
10951147
);
@@ -1121,7 +1173,10 @@ const RunQueriesButton: React.FC = () => {
11211173
);
11221174
};
11231175

1124-
const QueriesList: React.FC<{ customDatasource?: CustomDataSource }> = ({ customDatasource }) => {
1176+
const QueriesList: React.FC<{ customDatasource?: CustomDataSource; units: GraphUnits }> = ({
1177+
customDatasource,
1178+
units,
1179+
}) => {
11251180
const { perspective } = usePerspective();
11261181
const count = useSelector(
11271182
(state: MonitoringState) =>
@@ -1133,7 +1188,12 @@ const QueriesList: React.FC<{ customDatasource?: CustomDataSource }> = ({ custom
11331188
{_.range(count).map((index) => {
11341189
const reversedIndex = count - index - 1;
11351190
return (
1136-
<Query index={reversedIndex} key={reversedIndex} customDatasource={customDatasource} />
1191+
<Query
1192+
index={reversedIndex}
1193+
key={reversedIndex}
1194+
customDatasource={customDatasource}
1195+
units={units}
1196+
/>
11371197
);
11381198
})}
11391199
</DataList>
@@ -1153,12 +1213,51 @@ const IntervalDropdown = () => {
11531213
return <DropDownPollInterval setInterval={setInterval} selectedInterval={pollInterval} />;
11541214
};
11551215

1216+
const GraphUnitsDropDown: React.FC = () => {
1217+
const { t } = useTranslation(process.env.I18N_NAMESPACE);
1218+
const [selectedUnits, setUnits] = useQueryParam(QueryParams.Units, StringParam);
1219+
1220+
const initialOptions = React.useMemo<SimpleSelectOption[]>(() => {
1221+
const intervalOptions: SimpleSelectOption[] = [
1222+
{ content: t('Bytes Binary (KiB, MiB)'), value: 'bytes' },
1223+
{ content: t('Bytes Decimal (kb, MB)'), value: 'Bytes' },
1224+
{ content: t('Bytes Binary Per Second (KiB/s, MiB/s)'), value: 'bps' },
1225+
{ content: t('Bytes Decimal Per Second (kB/s, MB/s)'), value: 'Bps' },
1226+
{ content: t('Packets Per Second'), value: 'pps' },
1227+
{ content: t('Miliseconds'), value: 'ms' },
1228+
{ content: t('Seconds'), value: 's' },
1229+
{ content: t('Percentage'), value: 'percentunit' },
1230+
{ content: t('No Units'), value: 'short' },
1231+
];
1232+
return intervalOptions.map((o) => ({ ...o, selected: o.value === selectedUnits }));
1233+
}, [selectedUnits, t]);
1234+
1235+
const onSelect = (_ev, selection: string) => {
1236+
setUnits(selection);
1237+
};
1238+
1239+
return (
1240+
<SimpleSelect
1241+
initialOptions={initialOptions}
1242+
onSelect={(_ev, selection: string) => onSelect(_ev, selection)}
1243+
toggleWidth="300px"
1244+
/>
1245+
);
1246+
};
1247+
11561248
const MetricsPage_: React.FC = () => {
11571249
const { t } = useTranslation(process.env.I18N_NAMESPACE);
1250+
const [units, setUnits] = useQueryParam(QueryParams.Units, StringParam);
11581251

11591252
const dispatch = useDispatch();
11601253
const { perspective } = usePerspective();
11611254

1255+
React.useEffect(() => {
1256+
if (!isGraphUnit(units)) {
1257+
setUnits('short');
1258+
}
1259+
}, [units, setUnits]);
1260+
11621261
// Clear queries on unmount
11631262
React.useEffect(() => {
11641263
return () => {
@@ -1240,6 +1339,11 @@ const MetricsPage_: React.FC = () => {
12401339
</SplitItem>
12411340
)}
12421341
<SplitItem isFilled />
1342+
<SplitItem>
1343+
<Tooltip content={<>{t('This dropdown only formats results.')}</>}>
1344+
<GraphUnitsDropDown />
1345+
</Tooltip>
1346+
</SplitItem>
12431347
<SplitItem>
12441348
<IntervalDropdown />
12451349
</SplitItem>
@@ -1258,6 +1362,7 @@ const MetricsPage_: React.FC = () => {
12581362
customDataSource={customDataSource}
12591363
customDataSourceName={customDataSourceName}
12601364
customDatasourceError={customDatasourceError}
1365+
units={units as GraphUnits}
12611366
/>
12621367
</StackItem>
12631368
<StackItem>
@@ -1275,24 +1380,32 @@ const MetricsPage_: React.FC = () => {
12751380
</Flex>
12761381
</StackItem>
12771382
<StackItem>
1278-
<QueriesList customDatasource={customDataSource} />
1383+
<QueriesList customDatasource={customDataSource} units={units as GraphUnits} />
12791384
</StackItem>
12801385
</Stack>
12811386
</PageSection>
12821387
</>
12831388
);
12841389
};
1285-
export const MetricsPage = withFallback(MetricsPage_);
1390+
1391+
const MetricsPage = withFallback(MetricsPage_);
1392+
1393+
const MetricsPageWrapper_: React.FC = () => (
1394+
<QueryParamProvider adapter={ReactRouter5Adapter}>
1395+
<MetricsPage />
1396+
</QueryParamProvider>
1397+
);
12861398

12871399
type QueryTableProps = {
12881400
index: number;
12891401
namespace?: string;
12901402
customDatasource?: CustomDataSource;
1403+
units: GraphUnits;
12911404
};
12921405

12931406
type SeriesButtonProps = {
12941407
index: number;
12951408
labels: PrometheusLabels;
12961409
};
12971410

1298-
export default MetricsPage;
1411+
export default MetricsPageWrapper_;

web/src/components/alerting/AlertRulesDetailsPage.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,13 @@ const AlertRulesDetailsPage_: React.FC = () => {
311311
<DescriptionListDescription>
312312
{/* display a link only if its a metrics based alert */}
313313
{(!sourceId || sourceId === 'prometheus') && perspective !== 'acm' ? (
314-
<Link to={getQueryBrowserUrl(perspective, rule?.query, namespace)}>
314+
<Link
315+
to={getQueryBrowserUrl({
316+
perspective: perspective,
317+
query: rule?.query,
318+
namespace: namespace,
319+
})}
320+
>
315321
<CodeBlock>
316322
<CodeBlockCode>{rule?.query}</CodeBlockCode>
317323
</CodeBlock>

web/src/components/alerting/AlertUtils.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export const Graph: React.FC<GraphProps> = ({
250250

251251
const GraphLink = () =>
252252
query && perspective !== 'acm' ? (
253-
<Link aria-label={t('Inspect')} to={getQueryBrowserUrl(perspective, query, namespace)}>
253+
<Link aria-label={t('Inspect')} to={getQueryBrowserUrl({ perspective, query, namespace })}>
254254
{t('Inspect')}
255255
</Link>
256256
) : null;

web/src/components/console/console-shared/src/components/query-browser/QueryBrowserTooltip.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const formatPositiveValue = (v: number): string =>
3131
const formatValue = (v: number): string => (v < 0 ? '-' : '') + formatPositiveValue(Math.abs(v));
3232

3333
export const valueFormatter = (units: string): ((v: number) => string) =>
34-
['ms', 's', 'bytes', 'Bps', 'pps'].includes(units)
34+
['ms', 's', 'bytes', 'Bytes', 'bps', 'Bps', 'pps'].includes(units)
3535
? (v: number) => formatNumber(String(v), undefined, units)
3636
: formatValue;
3737

web/src/components/console/utils/units.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,21 @@ const TYPES = {
1313
space: true,
1414
divisor: 1024,
1515
},
16+
decimalBytes: {
17+
units: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'],
18+
space: true,
19+
divisor: 1000,
20+
},
1621
SI: {
1722
units: ['', 'k', 'M', 'G', 'T', 'P', 'E'],
1823
space: false,
1924
divisor: 1000,
2025
},
26+
binaryBytesPerSec: {
27+
units: ['Bps', 'KiBps', 'MiBps', 'GiBps', 'TiBps', 'PiBps'],
28+
space: true,
29+
divisor: 1024,
30+
},
2131
decimalBytesPerSec: {
2232
units: ['Bps', 'KBps', 'MBps', 'GBps', 'TBps', 'PBps', 'EBps'],
2333
space: true,
@@ -143,6 +153,10 @@ const humanize = (units.humanize = (
143153

144154
export const humanizeBinaryBytes = (v, initialUnit, preferredUnit) =>
145155
humanize(v, 'binaryBytes', true, initialUnit, preferredUnit);
156+
export const humanizeDecimalBytes = (v, initialUnit, preferredUnit) =>
157+
humanize(v, 'decimalBytes', true, initialUnit, preferredUnit);
158+
export const humanizeBinaryBytesPerSec = (v, initialUnit, preferredUnit) =>
159+
humanize(v, 'binaryBytesPerSec', true, initialUnit, preferredUnit);
146160
export const humanizeDecimalBytesPerSec = (v, initialUnit, preferredUnit) =>
147161
humanize(v, 'decimalBytesPerSec', true, initialUnit, preferredUnit);
148162
export const humanizePacketsPerSec = (v, initialUnit, preferredUnit) =>

0 commit comments

Comments
 (0)