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;
description: string;
unit: string;
temporality: Temporality;
temporality?: Temporality;
};
alerts: MetricDetailsAlert[] | null;
dashboards: MetricDetailsDashboard[] | null;

View File

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

View File

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

View File

@ -7,7 +7,7 @@ export default function EmptyMetricsSearch(): JSX.Element {
<Empty
description={
<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>
}
/>

View File

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

View File

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

View File

@ -106,29 +106,31 @@ function TimeSeries({ showOneChartPerQuery }: TimeSeriesProps): JSX.Element {
};
return (
<div
className={classNames({
'time-series-container': changeLayoutForOneChartPerQuery,
})}
>
<>
<BuilderUnitsFilter onChange={onUnitChangeHandler} yAxisUnit={yAxisUnit} />
{responseData.map((datapoint, index) => (
<div
className="time-series-view"
// eslint-disable-next-line react/no-array-index-key
key={index}
>
<TimeSeriesView
isFilterApplied={false}
isError={queries[index].isError}
isLoading={queries[index].isLoading}
data={datapoint}
yAxisUnit={yAxisUnit}
dataSource={DataSource.METRICS}
/>
</div>
))}
</div>
<div
className={classNames({
'time-series-container': changeLayoutForOneChartPerQuery,
})}
>
{responseData.map((datapoint, index) => (
<div
className="time-series-view"
// eslint-disable-next-line react/no-array-index-key
key={index}
>
<TimeSeriesView
isFilterApplied={false}
isError={queries[index].isError}
isLoading={queries[index].isLoading}
data={datapoint}
yAxisUnit={yAxisUnit}
dataSource={DataSource.METRICS}
/>
</div>
))}
</div>
</>
);
}

View File

@ -1,12 +1,37 @@
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { v4 as uuid } from 'uuid';
export const splitQueryIntoOneChartPerQuery = (query: Query): Query[] =>
query.builder.queryData.map((currentQuery) => ({
...query,
id: uuid(),
builder: {
...query.builder,
queryData: [currentQuery],
},
}));
export const splitQueryIntoOneChartPerQuery = (query: Query): Query[] => {
const queries: Query[] = [];
query.builder.queryData.forEach((currentQuery) => {
const newQuery = {
...query,
id: uuid(),
builder: {
...query.builder,
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 { useUpdateMetricMetadata } from 'hooks/metricsExplorer/useUpdateMetricMetadata';
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 {
@ -32,7 +32,7 @@ function Metadata({
] = useState<UpdateMetricMetadataProps>({
metricType: metadata?.metric_type || MetricType.SUM,
description: metadata?.description || '',
temporality: metadata?.temporality || Temporality.CUMULATIVE,
temporality: metadata?.temporality,
});
const { notifications } = useNotifications();
const {
@ -46,7 +46,10 @@ function Metadata({
const tableData = useMemo(
() =>
metadata
? Object.keys(metadata)
? Object.keys({
...metadata,
temporality: metadata?.temporality,
})
// Filter out isMonotonic as user input is not required
.filter((key) => key !== 'isMonotonic')
.map((key) => ({
@ -101,12 +104,12 @@ function Metadata({
value: key,
label: METRIC_TYPE_LABEL_MAP[key as MetricType],
}))}
value={metricMetadata.metricType}
defaultValue={metricMetadata.metricType}
onChange={(value): void => {
setMetricMetadata({
...metricMetadata,
setMetricMetadata((prev) => ({
...prev,
metricType: value as MetricType,
});
}));
}}
/>
);
@ -118,12 +121,12 @@ function Metadata({
value: key,
label: key,
}))}
value={metricMetadata.temporality}
defaultValue={metricMetadata.temporality}
onChange={(value): void => {
setMetricMetadata({
...metricMetadata,
setMetricMetadata((prev) => ({
...prev,
temporality: value as Temporality,
});
}));
}}
/>
);
@ -131,13 +134,16 @@ function Metadata({
return (
<Input
name={field.key}
value={
defaultValue={
metricMetadata[
field.key as Exclude<keyof UpdateMetricMetadataProps, 'isMonotonic'>
]
}
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 => {
if (response?.payload?.success) {
if (response?.statusCode === 200) {
notifications.success({
message: 'Metadata updated successfully',
});
@ -192,33 +198,49 @@ function Metadata({
const actionButton = useMemo(() => {
if (isEditing) {
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
className="action-button"
type="text"
onClick={(e): void => {
e.stopPropagation();
handleSave();
}}
disabled={isUpdatingMetricsMetadata}
>
<Save size={14} />
<Typography.Text>Save</Typography.Text>
</Button>
</div>
);
}
return (
<div className="action-menu">
<Button
className="action-button"
type="text"
onClick={(e): void => {
e.stopPropagation();
handleSave();
setIsEditing(true);
}}
disabled={isUpdatingMetricsMetadata}
>
<Save size={14} />
<Typography.Text>Save</Typography.Text>
<Edit2 size={14} />
<Typography.Text>Edit</Typography.Text>
</Button>
);
}
return (
<Button
className="action-button"
type="text"
onClick={(e): void => {
e.stopPropagation();
setIsEditing(true);
}}
disabled={isUpdatingMetricsMetadata}
>
<Edit2 size={14} />
<Typography.Text>Edit</Typography.Text>
</Button>
</div>
);
}, [handleSave, isEditing, isUpdatingMetricsMetadata]);

View File

@ -102,14 +102,21 @@
color: var(--bg-robin-400);
}
.action-button {
.action-menu {
display: flex;
gap: 4px;
align-items: center;
align-self: flex-start;
.ant-typography {
font-family: 'Inter';
color: var(--bg-vanilla-400);
.action-button {
display: flex;
gap: 4px;
align-items: center;
align-self: flex-start;
.ant-typography {
font-family: 'Inter';
color: var(--bg-vanilla-400);
}
}
}
@ -133,7 +140,7 @@
color: var(--bg-vanilla-400);
background-color: rgba(171, 189, 255, 0.1);
height: 24px;
width: 24px;
min-width: 24px;
border-radius: 50%;
text-align: center;
display: flex;

View File

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

View File

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

View File

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

View File

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

View File

@ -2,8 +2,12 @@ import './MetricsExplorerPage.styles.scss';
import RouteTab from 'components/RouteTab';
import { TabRoutes } from 'components/RouteTab/types';
import { initialQueriesMap } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import history from 'lib/history';
import { useLayoutEffect, useMemo } from 'react';
import { useLocation } from 'react-use';
import { DataSource } from 'types/common/queryBuilder';
import { Explorer, Summary } from './constants';
@ -12,6 +16,14 @@ function MetricsExplorerPage(): JSX.Element {
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 (
<div className="metrics-explorer-page">
<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_VIEWS: ['ADMIN', 'EDITOR', 'VIEWER'],
WORKSPACE_ACCESS_RESTRICTED: ['ADMIN', 'EDITOR', 'VIEWER'],
METRICS_EXPLORER_BASE: ['ADMIN', 'EDITOR', 'VIEWER'],
};