chore: metris explorer fixes (#7377)

This commit is contained in:
Amlan Kumar Nandy 2025-03-21 00:28:15 +05:30 committed by GitHub
parent efd4e30edf
commit ad2b75e3f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 164 additions and 90 deletions

View File

@ -20,7 +20,7 @@ export interface MetricDetails {
metric_type: MetricType; metric_type: MetricType;
description: string; description: string;
unit: string; unit: string;
temporality: Temporality; temporality?: Temporality;
}; };
alerts: MetricDetailsAlert[] | null; alerts: MetricDetailsAlert[] | null;
dashboards: MetricDetailsDashboard[] | null; dashboards: MetricDetailsDashboard[] | null;

View File

@ -7,7 +7,7 @@ import { MetricType } from './getMetricsList';
export interface UpdateMetricMetadataProps { export interface UpdateMetricMetadataProps {
description: string; description: string;
metricType: MetricType; metricType: MetricType;
temporality: Temporality; temporality?: Temporality;
isMonotonic?: boolean; isMonotonic?: boolean;
} }

View File

@ -69,6 +69,7 @@ const ROUTES = {
METRICS_EXPLORER: '/metrics-explorer/summary', METRICS_EXPLORER: '/metrics-explorer/summary',
METRICS_EXPLORER_EXPLORER: '/metrics-explorer/explorer', METRICS_EXPLORER_EXPLORER: '/metrics-explorer/explorer',
METRICS_EXPLORER_VIEWS: '/metrics-explorer/views', METRICS_EXPLORER_VIEWS: '/metrics-explorer/views',
METRICS_EXPLORER_BASE: '/metrics-explorer',
WORKSPACE_ACCESS_RESTRICTED: '/workspace-access-restricted', WORKSPACE_ACCESS_RESTRICTED: '/workspace-access-restricted',
HOME_PAGE: '/', HOME_PAGE: '/',
} as const; } as const;

View File

