mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 02:29:03 +08:00
chore: metrics explorer improvements (#7285)
This commit is contained in:
parent
9a3c49bce4
commit
86a888a6a2
@ -16,15 +16,21 @@ export interface MetricDetails {
|
||||
timeSeriesActive: number;
|
||||
lastReceived: string;
|
||||
attributes: MetricDetailsAttribute[];
|
||||
metadata: {
|
||||
metadata?: {
|
||||
metric_type: MetricType;
|
||||
description: string;
|
||||
unit: string;
|
||||
temporality: Temporality;
|
||||
};
|
||||
alerts: MetricDetailsAlert[] | null;
|
||||
dashboards: MetricDetailsDashboard[] | null;
|
||||
}
|
||||
|
||||
export enum Temporality {
|
||||
CUMULATIVE = 'cumulative',
|
||||
DELTA = 'delta',
|
||||
}
|
||||
|
||||
export interface MetricDetailsAttribute {
|
||||
key: string;
|
||||
value: string[];
|
||||
|
@ -1,12 +1,15 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import { Temporality } from './getMetricDetails';
|
||||
import { MetricType } from './getMetricsList';
|
||||
|
||||
export interface UpdateMetricMetadataProps {
|
||||
description: string;
|
||||
unit: string;
|
||||
type: MetricType;
|
||||
metricType: MetricType;
|
||||
temporality: Temporality;
|
||||
isMonotonic?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateMetricMetadataResponse {
|
||||
|
@ -26,4 +26,5 @@ export enum LOCALSTORAGE {
|
||||
UNAUTHENTICATED_ROUTE_HIT = 'UNAUTHENTICATED_ROUTE_HIT',
|
||||
CELERY_OVERVIEW_COLUMNS = 'CELERY_OVERVIEW_COLUMNS',
|
||||
DONT_SHOW_SLOW_API_WARNING = 'DONT_SHOW_SLOW_API_WARNING',
|
||||
METRICS_LIST_OPTIONS = 'METRICS_LIST_OPTIONS',
|
||||
}
|
||||
|
@ -67,7 +67,6 @@
|
||||
.related-metrics-container {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
max-height: 450px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
@ -102,19 +101,23 @@
|
||||
}
|
||||
|
||||
.related-metrics-body {
|
||||
padding: 10px 0;
|
||||
margin-top: 20px;
|
||||
max-height: 650px;
|
||||
overflow-y: scroll;
|
||||
|
||||
.related-metrics-card-container {
|
||||
min-height: 300px;
|
||||
margin-bottom: 25px;
|
||||
margin-bottom: 20px;
|
||||
min-height: 640px;
|
||||
|
||||
.related-metrics-card {
|
||||
// height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.related-metrics-card-error {
|
||||
padding-top: 10px;
|
||||
height: fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ function Explorer(): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
const { mutate: updateDashboard, isLoading } = useUpdateDashboard();
|
||||
const { options } = useOptionsMenu({
|
||||
storageKey: LOCALSTORAGE.TRACES_LIST_OPTIONS,
|
||||
dataSource: DataSource.TRACES,
|
||||
storageKey: LOCALSTORAGE.METRICS_LIST_OPTIONS,
|
||||
dataSource: DataSource.METRICS,
|
||||
aggregateOperator: 'noop',
|
||||
});
|
||||
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Card, Col, Input, Row, Select, Skeleton } from 'antd';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { Card, Col, Empty, Input, Row, Select, Skeleton } from 'antd';
|
||||
import { Gauge } from 'lucide-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
@ -15,9 +11,7 @@ import { RelatedMetricsProps, RelatedMetricWithQueryResult } from './types';
|
||||
import { useGetRelatedMetricsGraphs } from './useGetRelatedMetricsGraphs';
|
||||
|
||||
function RelatedMetrics({ metricNames }: RelatedMetricsProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
const dimensions = useResizeObserver(graphRef);
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
@ -41,13 +35,15 @@ function RelatedMetrics({ metricNames }: RelatedMetricsProps): JSX.Element {
|
||||
}
|
||||
}, [metricNames]);
|
||||
|
||||
const { relatedMetrics, isRelatedMetricsLoading } = useGetRelatedMetricsGraphs(
|
||||
{
|
||||
const {
|
||||
relatedMetrics,
|
||||
isRelatedMetricsLoading,
|
||||
isRelatedMetricsError,
|
||||
} = useGetRelatedMetricsGraphs({
|
||||
selectedMetricName,
|
||||
startMs,
|
||||
endMs,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const metricNamesSelectOptions = useMemo(
|
||||
() =>
|
||||
@ -91,31 +87,6 @@ function RelatedMetrics({ metricNames }: RelatedMetricsProps): JSX.Element {
|
||||
return filteredMetrics;
|
||||
}, [relatedMetrics, selectedRelatedMetric, searchValue]);
|
||||
|
||||
const chartData = useMemo(
|
||||
() =>
|
||||
filteredRelatedMetrics.map(({ queryResult }) =>
|
||||
getUPlotChartData(queryResult.data?.payload),
|
||||
),
|
||||
[filteredRelatedMetrics],
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
() =>
|
||||
filteredRelatedMetrics.map(({ queryResult }) =>
|
||||
getUPlotChartOptions({
|
||||
apiResponse: queryResult.data?.payload,
|
||||
isDarkMode,
|
||||
dimensions,
|
||||
yAxisUnit: '',
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
minTimeScale: startMs,
|
||||
maxTimeScale: endMs,
|
||||
}),
|
||||
),
|
||||
[filteredRelatedMetrics, isDarkMode, dimensions, startMs, endMs],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="related-metrics-container">
|
||||
<div className="related-metrics-header">
|
||||
@ -145,20 +116,34 @@ function RelatedMetrics({ metricNames }: RelatedMetricsProps): JSX.Element {
|
||||
</div>
|
||||
<div className="related-metrics-body">
|
||||
{isRelatedMetricsLoading && <Skeleton active />}
|
||||
{isRelatedMetricsError && (
|
||||
<Empty description="Error fetching related metrics" />
|
||||
)}
|
||||
{!isRelatedMetricsLoading &&
|
||||
!isRelatedMetricsError &&
|
||||
filteredRelatedMetrics.length === 0 && (
|
||||
<Empty description="No related metrics found" />
|
||||
)}
|
||||
{!isRelatedMetricsLoading &&
|
||||
!isRelatedMetricsError &&
|
||||
filteredRelatedMetrics.length > 0 && (
|
||||
<Row gutter={24}>
|
||||
{filteredRelatedMetrics.map((relatedMetricWithQueryResult, index) => (
|
||||
<Col span={8} key={relatedMetricWithQueryResult.name}>
|
||||
<Card bordered ref={graphRef} className="related-metrics-card-container">
|
||||
{filteredRelatedMetrics.map((relatedMetricWithQueryResult) => (
|
||||
<Col span={12} key={relatedMetricWithQueryResult.name}>
|
||||
<Card
|
||||
bordered
|
||||
ref={graphRef}
|
||||
className="related-metrics-card-container"
|
||||
>
|
||||
<RelatedMetricsCard
|
||||
key={relatedMetricWithQueryResult.name}
|
||||
metric={relatedMetricWithQueryResult}
|
||||
options={options[index]}
|
||||
chartData={chartData[index]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,14 +1,11 @@
|
||||
import { Skeleton, Typography } from 'antd';
|
||||
import Uplot from 'components/Uplot';
|
||||
import { Empty, Skeleton, Typography } from 'antd';
|
||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import DashboardsAndAlertsPopover from '../MetricDetails/DashboardsAndAlertsPopover';
|
||||
import { RelatedMetricsCardProps } from './types';
|
||||
|
||||
function RelatedMetricsCard({
|
||||
metric,
|
||||
options,
|
||||
chartData,
|
||||
}: RelatedMetricsCardProps): JSX.Element {
|
||||
function RelatedMetricsCard({ metric }: RelatedMetricsCardProps): JSX.Element {
|
||||
const { queryResult } = metric;
|
||||
|
||||
if (queryResult.isLoading) {
|
||||
@ -27,13 +24,20 @@ function RelatedMetricsCard({
|
||||
{metric.name}
|
||||
</Typography.Text>
|
||||
{queryResult.isLoading ? <Skeleton /> : null}
|
||||
{queryResult.error ? (
|
||||
{queryResult.isError ? (
|
||||
<div className="related-metrics-card-error">
|
||||
<Typography.Text>Something went wrong</Typography.Text>
|
||||
<Empty description="Error fetching metric data" />
|
||||
</div>
|
||||
) : null}
|
||||
{!queryResult.isLoading && !queryResult.error && (
|
||||
<Uplot options={options} data={chartData} />
|
||||
<TimeSeriesView
|
||||
isFilterApplied={false}
|
||||
isError={queryResult.isError}
|
||||
isLoading={queryResult.isLoading}
|
||||
data={queryResult.data}
|
||||
yAxisUnit="ms"
|
||||
dataSource={DataSource.METRICS}
|
||||
/>
|
||||
)}
|
||||
<DashboardsAndAlertsPopover
|
||||
dashboards={metric.dashboards}
|
||||
|
@ -18,8 +18,6 @@ export interface RelatedMetricsProps {
|
||||
|
||||
export interface RelatedMetricsCardProps {
|
||||
metric: RelatedMetricWithQueryResult;
|
||||
options: uPlot.Options;
|
||||
chartData: any[];
|
||||
}
|
||||
|
||||
export interface UseGetRelatedMetricsGraphsProps {
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { Button, Collapse, Input, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DataType } from 'container/LogDetailedView/TableView';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { Search } from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { AllAttributesProps } from './types';
|
||||
import { getMetricDetailsQuery } from './utils';
|
||||
|
||||
function AllAttributes({
|
||||
attributes,
|
||||
@ -16,12 +19,17 @@ function AllAttributes({
|
||||
'all-attributes',
|
||||
);
|
||||
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
|
||||
const goToMetricsExploreWithAppliedAttribute = useCallback(
|
||||
(attribute: string) => {
|
||||
// TODO: Implement this when explore page is ready
|
||||
console.log(metricName, attribute);
|
||||
(key: string, value: string) => {
|
||||
const compositeQuery = getMetricDetailsQuery(metricName, { key, value });
|
||||
const encodedCompositeQuery = JSON.stringify(compositeQuery);
|
||||
safeNavigate(
|
||||
`${ROUTES.METRICS_EXPLORER_EXPLORER}?compositeQuery=${encodedCompositeQuery}`,
|
||||
);
|
||||
},
|
||||
[metricName],
|
||||
[metricName, safeNavigate],
|
||||
);
|
||||
|
||||
const filteredAttributes = useMemo(
|
||||
@ -40,7 +48,10 @@ function AllAttributes({
|
||||
label: attribute.key,
|
||||
contribution: attribute.valueCount,
|
||||
},
|
||||
value: {
|
||||
key: attribute.key,
|
||||
value: attribute.value,
|
||||
},
|
||||
}))
|
||||
: [],
|
||||
[filteredAttributes],
|
||||
@ -70,14 +81,14 @@ function AllAttributes({
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
className: 'metric-metadata-value',
|
||||
render: (attributes: string[]): JSX.Element => (
|
||||
render: (field: { key: string; value: string[] }): JSX.Element => (
|
||||
<div className="all-attributes-value">
|
||||
{attributes.map((attribute) => (
|
||||
{field.value.map((attribute) => (
|
||||
<Button
|
||||
key={attribute}
|
||||
type="text"
|
||||
onClick={(): void => {
|
||||
goToMetricsExploreWithAppliedAttribute(attribute);
|
||||
goToMetricsExploreWithAppliedAttribute(field.key, attribute);
|
||||
}}
|
||||
>
|
||||
<Typography.Text>{attribute}</Typography.Text>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Button, Collapse, Input, Select, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import { Temporality } from 'api/metricsExplorer/getMetricDetails';
|
||||
import { MetricType } from 'api/metricsExplorer/getMetricsList';
|
||||
import { UpdateMetricMetadataProps } from 'api/metricsExplorer/updateMetricMetadata';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
@ -14,6 +15,7 @@ import { METRIC_TYPE_LABEL_MAP } from '../Summary/constants';
|
||||
import { MetricTypeRenderer } from '../Summary/utils';
|
||||
import { METRIC_METADATA_KEYS } from './constants';
|
||||
import { MetadataProps } from './types';
|
||||
import { determineIsMonotonic } from './utils';
|
||||
|
||||
function Metadata({
|
||||
metricName,
|
||||
@ -25,9 +27,10 @@ function Metadata({
|
||||
metricMetadata,
|
||||
setMetricMetadata,
|
||||
] = useState<UpdateMetricMetadataProps>({
|
||||
type: metadata.metric_type,
|
||||
description: metadata.description,
|
||||
unit: metadata.unit,
|
||||
metricType: metadata?.metric_type || MetricType.SUM,
|
||||
description: metadata?.description || '',
|
||||
unit: metadata?.unit || '',
|
||||
temporality: metadata?.temporality || Temporality.CUMULATIVE,
|
||||
});
|
||||
const { notifications } = useNotifications();
|
||||
const {
|
||||
@ -41,7 +44,10 @@ function Metadata({
|
||||
const tableData = useMemo(
|
||||
() =>
|
||||
metadata
|
||||
? Object.keys(metadata).map((key) => ({
|
||||
? Object.keys(metadata)
|
||||
// Filter out isMonotonic as user input is not required
|
||||
.filter((key) => key !== 'isMonotonic')
|
||||
.map((key) => ({
|
||||
key,
|
||||
value: {
|
||||
value: metadata[key as keyof typeof metadata],
|
||||
@ -93,11 +99,28 @@ function Metadata({
|
||||
value: key,
|
||||
label: value,
|
||||
}))}
|
||||
value={metricMetadata.type}
|
||||
value={metricMetadata.metricType}
|
||||
onChange={(value): void => {
|
||||
setMetricMetadata({
|
||||
...metricMetadata,
|
||||
type: value as MetricType,
|
||||
metricType: value as MetricType,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (field.key === 'temporality') {
|
||||
return (
|
||||
<Select
|
||||
options={Object.values(Temporality).map((key) => ({
|
||||
value: key,
|
||||
label: key,
|
||||
}))}
|
||||
value={metricMetadata.temporality}
|
||||
onChange={(value): void => {
|
||||
setMetricMetadata({
|
||||
...metricMetadata,
|
||||
temporality: value as Temporality,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@ -106,7 +129,11 @@ function Metadata({
|
||||
return (
|
||||
<Input
|
||||
name={field.key}
|
||||
value={metricMetadata[field.key as keyof UpdateMetricMetadataProps]}
|
||||
value={
|
||||
metricMetadata[
|
||||
field.key as Exclude<keyof UpdateMetricMetadataProps, 'isMonotonic'>
|
||||
]
|
||||
}
|
||||
onChange={(e): void => {
|
||||
setMetricMetadata({ ...metricMetadata, [field.key]: e.target.value });
|
||||
}}
|
||||
@ -122,7 +149,13 @@ function Metadata({
|
||||
updateMetricMetadata(
|
||||
{
|
||||
metricName,
|
||||
payload: metricMetadata,
|
||||
payload: {
|
||||
...metricMetadata,
|
||||
isMonotonic: determineIsMonotonic(
|
||||
metricMetadata.metricType,
|
||||
metricMetadata.temporality,
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: (response): void => {
|
||||
|
@ -2,9 +2,19 @@ import './MetricDetails.styles.scss';
|
||||
import '../Summary/Summary.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Skeleton, Tooltip, Typography } from 'antd';
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Drawer,
|
||||
Empty,
|
||||
Skeleton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useGetMetricDetails } from 'hooks/metricsExplorer/useGetMetricDetails';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { Compass, X } from 'lucide-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
@ -16,6 +26,7 @@ import { MetricDetailsProps } from './types';
|
||||
import {
|
||||
formatNumberToCompactFormat,
|
||||
formatTimestampToReadableDate,
|
||||
getMetricDetailsQuery,
|
||||
} from './utils';
|
||||
|
||||
function MetricDetails({
|
||||
@ -24,10 +35,13 @@ function MetricDetails({
|
||||
metricName,
|
||||
}: MetricDetailsProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetching,
|
||||
error: metricDetailsError,
|
||||
refetch: refetchMetricDetails,
|
||||
} = useGetMetricDetails(metricName ?? '', {
|
||||
enabled: !!metricName,
|
||||
@ -40,7 +54,7 @@ function MetricDetails({
|
||||
return formatTimestampToReadableDate(metric.lastReceived);
|
||||
}, [metric]);
|
||||
|
||||
const isMetricDetailsLoading = isLoading || isFetching || !metric;
|
||||
const isMetricDetailsLoading = isLoading || isFetching;
|
||||
|
||||
const timeSeries = useMemo(() => {
|
||||
if (!metric) return null;
|
||||
@ -50,21 +64,28 @@ function MetricDetails({
|
||||
}, [metric]);
|
||||
|
||||
const goToMetricsExplorerwithSelectedMetric = useCallback(() => {
|
||||
// TODO: Implement this when explore page is ready
|
||||
console.log(metricName);
|
||||
}, [metricName]);
|
||||
if (metricName) {
|
||||
const compositeQuery = getMetricDetailsQuery(metricName);
|
||||
const encodedCompositeQuery = JSON.stringify(compositeQuery);
|
||||
safeNavigate(
|
||||
`${ROUTES.METRICS_EXPLORER_EXPLORER}?compositeQuery=${encodedCompositeQuery}`,
|
||||
);
|
||||
}
|
||||
}, [metricName, safeNavigate]);
|
||||
|
||||
const top5Attributes = useMemo(() => {
|
||||
if (!metric) return [];
|
||||
const totalSum =
|
||||
metric?.attributes.reduce((acc, curr) => acc + curr.valueCount, 0) || 0;
|
||||
if (!metric) return [];
|
||||
return metric.attributes.slice(0, 5).map((attr) => ({
|
||||
return metric?.attributes.slice(0, 5).map((attr) => ({
|
||||
key: attr.key,
|
||||
count: attr.valueCount,
|
||||
percentage: totalSum === 0 ? 0 : (attr.valueCount / totalSum) * 100,
|
||||
}));
|
||||
}, [metric]);
|
||||
|
||||
const isMetricDetailsError = metricDetailsError || !metric;
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
width="60%"
|
||||
@ -77,6 +98,7 @@ function MetricDetails({
|
||||
<Button
|
||||
onClick={goToMetricsExplorerwithSelectedMetric}
|
||||
icon={<Compass size={16} />}
|
||||
disabled={!metricName}
|
||||
>
|
||||
Open in Explorer
|
||||
</Button>
|
||||
@ -93,9 +115,11 @@ function MetricDetails({
|
||||
destroyOnClose
|
||||
closeIcon={<X size={16} />}
|
||||
>
|
||||
{isMetricDetailsLoading ? (
|
||||
<Skeleton active />
|
||||
) : (
|
||||
{isMetricDetailsLoading && <Skeleton active />}
|
||||
{isMetricDetailsError && !isMetricDetailsLoading && (
|
||||
<Empty description="Error fetching metric details" />
|
||||
)}
|
||||
{!isMetricDetailsLoading && !isMetricDetailsError && (
|
||||
<div className="metric-details-content">
|
||||
<div className="metric-details-content-grid">
|
||||
<div className="labels-row">
|
||||
|
@ -2,4 +2,5 @@ export const METRIC_METADATA_KEYS = {
|
||||
description: 'Description',
|
||||
unit: 'Unit',
|
||||
metric_type: 'Metric Type',
|
||||
temporality: 'Temporality',
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ export interface DashboardsAndAlertsPopoverProps {
|
||||
|
||||
export interface MetadataProps {
|
||||
metricName: string;
|
||||
metadata: MetricDetails['metadata'];
|
||||
metadata: MetricDetails['metadata'] | undefined;
|
||||
refetchMetricDetails: () => void;
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
import { Temporality } from 'api/metricsExplorer/getMetricDetails';
|
||||
import { MetricType } from 'api/metricsExplorer/getMetricsList';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export function formatTimestampToReadableDate(timestamp: string): string {
|
||||
const date = new Date(timestamp);
|
||||
// Extracting date components
|
||||
@ -19,3 +26,59 @@ export function formatNumberToCompactFormat(num: number): string {
|
||||
maximumFractionDigits: 1,
|
||||
}).format(num);
|
||||
}
|
||||
|
||||
export function determineIsMonotonic(
|
||||
metricType: MetricType,
|
||||
temporality: Temporality,
|
||||
): boolean {
|
||||
if (metricType === MetricType.HISTOGRAM) {
|
||||
return true;
|
||||
}
|
||||
if (metricType === MetricType.GAUGE || metricType === MetricType.SUMMARY) {
|
||||
return false;
|
||||
}
|
||||
if (metricType === MetricType.SUM) {
|
||||
return temporality === Temporality.CUMULATIVE;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getMetricDetailsQuery(
|
||||
metricName: string,
|
||||
filter?: { key: string; value: string },
|
||||
): Query {
|
||||
return {
|
||||
...initialQueriesMap[DataSource.METRICS],
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
...initialQueriesMap[DataSource.METRICS].builder.queryData[0],
|
||||
aggregateAttribute: {
|
||||
key: metricName,
|
||||
type: DataTypes.String,
|
||||
id: `${metricName}----string--`,
|
||||
},
|
||||
timeAggregation: 'rate',
|
||||
spaceAggregation: 'sum',
|
||||
filters: {
|
||||
op: 'AND',
|
||||
items: filter
|
||||
? [
|
||||
{
|
||||
op: '=',
|
||||
id: filter.key,
|
||||
value: filter.value,
|
||||
key: {
|
||||
key: filter.key,
|
||||
type: DataTypes.String,
|
||||
},
|
||||
},
|
||||
]
|
||||
: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
.loading-metrics {
|
||||
padding: 24px 0;
|
||||
height: 240px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
|
||||
.loading-metrics-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
|
||||
.loading-gif {
|
||||
height: 72px;
|
||||
margin-left: -24px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import './MetricsLoading.styles.scss';
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export function MetricsLoading(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
return (
|
||||
<div className="loading-metrics">
|
||||
<div className="loading-metrics-content">
|
||||
<img
|
||||
className="loading-gif"
|
||||
src="/Icons/loading-plane.gif"
|
||||
alt="wait-icon"
|
||||
/>
|
||||
|
||||
<Typography>
|
||||
{t('pending_data_placeholder', { dataSource: DataSource.METRICS })}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -7,7 +7,7 @@ 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 { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@ -99,6 +99,15 @@ function Summary(): JSX.Element {
|
||||
enabled: !!metricsTreemapQuery,
|
||||
});
|
||||
|
||||
// Reset the filters when the component mounts
|
||||
useEffect(() => {
|
||||
handleChangeQueryData('filters', {
|
||||
op: 'AND',
|
||||
items: [],
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const handleFilterChange = useCallback(
|
||||
(value: TagFilter) => {
|
||||
handleChangeQueryData('filters', value);
|
||||
|
@ -6,6 +6,7 @@ import { QueryParams } from 'constants/query';
|
||||
import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch';
|
||||
import LogsError from 'container/LogsError/LogsError';
|
||||
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
|
||||
import { MetricsLoading } from 'container/MetricsExplorer/MetricsLoading/MetricsLoading';
|
||||
import NoLogs from 'container/NoLogs/NoLogs';
|
||||
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||
import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading';
|
||||
@ -131,6 +132,10 @@ function TimeSeriesView({
|
||||
logEvent('Logs Explorer: Data present', {
|
||||
panelType: 'TIME_SERIES',
|
||||
});
|
||||
} else if (dataSource === DataSource.METRICS) {
|
||||
logEvent('Metrics Explorer: Data present', {
|
||||
panelType: 'TIME_SERIES',
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [isLoading, isError, chartData, dataSource]);
|
||||
@ -164,8 +169,9 @@ function TimeSeriesView({
|
||||
ref={graphRef}
|
||||
data-testid="time-series-graph"
|
||||
>
|
||||
{isLoading &&
|
||||
(dataSource === DataSource.LOGS ? <LogsLoading /> : <TracesLoading />)}
|
||||
{isLoading && dataSource === DataSource.LOGS && <LogsLoading />}
|
||||
{isLoading && dataSource === DataSource.TRACES && <TracesLoading />}
|
||||
{isLoading && dataSource === DataSource.METRICS && <MetricsLoading />}
|
||||
|
||||
{chartData &&
|
||||
chartData[0] &&
|
||||
|
@ -5,12 +5,12 @@ import { TabRoutes } from 'components/RouteTab/types';
|
||||
import history from 'lib/history';
|
||||
import { useLocation } from 'react-use';
|
||||
|
||||
import { Explorer, Summary, Views } from './constants';
|
||||
import { Explorer, Summary } from './constants';
|
||||
|
||||
function MetricsExplorerPage(): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const routes: TabRoutes[] = [Summary, Explorer, Views];
|
||||
const routes: TabRoutes[] = [Summary, Explorer];
|
||||
|
||||
return (
|
||||
<div className="metrics-explorer-page">
|
||||
|
Loading…
x
Reference in New Issue
Block a user