@ -7,7 +7,7 @@ export default function EmptyMetricsSearch(): JSX.Element {
<Empty <Empty
description={ description={
<Typography.Title level={5}> <Typography.Title level={5}>
Please build and run query to see the result Please build and run a valid query to see the result
</Typography.Title> </Typography.Title>
} }
/> />

View File

@ -69,17 +69,22 @@
height: 100%; height: 100%;
} }
.time-series-view {
min-width: 100%;
width: 100%;
}
.time-series-container { .time-series-container {
display: flex; display: flex;
gap: 10px; gap: 10px;
width: 100%; width: 100%;
height: fit-content; height: fit-content;
overflow-y: scroll; overflow-y: scroll;
}
.time-series-view { .time-series-view {
min-width: 100%; min-width: 80%;
width: 100%; width: 80%;
}
} }
.related-metrics-container { .related-metrics-container {

View File

@ -1,9 +1,8 @@
import './Explorer.styles.scss'; import './Explorer.styles.scss';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Button, Switch, Typography } from 'antd'; import { Switch } from 'antd';
import axios from 'axios'; import axios from 'axios';
import classNames from 'classnames';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper'; import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
@ -43,9 +42,7 @@ function Explorer(): JSX.Element {
}); });
const [showOneChartPerQuery, toggleShowOneChartPerQuery] = useState(false); const [showOneChartPerQuery, toggleShowOneChartPerQuery] = useState(false);
const [selectedTab, setSelectedTab] = useState<ExplorerTabs>( const [selectedTab] = useState<ExplorerTabs>(ExplorerTabs.TIME_SERIES);
ExplorerTabs.TIME_SERIES,
);
const handleToggleShowOneChartPerQuery = (): void => const handleToggleShowOneChartPerQuery = (): void =>
toggleShowOneChartPerQuery(!showOneChartPerQuery); toggleShowOneChartPerQuery(!showOneChartPerQuery);
@ -139,7 +136,8 @@ function Explorer(): JSX.Element {
</div> </div>
</div> </div>
<QuerySection /> <QuerySection />
<Button.Group className="explore-tabs"> {/* TODO: Enable once we have resolved all related metrics issues */}
{/* <Button.Group className="explore-tabs">
<Button <Button
value={ExplorerTabs.TIME_SERIES} value={ExplorerTabs.TIME_SERIES}
className={classNames('tab', { className={classNames('tab', {
@ -149,8 +147,7 @@ function Explorer(): JSX.Element {
> >
<Typography.Text>Time series</Typography.Text> <Typography.Text>Time series</Typography.Text>
</Button> </Button>
{/* TODO: Enable once we have resolved all related metrics issues */} <Button
{/* <Button
value={ExplorerTabs.RELATED_METRICS} value={ExplorerTabs.RELATED_METRICS}
className={classNames('tab', { className={classNames('tab', {
'selected-view': selectedTab === ExplorerTabs.RELATED_METRICS, 'selected-view': selectedTab === ExplorerTabs.RELATED_METRICS,
@ -158,8 +155,8 @@ function Explorer(): JSX.Element {
onClick={(): void => setSelectedTab(ExplorerTabs.RELATED_METRICS)} onClick={(): void => setSelectedTab(ExplorerTabs.RELATED_METRICS)}
> >
<Typography.Text>Related</Typography.Text> <Typography.Text>Related</Typography.Text>
</Button> */} </Button>
</Button.Group> </Button.Group> */}
<div className="explore-content"> <div className="explore-content">
{selectedTab === ExplorerTabs.TIME_SERIES && ( {selectedTab === ExplorerTabs.TIME_SERIES && (
<TimeSeries showOneChartPerQuery={showOneChartPerQuery} /> <TimeSeries showOneChartPerQuery={showOneChartPerQuery} />

View File

@ -106,12 +106,13 @@ function TimeSeries({ showOneChartPerQuery }: TimeSeriesProps): JSX.Element {
}; };
return ( return (
<>
<BuilderUnitsFilter onChange={onUnitChangeHandler} yAxisUnit={yAxisUnit} />
<div <div
className={classNames({ className={classNames({
'time-series-container': changeLayoutForOneChartPerQuery, 'time-series-container': changeLayoutForOneChartPerQuery,
})} })}
> >
<BuilderUnitsFilter onChange={onUnitChangeHandler} yAxisUnit={yAxisUnit} />
{responseData.map((datapoint, index) => ( {responseData.map((datapoint, index) => (
<div <div
className="time-series-view" className="time-series-view"
@ -129,6 +130,7 @@ function TimeSeries({ showOneChartPerQuery }: TimeSeriesProps): JSX.Element {
</div> </div>
))} ))}
</div> </div>
</>
); );
} }

View File

@ -1,12 +1,37 @@
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
export const splitQueryIntoOneChartPerQuery = (query: Query): Query[] => export const splitQueryIntoOneChartPerQuery = (query: Query): Query[] => {
query.builder.queryData.map((currentQuery) => ({ const queries: Query[] = [];
query.builder.queryData.forEach((currentQuery) => {
const newQuery = {
...query, ...query,
id: uuid(), id: uuid(),
builder: { builder: {
...query.builder, ...query.builder,
queryData: [currentQuery], queryData: [currentQuery],
queryFormulas: [],
}, },
})); };
queries.push(newQuery);
});
query.builder.queryFormulas.forEach((currentFormula) => {
const newQuery = {
...query,
id: uuid(),
builder: {
...query.builder,
queryFormulas: [currentFormula],
queryData: query.builder.queryData.map((currentQuery) => ({
...currentQuery,
disabled: true,
})),
},
};
queries.push(newQuery);
});
return queries;
};

View File

@ -8,7 +8,7 @@ import FieldRenderer from 'container/LogDetailedView/FieldRenderer';
import { DataType } from 'container/LogDetailedView/TableView'; import { DataType } from 'container/LogDetailedView/TableView';
import { useUpdateMetricMetadata } from 'hooks/metricsExplorer/useUpdateMetricMetadata'; import { useUpdateMetricMetadata } from 'hooks/metricsExplorer/useUpdateMetricMetadata';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import { Edit2, Save } from 'lucide-react'; import { Edit2, Save, X } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { import {
@ -32,7 +32,7 @@ function Metadata({
] = useState<UpdateMetricMetadataProps>({ ] = useState<UpdateMetricMetadataProps>({
metricType: metadata?.metric_type || MetricType.SUM, metricType: metadata?.metric_type || MetricType.SUM,
description: metadata?.description || '', description: metadata?.description || '',
temporality: metadata?.temporality || Temporality.CUMULATIVE, temporality: metadata?.temporality,
}); });
const { notifications } = useNotifications(); const { notifications } = useNotifications();
const { const {
@ -46,7 +46,10 @@ function Metadata({
const tableData = useMemo( const tableData = useMemo(
() => () =>
metadata metadata
? Object.keys(metadata) ? Object.keys({
...metadata,
temporality: metadata?.temporality,
})
// Filter out isMonotonic as user input is not required // Filter out isMonotonic as user input is not required
.filter((key) => key !== 'isMonotonic') .filter((key) => key !== 'isMonotonic')
.map((key) => ({ .map((key) => ({
@ -101,12 +104,12 @@ function Metadata({
value: key, value: key,
label: METRIC_TYPE_LABEL_MAP[key as MetricType], label: METRIC_TYPE_LABEL_MAP[key as MetricType],
}))} }))}
value={metricMetadata.metricType} defaultValue={metricMetadata.metricType}
onChange={(value): void => { onChange={(value): void => {
setMetricMetadata({ setMetricMetadata((prev) => ({
...metricMetadata, ...prev,
metricType: value as MetricType, metricType: value as MetricType,
}); }));
}} }}
/> />
); );
@ -118,12 +121,12 @@ function Metadata({
value: key, value: key,
label: key, label: key,
}))} }))}
value={metricMetadata.temporality} defaultValue={metricMetadata.temporality}
onChange={(value): void => { onChange={(value): void => {
setMetricMetadata({ setMetricMetadata((prev) => ({
...metricMetadata, ...prev,
temporality: value as Temporality, temporality: value as Temporality,
}); }));
}} }}
/> />
); );
@ -131,13 +134,16 @@ function Metadata({
return ( return (
<Input <Input
name={field.key} name={field.key}
value={ defaultValue={
metricMetadata[ metricMetadata[
field.key as Exclude<keyof UpdateMetricMetadataProps, 'isMonotonic'> field.key as Exclude<keyof UpdateMetricMetadataProps, 'isMonotonic'>
] ]
} }
onChange={(e): void => { onChange={(e): void => {
setMetricMetadata({ ...metricMetadata, [field.key]: e.target.value }); setMetricMetadata((prev) => ({
...prev,
[field.key]: e.target.value,
}));
}} }}
/> />
); );
@ -161,7 +167,7 @@ function Metadata({
}, },
{ {
onSuccess: (response): void => { onSuccess: (response): void => {
if (response?.payload?.success) { if (response?.statusCode === 200) {
notifications.success({ notifications.success({
message: 'Metadata updated successfully', message: 'Metadata updated successfully',
}); });
@ -192,6 +198,19 @@ function Metadata({
const actionButton = useMemo(() => { const actionButton = useMemo(() => {
if (isEditing) { if (isEditing) {
return ( return (
<div className="action-menu">
<Button
className="action-button"
type="text"
onClick={(e): void => {
e.stopPropagation();
setIsEditing(false);
}}
disabled={isUpdatingMetricsMetadata}
>
<X size={14} />
<Typography.Text>Cancel</Typography.Text>
</Button>
<Button <Button
className="action-button" className="action-button"
type="text" type="text"
@ -204,9 +223,11 @@ function Metadata({
<Save size={14} /> <Save size={14} />
<Typography.Text>Save</Typography.Text> <Typography.Text>Save</Typography.Text>
</Button> </Button>
</div>
); );
} }
return ( return (
<div className="action-menu">
<Button <Button
className="action-button" className="action-button"
type="text" type="text"
@ -219,6 +240,7 @@ function Metadata({
<Edit2 size={14} /> <Edit2 size={14} />
<Typography.Text>Edit</Typography.Text> <Typography.Text>Edit</Typography.Text>
</Button> </Button>
</div>
); );
}, [handleSave, isEditing, isUpdatingMetricsMetadata]); }, [handleSave, isEditing, isUpdatingMetricsMetadata]);

View File

@ -102,6 +102,12 @@
color: var(--bg-robin-400); color: var(--bg-robin-400);
} }
.action-menu {
display: flex;
gap: 4px;
align-items: center;
align-self: flex-start;
.action-button { .action-button {
display: flex; display: flex;
gap: 4px; gap: 4px;
@ -112,6 +118,7 @@
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
} }
} }
}
.all-attributes-search-input { .all-attributes-search-input {
width: 300px; width: 300px;
@ -133,7 +140,7 @@
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
background-color: rgba(171, 189, 255, 0.1); background-color: rgba(171, 189, 255, 0.1);
height: 24px; height: 24px;
width: 24px; min-width: 24px;
border-radius: 50%; border-radius: 50%;
text-align: center; text-align: center;
display: flex; display: flex;

View File

@ -41,7 +41,7 @@ export function formatNumberToCompactFormat(num: number): string {
export function determineIsMonotonic( export function determineIsMonotonic(
metricType: MetricType, metricType: MetricType,
temporality: Temporality, temporality?: Temporality,
): boolean { ): boolean {
if ( if (
metricType === MetricType.HISTOGRAM || metricType === MetricType.HISTOGRAM ||

View File

@ -68,6 +68,7 @@ function MetricNameSearch(): JSX.Element {
(selectedMetricName: string): void => { (selectedMetricName: string): void => {
handleChangeQueryData('filters', { handleChangeQueryData('filters', {
items: [ items: [
...currentQuery.builder.queryData[0].filters.items,
{ {
id: 'metric_name', id: 'metric_name',
op: 'CONTAINS', op: 'CONTAINS',
@ -83,7 +84,7 @@ function MetricNameSearch(): JSX.Element {
}); });
setIsPopoverOpen(false); setIsPopoverOpen(false);
}, },
[handleChangeQueryData], [currentQuery.builder.queryData, handleChangeQueryData],
); );
const metricNameFilterValues = useMemo( const metricNameFilterValues = useMemo(

View File

@ -36,6 +36,7 @@ function MetricTypeSearch(): JSX.Element {
if (selectedMetricType !== 'all') { if (selectedMetricType !== 'all') {
handleChangeQueryData('filters', { handleChangeQueryData('filters', {
items: [ items: [
...currentQuery.builder.queryData[0].filters.items,
{ {
id: 'metric_type', id: 'metric_type',
op: '=', op: '=',

View File

@ -1,17 +1,16 @@
import './Summary.styles.scss'; import './Summary.styles.scss';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { initialQueriesMap } from 'constants/queryBuilder';
import { usePageSize } from 'container/InfraMonitoringK8s/utils'; import { usePageSize } from 'container/InfraMonitoringK8s/utils';
import { useGetMetricsList } from 'hooks/metricsExplorer/useGetMetricsList'; import { useGetMetricsList } from 'hooks/metricsExplorer/useGetMetricsList';
import { useGetMetricsTreeMap } from 'hooks/metricsExplorer/useGetMetricsTreeMap'; import { useGetMetricsTreeMap } from 'hooks/metricsExplorer/useGetMetricsTreeMap';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import MetricDetails from '../MetricDetails'; import MetricDetails from '../MetricDetails';
@ -44,7 +43,7 @@ function Summary(): JSX.Element {
(state) => state.globalTime, (state) => state.globalTime,
); );
const currentQuery = initialQueriesMap[DataSource.METRICS]; const { currentQuery } = useQueryBuilder();
const queryFilters = useMemo( const queryFilters = useMemo(
() => () =>
currentQuery?.builder?.queryData[0]?.filters || { currentQuery?.builder?.queryData[0]?.filters || {

View File

@ -161,6 +161,7 @@ export const NEW_ROUTES_MENU_ITEM_KEY_MAP: Record<string, string> = {
[ROUTES.TRACE]: ROUTES.TRACES_EXPLORER, [ROUTES.TRACE]: ROUTES.TRACES_EXPLORER,
[ROUTES.TRACE_EXPLORER]: ROUTES.TRACES_EXPLORER, [ROUTES.TRACE_EXPLORER]: ROUTES.TRACES_EXPLORER,
[ROUTES.LOGS_BASE]: ROUTES.LOGS_EXPLORER, [ROUTES.LOGS_BASE]: ROUTES.LOGS_EXPLORER,
[ROUTES.METRICS_EXPLORER_BASE]: ROUTES.METRICS_EXPLORER,
}; };
export default menuItems; export default menuItems;

View File

@ -2,8 +2,12 @@ import './MetricsExplorerPage.styles.scss';
import RouteTab from 'components/RouteTab'; import RouteTab from 'components/RouteTab';
import { TabRoutes } from 'components/RouteTab/types'; import { TabRoutes } from 'components/RouteTab/types';
import { initialQueriesMap } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import history from 'lib/history'; import history from 'lib/history';
import { useLayoutEffect, useMemo } from 'react';
import { useLocation } from 'react-use'; import { useLocation } from 'react-use';
import { DataSource } from 'types/common/queryBuilder';
import { Explorer, Summary } from './constants'; import { Explorer, Summary } from './constants';
@ -12,6 +16,14 @@ function MetricsExplorerPage(): JSX.Element {
const routes: TabRoutes[] = [Summary, Explorer]; const routes: TabRoutes[] = [Summary, Explorer];
const initialQuery = useMemo(() => initialQueriesMap[DataSource.METRICS], []);
const { resetQuery } = useQueryBuilder();
useLayoutEffect(() => {
resetQuery(initialQuery);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return ( return (
<div className="metrics-explorer-page"> <div className="metrics-explorer-page">
<RouteTab routes={routes} activeKey={pathname} history={history} /> <RouteTab routes={routes} activeKey={pathname} history={history} />

View File

@ -116,4 +116,5 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
METRICS_EXPLORER_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'], METRICS_EXPLORER_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
METRICS_EXPLORER_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'], METRICS_EXPLORER_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
WORKSPACE_ACCESS_RESTRICTED: ['ADMIN', 'EDITOR', 'VIEWER'], WORKSPACE_ACCESS_RESTRICTED: ['ADMIN', 'EDITOR', 'VIEWER'],
METRICS_EXPLORER_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
}; };