mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 05:55:59 +08:00
Service layer to metrics using USE_SPAN_METRIC
feature flag (#3196)
* refactor: remove the dependency of services using redux * refactor: seperated columns and unit test case * refactor: move the constant to other file * refactor: updated test case * refactor: removed the duplicate enum * fix: removed the inline function * fix: removed the inline function * refactor: removed the magic string * fix: change the name from matrics to metrics * fix: one on one mapping of props * refactor: created a hook to getting services through api call * fix: linter error * refactor: renamed the file according to functionality * refactor: renamed more file according to functionality * refactor: generic querybuilderWithFormula * refactor: added generic datasource * refactor: dynamic disabled in getQueryBuilderQueriesWithFormula * refactor: generic legend for building query with formulas * feat: added new TopOperationMetrics component for key operation * refactor: added feature flag for key operation * refactor: shifted types and fixed typos * refactor: separated types and renamed file * refactor: one on one mapping * refactor: removed unwanted interfaces and renamed files * refactor: separated types * chore: done with basic struction and moving up the files * chore: moved some files to proper places * feat: added the support for metrics in service layer * refactor: shifted SkipOnBoardingModal logic to parent * refactor: created object to send as an augument for getQueryRangeRequestData * refactor: changes from columns to getColumns * refactor: updated the utils function getServiceListFromQuery * refactor: added memo to getQueryRangeRequestData in serive metrics application * refactor: separated constants from ServiceMetricsQuery.ts * refactor: separated mock data and updated test case * refactor: added useMemo on getColumns * refactor: made the use of useErrorNotification for show error * refactor: handled the error case * refactor: one on one mapping * chore: useGetQueriesRange hooks type is updated * refactor: review changes * chore: update type for columnconstants * chore: reverted back the changes lost in merge conflicts --------- Co-authored-by: Vishal Sharma <makeavish786@gmail.com> Co-authored-by: Palash Gupta <palashgdev@gmail.com> Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
parent
68ab022836
commit
562621a117
@ -51,6 +51,7 @@ const themeColors = {
|
|||||||
snowWhite: '#fafafa',
|
snowWhite: '#fafafa',
|
||||||
gamboge: '#D89614',
|
gamboge: '#D89614',
|
||||||
bckgGrey: '#1d1d1d',
|
bckgGrey: '#1d1d1d',
|
||||||
|
lightBlue: '#177ddc',
|
||||||
};
|
};
|
||||||
|
|
||||||
export { themeColors };
|
export { themeColors };
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
} from 'types/common/queryBuilder';
|
} from 'types/common/queryBuilder';
|
||||||
|
|
||||||
import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant';
|
import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant';
|
||||||
import { IServiceName } from '../Tabs/types';
|
import { DatabaseCallProps, DatabaseCallsRPSProps } from '../types';
|
||||||
import {
|
import {
|
||||||
getQueryBuilderQueries,
|
getQueryBuilderQueries,
|
||||||
getQueryBuilderQuerieswithFormula,
|
getQueryBuilderQuerieswithFormula,
|
||||||
@ -103,8 +103,8 @@ export const databaseCallsAvgDuration = ({
|
|||||||
|
|
||||||
const legends = ['', ''];
|
const legends = ['', ''];
|
||||||
const disabled = [true, true];
|
const disabled = [true, true];
|
||||||
const legendFormula = 'Average Duration';
|
const legendFormulas = ['Average Duration'];
|
||||||
const expression = FORMULA.DATABASE_CALLS_AVG_DURATION;
|
const expressions = [FORMULA.DATABASE_CALLS_AVG_DURATION];
|
||||||
const aggregateOperators = [
|
const aggregateOperators = [
|
||||||
MetricAggregateOperator.SUM,
|
MetricAggregateOperator.SUM,
|
||||||
MetricAggregateOperator.SUM,
|
MetricAggregateOperator.SUM,
|
||||||
@ -116,18 +116,9 @@ export const databaseCallsAvgDuration = ({
|
|||||||
additionalItems,
|
additionalItems,
|
||||||
legends,
|
legends,
|
||||||
disabled,
|
disabled,
|
||||||
expression,
|
expressions,
|
||||||
legendFormula,
|
legendFormulas,
|
||||||
aggregateOperators,
|
aggregateOperators,
|
||||||
dataSource,
|
dataSource,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DatabaseCallsRPSProps extends DatabaseCallProps {
|
|
||||||
legend: '{{db_system}}';
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DatabaseCallProps {
|
|
||||||
servicename: IServiceName['servicename'];
|
|
||||||
tagFilterItems: TagFilterItem[];
|
|
||||||
}
|
|
||||||
|
@ -83,22 +83,18 @@ export const externalCallErrorPercent = ({
|
|||||||
},
|
},
|
||||||
...tagFilterItems,
|
...tagFilterItems,
|
||||||
];
|
];
|
||||||
|
const legendFormulas = [legend];
|
||||||
|
const expressions = [FORMULA.ERROR_PERCENTAGE];
|
||||||
|
const disabled = [true, true];
|
||||||
|
const autocompleteData = [autocompleteDataA, autocompleteDataB];
|
||||||
|
|
||||||
const legendFormula = legend;
|
const additionalItems = [additionalItemsA, additionalItemsB];
|
||||||
const expression = FORMULA.ERROR_PERCENTAGE;
|
|
||||||
const autocompleteData: BaseAutocompleteData[] = [
|
const aggregateOperators = [
|
||||||
autocompleteDataA,
|
MetricAggregateOperator.SUM,
|
||||||
autocompleteDataB,
|
MetricAggregateOperator.SUM,
|
||||||
];
|
];
|
||||||
|
const legends = [legend, legend];
|
||||||
const additionalItems: TagFilterItem[][] = [
|
|
||||||
additionalItemsA,
|
|
||||||
additionalItemsB,
|
|
||||||
];
|
|
||||||
|
|
||||||
const legends = Array(2).fill(legend);
|
|
||||||
const aggregateOperators = Array(2).fill(MetricAggregateOperator.SUM);
|
|
||||||
const disabled = Array(2).fill(true);
|
|
||||||
const dataSource = DataSource.METRICS;
|
const dataSource = DataSource.METRICS;
|
||||||
|
|
||||||
return getQueryBuilderQuerieswithFormula({
|
return getQueryBuilderQuerieswithFormula({
|
||||||
@ -107,8 +103,8 @@ export const externalCallErrorPercent = ({
|
|||||||
legends,
|
legends,
|
||||||
groupBy,
|
groupBy,
|
||||||
disabled,
|
disabled,
|
||||||
expression,
|
expressions,
|
||||||
legendFormula,
|
legendFormulas,
|
||||||
aggregateOperators,
|
aggregateOperators,
|
||||||
dataSource,
|
dataSource,
|
||||||
});
|
});
|
||||||
@ -130,11 +126,10 @@ export const externalCallDuration = ({
|
|||||||
key: WidgetKeys.SignozExternalCallLatencyCount,
|
key: WidgetKeys.SignozExternalCallLatencyCount,
|
||||||
type: null,
|
type: null,
|
||||||
};
|
};
|
||||||
|
const expressions = [FORMULA.DATABASE_CALLS_AVG_DURATION];
|
||||||
const expression = FORMULA.DATABASE_CALLS_AVG_DURATION;
|
const legendFormulas = ['Average Duration'];
|
||||||
const legendFormula = 'Average Duration';
|
|
||||||
const legend = '';
|
const legend = '';
|
||||||
const disabled = Array(2).fill(true);
|
const disabled = [true, true];
|
||||||
const additionalItemsA: TagFilterItem[] = [
|
const additionalItemsA: TagFilterItem[] = [
|
||||||
{
|
{
|
||||||
id: '',
|
id: '',
|
||||||
@ -150,28 +145,25 @@ export const externalCallDuration = ({
|
|||||||
...tagFilterItems,
|
...tagFilterItems,
|
||||||
];
|
];
|
||||||
|
|
||||||
const autocompleteData: BaseAutocompleteData[] = [
|
const autocompleteData = [autocompleteDataA, autocompleteDataB];
|
||||||
autocompleteDataA,
|
|
||||||
autocompleteDataB,
|
|
||||||
];
|
|
||||||
|
|
||||||
const additionalItems: TagFilterItem[][] = [
|
const additionalItems = [additionalItemsA, additionalItemsA];
|
||||||
additionalItemsA,
|
const legends = [legend, legend];
|
||||||
additionalItemsA,
|
const aggregateOperators = [
|
||||||
|
MetricAggregateOperator.SUM,
|
||||||
|
MetricAggregateOperator.SUM,
|
||||||
];
|
];
|
||||||
|
const dataSource = DataSource.METRICS;
|
||||||
const legends = Array(2).fill(legend);
|
|
||||||
const aggregateOperators = Array(2).fill(MetricAggregateOperator.SUM);
|
|
||||||
|
|
||||||
return getQueryBuilderQuerieswithFormula({
|
return getQueryBuilderQuerieswithFormula({
|
||||||
autocompleteData,
|
autocompleteData,
|
||||||
additionalItems,
|
additionalItems,
|
||||||
legends,
|
legends,
|
||||||
disabled,
|
disabled,
|
||||||
expression,
|
expressions,
|
||||||
legendFormula,
|
legendFormulas,
|
||||||
aggregateOperators,
|
aggregateOperators,
|
||||||
dataSource: DataSource.METRICS,
|
dataSource,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -234,8 +226,8 @@ export const externalCallDurationByAddress = ({
|
|||||||
key: WidgetKeys.SignozExternalCallLatencyCount,
|
key: WidgetKeys.SignozExternalCallLatencyCount,
|
||||||
type: null,
|
type: null,
|
||||||
};
|
};
|
||||||
const expression = FORMULA.DATABASE_CALLS_AVG_DURATION;
|
const expressions = [FORMULA.DATABASE_CALLS_AVG_DURATION];
|
||||||
const legendFormula = legend;
|
const legendFormulas = [legend];
|
||||||
const disabled = [true, true];
|
const disabled = [true, true];
|
||||||
const additionalItemsA: TagFilterItem[] = [
|
const additionalItemsA: TagFilterItem[] = [
|
||||||
{
|
{
|
||||||
@ -252,18 +244,13 @@ export const externalCallDurationByAddress = ({
|
|||||||
...tagFilterItems,
|
...tagFilterItems,
|
||||||
];
|
];
|
||||||
|
|
||||||
const autocompleteData: BaseAutocompleteData[] = [
|
const autocompleteData = [autocompleteDataA, autocompleteDataB];
|
||||||
autocompleteDataA,
|
const additionalItems = [additionalItemsA, additionalItemsA];
|
||||||
autocompleteDataB,
|
const legends = [legend, legend];
|
||||||
|
const aggregateOperators = [
|
||||||
|
MetricAggregateOperator.SUM,
|
||||||
|
MetricAggregateOperator.SUM,
|
||||||
];
|
];
|
||||||
|
|
||||||
const additionalItems: TagFilterItem[][] = [
|
|
||||||
additionalItemsA,
|
|
||||||
additionalItemsA,
|
|
||||||
];
|
|
||||||
|
|
||||||
const legends = Array(2).fill(legend);
|
|
||||||
const aggregateOperators = Array(2).fill(MetricAggregateOperator.SUM_RATE);
|
|
||||||
const dataSource = DataSource.METRICS;
|
const dataSource = DataSource.METRICS;
|
||||||
|
|
||||||
return getQueryBuilderQuerieswithFormula({
|
return getQueryBuilderQuerieswithFormula({
|
||||||
@ -272,8 +259,8 @@ export const externalCallDurationByAddress = ({
|
|||||||
legends,
|
legends,
|
||||||
groupBy,
|
groupBy,
|
||||||
disabled,
|
disabled,
|
||||||
expression,
|
expressions,
|
||||||
legendFormula,
|
legendFormulas,
|
||||||
aggregateOperators,
|
aggregateOperators,
|
||||||
dataSource,
|
dataSource,
|
||||||
});
|
});
|
||||||
|
@ -67,18 +67,16 @@ export const getQueryBuilderQuerieswithFormula = ({
|
|||||||
legends,
|
legends,
|
||||||
groupBy = [],
|
groupBy = [],
|
||||||
disabled,
|
disabled,
|
||||||
expression,
|
expressions,
|
||||||
legendFormula,
|
legendFormulas,
|
||||||
aggregateOperators,
|
aggregateOperators,
|
||||||
dataSource,
|
dataSource,
|
||||||
}: BuilderQuerieswithFormulaProps): QueryBuilderData => ({
|
}: BuilderQuerieswithFormulaProps): QueryBuilderData => ({
|
||||||
queryFormulas: [
|
queryFormulas: expressions.map((expression, index) => ({
|
||||||
{
|
...initialFormulaBuilderFormValues,
|
||||||
...initialFormulaBuilderFormValues,
|
expression,
|
||||||
expression,
|
legend: legendFormulas[index],
|
||||||
legend: legendFormula,
|
})),
|
||||||
},
|
|
||||||
],
|
|
||||||
queryData: autocompleteData.map((_, index) => ({
|
queryData: autocompleteData.map((_, index) => ({
|
||||||
...initialQueryBuilderFormValuesMap.metrics,
|
...initialQueryBuilderFormValuesMap.metrics,
|
||||||
aggregateOperator: aggregateOperators[index],
|
aggregateOperator: aggregateOperators[index],
|
||||||
|
@ -224,8 +224,8 @@ export const errorPercentage = ({
|
|||||||
const additionalItems = [additionalItemsA, additionalItemsB];
|
const additionalItems = [additionalItemsA, additionalItemsB];
|
||||||
const legends = [GraphTitle.ERROR_PERCENTAGE];
|
const legends = [GraphTitle.ERROR_PERCENTAGE];
|
||||||
const disabled = [true, true];
|
const disabled = [true, true];
|
||||||
const expression = FORMULA.ERROR_PERCENTAGE;
|
const expressions = [FORMULA.ERROR_PERCENTAGE];
|
||||||
const legendFormula = GraphTitle.ERROR_PERCENTAGE;
|
const legendFormulas = [GraphTitle.ERROR_PERCENTAGE];
|
||||||
const aggregateOperators = [
|
const aggregateOperators = [
|
||||||
MetricAggregateOperator.SUM_RATE,
|
MetricAggregateOperator.SUM_RATE,
|
||||||
MetricAggregateOperator.SUM_RATE,
|
MetricAggregateOperator.SUM_RATE,
|
||||||
@ -237,8 +237,8 @@ export const errorPercentage = ({
|
|||||||
additionalItems,
|
additionalItems,
|
||||||
legends,
|
legends,
|
||||||
disabled,
|
disabled,
|
||||||
expression,
|
expressions,
|
||||||
legendFormula,
|
legendFormulas,
|
||||||
aggregateOperators,
|
aggregateOperators,
|
||||||
dataSource,
|
dataSource,
|
||||||
});
|
});
|
||||||
|
@ -124,8 +124,8 @@ export const topOperationQueries = ({
|
|||||||
MetricAggregateOperator.SUM_RATE,
|
MetricAggregateOperator.SUM_RATE,
|
||||||
MetricAggregateOperator.SUM_RATE,
|
MetricAggregateOperator.SUM_RATE,
|
||||||
];
|
];
|
||||||
const expression = 'D*100/E';
|
const expressions = ['D*100/E'];
|
||||||
const legendFormula = GraphTitle.ERROR_PERCENTAGE;
|
const legendFormulas = [GraphTitle.ERROR_PERCENTAGE];
|
||||||
const dataSource = DataSource.METRICS;
|
const dataSource = DataSource.METRICS;
|
||||||
|
|
||||||
return getQueryBuilderQuerieswithFormula({
|
return getQueryBuilderQuerieswithFormula({
|
||||||
@ -134,8 +134,8 @@ export const topOperationQueries = ({
|
|||||||
disabled,
|
disabled,
|
||||||
legends,
|
legends,
|
||||||
aggregateOperators,
|
aggregateOperators,
|
||||||
expression,
|
expressions,
|
||||||
legendFormula,
|
legendFormulas,
|
||||||
dataSource,
|
dataSource,
|
||||||
groupBy,
|
groupBy,
|
||||||
});
|
});
|
||||||
|
@ -4,12 +4,13 @@ import { topOperationQueries } from 'container/MetricsApplication/MetricsPageQue
|
|||||||
import { QueryTable } from 'container/QueryTable';
|
import { QueryTable } from 'container/QueryTable';
|
||||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||||
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
import { ReactNode, useMemo, useState } from 'react';
|
import { ReactNode, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -18,18 +19,19 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { IServiceName } from '../types';
|
import { IServiceName } from '../types';
|
||||||
import { title } from './config';
|
|
||||||
import ColumnWithLink from './TableRenderer/ColumnWithLink';
|
import ColumnWithLink from './TableRenderer/ColumnWithLink';
|
||||||
import { getTableColumnRenderer } from './TableRenderer/TableColumnRenderer';
|
import { getTableColumnRenderer } from './TableRenderer/TableColumnRenderer';
|
||||||
|
|
||||||
function TopOperationMetrics(): JSX.Element {
|
function TopOperationMetrics(): JSX.Element {
|
||||||
const { servicename } = useParams<IServiceName>();
|
const { servicename } = useParams<IServiceName>();
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState<string | undefined>('');
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||||
AppState,
|
AppState,
|
||||||
GlobalReducer
|
GlobalReducer
|
||||||
>((state) => state.globalTime);
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
const { queries } = useResourceAttribute();
|
const { queries } = useResourceAttribute();
|
||||||
|
|
||||||
const selectedTraceTags = JSON.stringify(
|
const selectedTraceTags = JSON.stringify(
|
||||||
@ -80,7 +82,7 @@ function TopOperationMetrics(): JSX.Element {
|
|||||||
enabled: !isEmptyWidget,
|
enabled: !isEmptyWidget,
|
||||||
refetchOnMount: false,
|
refetchOnMount: false,
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
setErrorMessage(error.message);
|
notifications.error({ message: error.message });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -104,13 +106,8 @@ function TopOperationMetrics(): JSX.Element {
|
|||||||
[servicename, minTime, maxTime, selectedTraceTags],
|
[servicename, minTime, maxTime, selectedTraceTags],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (errorMessage) {
|
|
||||||
return <div>{errorMessage}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryTable
|
<QueryTable
|
||||||
title={title}
|
|
||||||
query={updatedQuery}
|
query={updatedQuery}
|
||||||
queryTableData={queryTableData}
|
queryTableData={queryTableData}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
@ -36,8 +36,8 @@ export interface BuilderQuerieswithFormulaProps {
|
|||||||
legends: string[];
|
legends: string[];
|
||||||
disabled: boolean[];
|
disabled: boolean[];
|
||||||
groupBy?: BaseAutocompleteData[];
|
groupBy?: BaseAutocompleteData[];
|
||||||
expression: string;
|
expressions: string[];
|
||||||
legendFormula: string;
|
legendFormulas: string[];
|
||||||
additionalItems: TagFilterItem[][];
|
additionalItems: TagFilterItem[][];
|
||||||
aggregateOperators: MetricAggregateOperator[];
|
aggregateOperators: MetricAggregateOperator[];
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
|
@ -34,6 +34,7 @@ export enum KeyOperationTableHeader {
|
|||||||
P99 = 'P99',
|
P99 = 'P99',
|
||||||
NUM_OF_CALLS = 'Number of Calls',
|
NUM_OF_CALLS = 'Number of Calls',
|
||||||
ERROR_RATE = 'Error Rate',
|
ERROR_RATE = 'Error Rate',
|
||||||
|
OPERATION_PR_SECOND = 'Op/s',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DataType {
|
export enum DataType {
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
import { IServiceName } from './Tabs/types';
|
||||||
|
|
||||||
export interface GetWidgetQueryBuilderProps {
|
export interface GetWidgetQueryBuilderProps {
|
||||||
query: Widgets['query'];
|
query: Widgets['query'];
|
||||||
@ -13,3 +16,12 @@ export interface NavigateToTraceProps {
|
|||||||
maxTime: number;
|
maxTime: number;
|
||||||
selectedTraceTags: string;
|
selectedTraceTags: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DatabaseCallsRPSProps extends DatabaseCallProps {
|
||||||
|
legend: '{{db_system}}';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseCallProps {
|
||||||
|
servicename: IServiceName['servicename'];
|
||||||
|
tagFilterItems: TagFilterItem[];
|
||||||
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
export enum ColumnKey {
|
||||||
|
Application = 'serviceName',
|
||||||
|
P99 = 'p99',
|
||||||
|
ErrorRate = 'errorRate',
|
||||||
|
Operations = 'callRate',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ColumnTitle: Record<ColumnKey, string> = {
|
||||||
|
[ColumnKey.Application]: 'Application',
|
||||||
|
[ColumnKey.P99]: 'P99 latency',
|
||||||
|
[ColumnKey.ErrorRate]: 'Error Rate (% of total)',
|
||||||
|
[ColumnKey.Operations]: 'Operations Per Second',
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum ColumnWidth {
|
||||||
|
Application = 200,
|
||||||
|
P99 = 150,
|
||||||
|
ErrorRate = 150,
|
||||||
|
Operations = 150,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SORTING_ORDER = 'descend';
|
||||||
|
|
||||||
|
export const SEARCH_PLACEHOLDER = 'Search by service';
|
@ -0,0 +1,34 @@
|
|||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import type { ColumnType } from 'antd/es/table';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import { routeConfig } from 'container/SideNav/config';
|
||||||
|
import { getQueryString } from 'container/SideNav/helper';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { ServicesList } from 'types/api/metrics/getService';
|
||||||
|
|
||||||
|
import { filterDropdown } from '../Filter/FilterDropdown';
|
||||||
|
import { Name } from '../styles';
|
||||||
|
|
||||||
|
export const getColumnSearchProps = (
|
||||||
|
dataIndex: keyof ServicesList,
|
||||||
|
search: string,
|
||||||
|
): ColumnType<ServicesList> => ({
|
||||||
|
filterDropdown,
|
||||||
|
filterIcon: <SearchOutlined />,
|
||||||
|
onFilter: (value: string | number | boolean, record: ServicesList): boolean =>
|
||||||
|
record[dataIndex]
|
||||||
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(value.toString().toLowerCase()),
|
||||||
|
render: (metrics: string): JSX.Element => {
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const avialableParams = routeConfig[ROUTES.SERVICE_METRICS];
|
||||||
|
const queryString = getQueryString(avialableParams, urlParams);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link to={`${ROUTES.APPLICATION}/${metrics}?${queryString.join('')}`}>
|
||||||
|
<Name>{metrics}</Name>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,54 @@
|
|||||||
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
import { ServicesList } from 'types/api/metrics/getService';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ColumnKey,
|
||||||
|
ColumnTitle,
|
||||||
|
ColumnWidth,
|
||||||
|
SORTING_ORDER,
|
||||||
|
} from './ColumnContants';
|
||||||
|
import { getColumnSearchProps } from './GetColumnSearchProps';
|
||||||
|
|
||||||
|
export const getColumns = (
|
||||||
|
search: string,
|
||||||
|
isMetricData: boolean,
|
||||||
|
): ColumnsType<ServicesList> => [
|
||||||
|
{
|
||||||
|
title: ColumnTitle[ColumnKey.Application],
|
||||||
|
dataIndex: ColumnKey.Application,
|
||||||
|
width: ColumnWidth.Application,
|
||||||
|
key: ColumnKey.Application,
|
||||||
|
...getColumnSearchProps('serviceName', search),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: `${ColumnTitle[ColumnKey.P99]}${
|
||||||
|
isMetricData ? ' (in ns)' : ' (in ms)'
|
||||||
|
}`,
|
||||||
|
dataIndex: ColumnKey.P99,
|
||||||
|
key: ColumnKey.P99,
|
||||||
|
width: ColumnWidth.P99,
|
||||||
|
defaultSortOrder: SORTING_ORDER,
|
||||||
|
sorter: (a: ServicesList, b: ServicesList): number => a.p99 - b.p99,
|
||||||
|
render: (value: number): string => {
|
||||||
|
if (Number.isNaN(value)) return '0.00';
|
||||||
|
return isMetricData ? value.toFixed(2) : (value / 1000000).toFixed(2);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: ColumnTitle[ColumnKey.ErrorRate],
|
||||||
|
dataIndex: ColumnKey.ErrorRate,
|
||||||
|
key: ColumnKey.ErrorRate,
|
||||||
|
width: 150,
|
||||||
|
sorter: (a: ServicesList, b: ServicesList): number =>
|
||||||
|
a.errorRate - b.errorRate,
|
||||||
|
render: (value: number): string => value.toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: ColumnTitle[ColumnKey.Operations],
|
||||||
|
dataIndex: ColumnKey.Operations,
|
||||||
|
key: ColumnKey.Operations,
|
||||||
|
width: ColumnWidth.Operations,
|
||||||
|
sorter: (a: ServicesList, b: ServicesList): number => a.callRate - b.callRate,
|
||||||
|
render: (value: number): string => value.toFixed(2),
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,41 @@
|
|||||||
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Card, Input, Space } from 'antd';
|
||||||
|
import type { FilterDropdownProps } from 'antd/es/table/interface';
|
||||||
|
|
||||||
|
import { SEARCH_PLACEHOLDER } from '../Columns/ColumnContants';
|
||||||
|
|
||||||
|
export const filterDropdown = ({
|
||||||
|
setSelectedKeys,
|
||||||
|
selectedKeys,
|
||||||
|
confirm,
|
||||||
|
}: FilterDropdownProps): JSX.Element => {
|
||||||
|
const handleSearch = (): void => {
|
||||||
|
confirm();
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedKeysHandler = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
setSelectedKeys(e.target.value ? [e.target.value] : []);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card size="small">
|
||||||
|
<Space align="start" direction="vertical">
|
||||||
|
<Input
|
||||||
|
placeholder={SEARCH_PLACEHOLDER}
|
||||||
|
value={selectedKeys[0]}
|
||||||
|
onChange={selectedKeysHandler}
|
||||||
|
allowClear
|
||||||
|
onPressEnter={handleSearch}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleSearch}
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,67 @@
|
|||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
|
import { useGetQueriesRange } from 'hooks/queryBuilder/useGetQueriesRange';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { ServicesList } from 'types/api/metrics/getService';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
import { getColumns } from '../Columns/ServiceColumn';
|
||||||
|
import { ServiceMetricsTableProps } from '../types';
|
||||||
|
import { getServiceListFromQuery } from '../utils';
|
||||||
|
|
||||||
|
function ServiceMetricTable({
|
||||||
|
topLevelOperations,
|
||||||
|
queryRangeRequestData,
|
||||||
|
}: ServiceMetricsTableProps): JSX.Element {
|
||||||
|
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
|
const queries = useGetQueriesRange(queryRangeRequestData, {
|
||||||
|
queryKey: [
|
||||||
|
`GetMetricsQueryRange-${queryRangeRequestData[0].selectedTime}-${globalSelectedInterval}`,
|
||||||
|
maxTime,
|
||||||
|
minTime,
|
||||||
|
globalSelectedInterval,
|
||||||
|
],
|
||||||
|
keepPreviousData: true,
|
||||||
|
enabled: true,
|
||||||
|
refetchOnMount: false,
|
||||||
|
onError: (error) => {
|
||||||
|
notifications.error({
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isLoading = queries.some((query) => query.isLoading);
|
||||||
|
const services: ServicesList[] = useMemo(
|
||||||
|
() =>
|
||||||
|
getServiceListFromQuery({
|
||||||
|
queries,
|
||||||
|
topLevelOperations,
|
||||||
|
isLoading,
|
||||||
|
}),
|
||||||
|
[isLoading, queries, topLevelOperations],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { search } = useLocation();
|
||||||
|
const tableColumns = useMemo(() => getColumns(search, true), [search]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResizeTable
|
||||||
|
columns={tableColumns}
|
||||||
|
loading={isLoading}
|
||||||
|
dataSource={services}
|
||||||
|
rowKey="serviceName"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceMetricTable;
|
@ -0,0 +1,36 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
import { ServiceMetricsProps } from '../types';
|
||||||
|
import { getQueryRangeRequestData } from '../utils';
|
||||||
|
import ServiceMetricTable from './ServiceMetricTable';
|
||||||
|
|
||||||
|
function ServiceMetricsApplication({
|
||||||
|
topLevelOperations,
|
||||||
|
}: ServiceMetricsProps): JSX.Element {
|
||||||
|
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
|
const queryRangeRequestData = useMemo(
|
||||||
|
() =>
|
||||||
|
getQueryRangeRequestData({
|
||||||
|
topLevelOperations,
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
globalSelectedInterval,
|
||||||
|
}),
|
||||||
|
[globalSelectedInterval, maxTime, minTime, topLevelOperations],
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<ServiceMetricTable
|
||||||
|
topLevelOperations={topLevelOperations}
|
||||||
|
queryRangeRequestData={queryRangeRequestData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceMetricsApplication;
|
@ -0,0 +1,208 @@
|
|||||||
|
import { ServiceDataProps } from 'api/metrics/getTopLevelOperations';
|
||||||
|
import { OPERATORS } from 'constants/queryBuilder';
|
||||||
|
import {
|
||||||
|
DataType,
|
||||||
|
KeyOperationTableHeader,
|
||||||
|
MetricsType,
|
||||||
|
WidgetKeys,
|
||||||
|
} from 'container/MetricsApplication/constant';
|
||||||
|
import { getQueryBuilderQuerieswithFormula } from 'container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory';
|
||||||
|
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||||
|
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
import {
|
||||||
|
DataSource,
|
||||||
|
MetricAggregateOperator,
|
||||||
|
QueryBuilderData,
|
||||||
|
} from 'types/common/queryBuilder';
|
||||||
|
|
||||||
|
export const serviceMetricsQuery = (
|
||||||
|
topLevelOperation: [keyof ServiceDataProps, string[]],
|
||||||
|
): QueryBuilderData => {
|
||||||
|
const p99AutoCompleteData: BaseAutocompleteData = {
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
key: WidgetKeys.Signoz_latency_bucket,
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorRateAutoCompleteData: BaseAutocompleteData = {
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
key: WidgetKeys.SignozCallsTotal,
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const operationPrSecondAutoCompleteData: BaseAutocompleteData = {
|
||||||
|
dataType: DataType.FLOAT64,
|
||||||
|
isColumn: true,
|
||||||
|
key: WidgetKeys.SignozCallsTotal,
|
||||||
|
type: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const autocompleteData = [
|
||||||
|
p99AutoCompleteData,
|
||||||
|
errorRateAutoCompleteData,
|
||||||
|
errorRateAutoCompleteData,
|
||||||
|
operationPrSecondAutoCompleteData,
|
||||||
|
];
|
||||||
|
|
||||||
|
const p99AdditionalItems: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.Service_name,
|
||||||
|
type: MetricsType.Resource,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [topLevelOperation[0].toString()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.Operation,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperation[1]],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const errorRateAdditionalItemsA: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.Service_name,
|
||||||
|
type: MetricsType.Resource,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [topLevelOperation[0].toString()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
dataType: DataType.INT64,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.StatusCode,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: ['STATUS_CODE_ERROR'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.Operation,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperation[1]],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const errorRateAdditionalItemsB: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.Service_name,
|
||||||
|
type: MetricsType.Resource,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [topLevelOperation[0].toString()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.Operation,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperation[1]],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const operationPrSecondAdditionalItems: TagFilterItem[] = [
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.Service_name,
|
||||||
|
type: MetricsType.Resource,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [topLevelOperation[0].toString()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
key: {
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.Operation,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
op: OPERATORS.IN,
|
||||||
|
value: [...topLevelOperation[1]],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const additionalItems = [
|
||||||
|
p99AdditionalItems,
|
||||||
|
errorRateAdditionalItemsA,
|
||||||
|
errorRateAdditionalItemsB,
|
||||||
|
operationPrSecondAdditionalItems,
|
||||||
|
];
|
||||||
|
|
||||||
|
const aggregateOperators = [
|
||||||
|
MetricAggregateOperator.HIST_QUANTILE_99,
|
||||||
|
MetricAggregateOperator.SUM_RATE,
|
||||||
|
MetricAggregateOperator.SUM_RATE,
|
||||||
|
MetricAggregateOperator.SUM_RATE,
|
||||||
|
];
|
||||||
|
|
||||||
|
const disabled = [false, true, true, false];
|
||||||
|
const legends = [
|
||||||
|
KeyOperationTableHeader.P99,
|
||||||
|
KeyOperationTableHeader.ERROR_RATE,
|
||||||
|
KeyOperationTableHeader.ERROR_RATE,
|
||||||
|
KeyOperationTableHeader.OPERATION_PR_SECOND,
|
||||||
|
];
|
||||||
|
|
||||||
|
const expressions = ['B*100/C'];
|
||||||
|
|
||||||
|
const legendFormulas = ['Error Rate'];
|
||||||
|
|
||||||
|
const groupBy: BaseAutocompleteData[] = [
|
||||||
|
{
|
||||||
|
dataType: DataType.STRING,
|
||||||
|
isColumn: false,
|
||||||
|
key: WidgetKeys.Service_name,
|
||||||
|
type: MetricsType.Tag,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const dataSource = DataSource.METRICS;
|
||||||
|
|
||||||
|
return getQueryBuilderQuerieswithFormula({
|
||||||
|
autocompleteData,
|
||||||
|
additionalItems,
|
||||||
|
disabled,
|
||||||
|
legends,
|
||||||
|
aggregateOperators,
|
||||||
|
expressions,
|
||||||
|
legendFormulas,
|
||||||
|
groupBy,
|
||||||
|
dataSource,
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,59 @@
|
|||||||
|
import localStorageGet from 'api/browser/localstorage/get';
|
||||||
|
import localStorageSet from 'api/browser/localstorage/set';
|
||||||
|
import Spinner from 'components/Spinner';
|
||||||
|
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
||||||
|
import useGetTopLevelOperations from 'hooks/useGetTopLevelOperations';
|
||||||
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { QueryKey } from 'react-query';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
import { Tags } from 'types/reducer/trace';
|
||||||
|
|
||||||
|
import SkipOnBoardingModal from '../SkipOnBoardModal';
|
||||||
|
import ServiceMetricsApplication from './ServiceMetricsApplication';
|
||||||
|
|
||||||
|
function ServicesUsingMetrics(): JSX.Element {
|
||||||
|
const { maxTime, minTime, selectedTime: globalSelectedInterval } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
const { queries } = useResourceAttribute();
|
||||||
|
const selectedTags = useMemo(
|
||||||
|
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
|
||||||
|
[queries],
|
||||||
|
);
|
||||||
|
|
||||||
|
const queryKey: QueryKey = [
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
selectedTags,
|
||||||
|
globalSelectedInterval,
|
||||||
|
];
|
||||||
|
const { data, isLoading, isError } = useGetTopLevelOperations(queryKey);
|
||||||
|
|
||||||
|
const [skipOnboarding, setSkipOnboarding] = useState(
|
||||||
|
localStorageGet(SKIP_ONBOARDING) === 'true',
|
||||||
|
);
|
||||||
|
|
||||||
|
const onContinueClick = (): void => {
|
||||||
|
localStorageSet(SKIP_ONBOARDING, 'true');
|
||||||
|
setSkipOnboarding(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const topLevelOperations = Object.entries(data || {});
|
||||||
|
|
||||||
|
if (isLoading === false && !skipOnboarding && isError === true) {
|
||||||
|
return <SkipOnBoardingModal onContinueClick={onContinueClick} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spinner tip="Loading..." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ServiceMetricsApplication topLevelOperations={topLevelOperations} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServicesUsingMetrics;
|
@ -0,0 +1,50 @@
|
|||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { services } from './__mocks__/getServices';
|
||||||
|
import ServiceTraceTable from './ServiceTracesTable';
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
...jest.requireActual('react-router-dom'),
|
||||||
|
useLocation: (): { pathname: string } => ({
|
||||||
|
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Metrics Component', () => {
|
||||||
|
it('renders without errors', async () => {
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<ServiceTraceTable services={services} loading={false} />
|
||||||
|
</BrowserRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/application/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/p99 latency \(in ms\)/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/error rate \(% of total\)/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/operations per second/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders if the data is loaded in the table', async () => {
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<ServiceTraceTable services={services} loading={false} />
|
||||||
|
</BrowserRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('frontend')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders no data when required conditions are met', async () => {
|
||||||
|
render(
|
||||||
|
<BrowserRouter>
|
||||||
|
<ServiceTraceTable services={[]} loading={false} />
|
||||||
|
</BrowserRouter>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('No data')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,26 @@
|
|||||||
|
import { ResizeTable } from 'components/ResizeTable';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { getColumns } from '../Columns/ServiceColumn';
|
||||||
|
import ServiceTableProps from '../types';
|
||||||
|
|
||||||
|
function ServiceTraceTable({
|
||||||
|
services,
|
||||||
|
loading,
|
||||||
|
}: ServiceTableProps): JSX.Element {
|
||||||
|
const { search } = useLocation();
|
||||||
|
|
||||||
|
const tableColumns = useMemo(() => getColumns(search, false), [search]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResizeTable
|
||||||
|
columns={tableColumns}
|
||||||
|
loading={loading}
|
||||||
|
dataSource={services}
|
||||||
|
rowKey="serviceName"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceTraceTable;
|
@ -0,0 +1,22 @@
|
|||||||
|
import { ServicesList } from 'types/api/metrics/getService';
|
||||||
|
|
||||||
|
export const services: ServicesList[] = [
|
||||||
|
{
|
||||||
|
serviceName: 'frontend',
|
||||||
|
p99: 1261498140,
|
||||||
|
avgDuration: 768497850.9803921,
|
||||||
|
numCalls: 255,
|
||||||
|
callRate: 0.9444444444444444,
|
||||||
|
numErrors: 0,
|
||||||
|
errorRate: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
serviceName: 'customer',
|
||||||
|
p99: 890150740.0000001,
|
||||||
|
avgDuration: 369612035.2941176,
|
||||||
|
numCalls: 255,
|
||||||
|
callRate: 0.9444444444444444,
|
||||||
|
numErrors: 0,
|
||||||
|
errorRate: 0,
|
||||||
|
},
|
||||||
|
];
|
@ -0,0 +1,60 @@
|
|||||||
|
import localStorageGet from 'api/browser/localstorage/get';
|
||||||
|
import localStorageSet from 'api/browser/localstorage/set';
|
||||||
|
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
||||||
|
import useErrorNotification from 'hooks/useErrorNotification';
|
||||||
|
import { useQueryService } from 'hooks/useQueryService';
|
||||||
|
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||||
|
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
import { Tags } from 'types/reducer/trace';
|
||||||
|
|
||||||
|
import SkipOnBoardingModal from '../SkipOnBoardModal';
|
||||||
|
import ServiceTraceTable from './ServiceTracesTable';
|
||||||
|
|
||||||
|
function ServiceTraces(): JSX.Element {
|
||||||
|
const { maxTime, minTime, selectedTime } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
const { queries } = useResourceAttribute();
|
||||||
|
const selectedTags = useMemo(
|
||||||
|
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
|
||||||
|
[queries],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data, error, isLoading, isError } = useQueryService({
|
||||||
|
minTime,
|
||||||
|
maxTime,
|
||||||
|
selectedTime,
|
||||||
|
selectedTags,
|
||||||
|
});
|
||||||
|
|
||||||
|
useErrorNotification(error);
|
||||||
|
|
||||||
|
const services = data || [];
|
||||||
|
|
||||||
|
const [skipOnboarding, setSkipOnboarding] = useState(
|
||||||
|
localStorageGet(SKIP_ONBOARDING) === 'true',
|
||||||
|
);
|
||||||
|
|
||||||
|
const onContinueClick = (): void => {
|
||||||
|
localStorageSet(SKIP_ONBOARDING, 'true');
|
||||||
|
setSkipOnboarding(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
services.length === 0 &&
|
||||||
|
isLoading === false &&
|
||||||
|
!skipOnboarding &&
|
||||||
|
isError === true
|
||||||
|
) {
|
||||||
|
return <SkipOnBoardingModal onContinueClick={onContinueClick} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ServiceTraceTable services={services} loading={isLoading} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceTraces;
|
@ -0,0 +1,48 @@
|
|||||||
|
import { Button, Typography } from 'antd';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
|
||||||
|
function SkipOnBoardingModal({ onContinueClick }: Props): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="Setup instrumentation"
|
||||||
|
isModalVisible
|
||||||
|
closable={false}
|
||||||
|
footer={[
|
||||||
|
<Button key="submit" type="primary" onClick={onContinueClick}>
|
||||||
|
Continue without instrumentation
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<iframe
|
||||||
|
width="100%"
|
||||||
|
height="265"
|
||||||
|
src="https://www.youtube.com/embed/J1Bof55DOb4"
|
||||||
|
frameBorder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowFullScreen
|
||||||
|
title="youtube_video"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Typography>No instrumentation data.</Typography>
|
||||||
|
<Typography>
|
||||||
|
Please instrument your application as mentioned
|
||||||
|
<a
|
||||||
|
href="https://signoz.io/docs/instrumentation/overview"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onContinueClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SkipOnBoardingModal;
|
19
frontend/src/container/ServiceApplication/index.tsx
Normal file
19
frontend/src/container/ServiceApplication/index.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { FeatureKeys } from 'constants/features';
|
||||||
|
import useFeatureFlag from 'hooks/useFeatureFlag';
|
||||||
|
|
||||||
|
import ServiceMetrics from './ServiceMetrics';
|
||||||
|
import ServiceTraces from './ServiceTraces';
|
||||||
|
import { Container } from './styles';
|
||||||
|
|
||||||
|
function Services(): JSX.Element {
|
||||||
|
const isSpanMetricEnabled = useFeatureFlag(FeatureKeys.USE_SPAN_METRICS)
|
||||||
|
?.active;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
{isSpanMetricEnabled ? <ServiceMetrics /> : <ServiceTraces />}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Services;
|
15
frontend/src/container/ServiceApplication/styles.ts
Normal file
15
frontend/src/container/ServiceApplication/styles.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Typography } from 'antd';
|
||||||
|
import { themeColors } from 'constants/theme';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const Container = styled.div`
|
||||||
|
margin-top: 2rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Name = styled(Typography)`
|
||||||
|
&&& {
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${themeColors.lightBlue};
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
34
frontend/src/container/ServiceApplication/types.ts
Normal file
34
frontend/src/container/ServiceApplication/types.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { ServiceDataProps } from 'api/metrics/getTopLevelOperations';
|
||||||
|
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||||
|
import { UseQueryResult } from 'react-query';
|
||||||
|
import { GetQueryResultsProps } from 'store/actions/dashboard/getQueryResults';
|
||||||
|
import { SuccessResponse } from 'types/api';
|
||||||
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
import { ServicesList } from 'types/api/metrics/getService';
|
||||||
|
|
||||||
|
export default interface ServiceTableProps {
|
||||||
|
services: ServicesList[];
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceMetricsProps {
|
||||||
|
topLevelOperations: [keyof ServiceDataProps, string[]][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceMetricsTableProps {
|
||||||
|
topLevelOperations: [keyof ServiceDataProps, string[]][];
|
||||||
|
queryRangeRequestData: GetQueryResultsProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetQueryRangeRequestDataProps {
|
||||||
|
topLevelOperations: [keyof ServiceDataProps, string[]][];
|
||||||
|
maxTime: number;
|
||||||
|
minTime: number;
|
||||||
|
globalSelectedInterval: Time;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetServiceListFromQueryProps {
|
||||||
|
queries: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error>[];
|
||||||
|
topLevelOperations: [keyof ServiceDataProps, string[]][];
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
99
frontend/src/container/ServiceApplication/utils.ts
Normal file
99
frontend/src/container/ServiceApplication/utils.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
|
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||||
|
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
|
||||||
|
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||||
|
import { GetQueryResultsProps } from 'store/actions/dashboard/getQueryResults';
|
||||||
|
import { ServicesList } from 'types/api/metrics/getService';
|
||||||
|
import { QueryDataV3 } from 'types/api/widgets/getQuery';
|
||||||
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
import { serviceMetricsQuery } from './ServiceMetrics/ServiceMetricsQuery';
|
||||||
|
import {
|
||||||
|
GetQueryRangeRequestDataProps,
|
||||||
|
GetServiceListFromQueryProps,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
export function getSeriesValue(
|
||||||
|
queryArray: QueryDataV3[],
|
||||||
|
queryName: string,
|
||||||
|
): string {
|
||||||
|
const queryObject = queryArray.find((item) => item.queryName === queryName);
|
||||||
|
const series = queryObject ? queryObject.series : 0;
|
||||||
|
return series ? series[0].values[0].value : '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getQueryRangeRequestData = ({
|
||||||
|
topLevelOperations,
|
||||||
|
maxTime,
|
||||||
|
minTime,
|
||||||
|
globalSelectedInterval,
|
||||||
|
}: GetQueryRangeRequestDataProps): GetQueryResultsProps[] => {
|
||||||
|
const requestData: GetQueryResultsProps[] = [];
|
||||||
|
topLevelOperations.forEach((operation) => {
|
||||||
|
const serviceMetricsWidget = getWidgetQueryBuilder({
|
||||||
|
query: {
|
||||||
|
queryType: EQueryType.QUERY_BUILDER,
|
||||||
|
promql: [],
|
||||||
|
builder: serviceMetricsQuery(operation),
|
||||||
|
clickhouse_sql: [],
|
||||||
|
id: uuid(),
|
||||||
|
},
|
||||||
|
panelTypes: PANEL_TYPES.TABLE,
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedQuery = updateStepInterval(
|
||||||
|
serviceMetricsWidget.query,
|
||||||
|
maxTime,
|
||||||
|
minTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
requestData.push({
|
||||||
|
selectedTime: serviceMetricsWidget?.timePreferance,
|
||||||
|
graphType: serviceMetricsWidget?.panelTypes,
|
||||||
|
query: updatedQuery,
|
||||||
|
globalSelectedInterval,
|
||||||
|
variables: getDashboardVariables(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return requestData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getServiceListFromQuery = ({
|
||||||
|
queries,
|
||||||
|
topLevelOperations,
|
||||||
|
isLoading,
|
||||||
|
}: GetServiceListFromQueryProps): ServicesList[] => {
|
||||||
|
const services: ServicesList[] = [];
|
||||||
|
if (!isLoading) {
|
||||||
|
queries.forEach((query, index) => {
|
||||||
|
// handling error case if query fails
|
||||||
|
if (query.isError) {
|
||||||
|
const serviceData: ServicesList = {
|
||||||
|
serviceName: topLevelOperations[index][0].toString(),
|
||||||
|
p99: 0,
|
||||||
|
callRate: 0,
|
||||||
|
errorRate: 0,
|
||||||
|
avgDuration: 0,
|
||||||
|
numCalls: 0,
|
||||||
|
numErrors: 0,
|
||||||
|
};
|
||||||
|
services.push(serviceData);
|
||||||
|
}
|
||||||
|
if (query.data) {
|
||||||
|
const queryArray = query.data.payload.data.newResult.data.result;
|
||||||
|
const serviceData: ServicesList = {
|
||||||
|
serviceName: topLevelOperations[index][0].toString(),
|
||||||
|
p99: parseFloat(getSeriesValue(queryArray, 'A')),
|
||||||
|
callRate: parseFloat(getSeriesValue(queryArray, 'D')),
|
||||||
|
errorRate: parseFloat(getSeriesValue(queryArray, 'F1')),
|
||||||
|
avgDuration: 0,
|
||||||
|
numCalls: 0,
|
||||||
|
numErrors: 0,
|
||||||
|
};
|
||||||
|
services.push(serviceData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return services;
|
||||||
|
};
|
@ -5,9 +5,7 @@ export enum ColumnKey {
|
|||||||
Operations = 'callRate',
|
Operations = 'callRate',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ColumnTitle: {
|
export const ColumnTitle: Record<ColumnKey, string> = {
|
||||||
[key in ColumnKey]: string;
|
|
||||||
} = {
|
|
||||||
[ColumnKey.Application]: 'Application',
|
[ColumnKey.Application]: 'Application',
|
||||||
[ColumnKey.P99]: 'P99 latency (in ms)',
|
[ColumnKey.P99]: 'P99 latency (in ms)',
|
||||||
[ColumnKey.ErrorRate]: 'Error Rate (% of total)',
|
[ColumnKey.ErrorRate]: 'Error Rate (% of total)',
|
||||||
|
35
frontend/src/hooks/queryBuilder/useGetQueriesRange.ts
Normal file
35
frontend/src/hooks/queryBuilder/useGetQueriesRange.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
QueryKey,
|
||||||
|
useQueries,
|
||||||
|
UseQueryOptions,
|
||||||
|
UseQueryResult,
|
||||||
|
} from 'react-query';
|
||||||
|
import {
|
||||||
|
GetMetricQueryRange,
|
||||||
|
GetQueryResultsProps,
|
||||||
|
} from 'store/actions/dashboard/getQueryResults';
|
||||||
|
import { SuccessResponse } from 'types/api';
|
||||||
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
|
|
||||||
|
export const useGetQueriesRange = (
|
||||||
|
requestData: GetQueryResultsProps[],
|
||||||
|
options: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
|
||||||
|
): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error>[] => {
|
||||||
|
const queryKey = useMemo(() => {
|
||||||
|
if (options?.queryKey) {
|
||||||
|
return [...options.queryKey];
|
||||||
|
}
|
||||||
|
return [REACT_QUERY_KEY.GET_QUERY_RANGE, requestData];
|
||||||
|
}, [options?.queryKey, requestData]);
|
||||||
|
|
||||||
|
const queryData = requestData.map((request, index) => ({
|
||||||
|
queryFn: async (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
||||||
|
GetMetricQueryRange(request),
|
||||||
|
...options,
|
||||||
|
queryKey: [...queryKey, index] as QueryKey,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return useQueries(queryData);
|
||||||
|
};
|
16
frontend/src/hooks/useGetTopLevelOperations.ts
Normal file
16
frontend/src/hooks/useGetTopLevelOperations.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import getTopLevelOperations, {
|
||||||
|
ServiceDataProps,
|
||||||
|
} from 'api/metrics/getTopLevelOperations';
|
||||||
|
import { QueryKey, useQuery, UseQueryResult } from 'react-query';
|
||||||
|
|
||||||
|
type UseGetTopLevelOperations = (
|
||||||
|
queryKey: QueryKey,
|
||||||
|
) => UseQueryResult<ServiceDataProps>;
|
||||||
|
|
||||||
|
const useGetTopLevelOperations: UseGetTopLevelOperations = (queryKey) =>
|
||||||
|
useQuery<ServiceDataProps>({
|
||||||
|
queryKey,
|
||||||
|
queryFn: getTopLevelOperations,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useGetTopLevelOperations;
|
@ -1,68 +1,18 @@
|
|||||||
import { Space } from 'antd';
|
import { Space } from 'antd';
|
||||||
import localStorageGet from 'api/browser/localstorage/get';
|
|
||||||
import localStorageSet from 'api/browser/localstorage/set';
|
|
||||||
import ReleaseNote from 'components/ReleaseNote';
|
import ReleaseNote from 'components/ReleaseNote';
|
||||||
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
|
||||||
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
|
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
|
||||||
import ServicesTable from 'container/ServiceTable';
|
import ServicesApplication from 'container/ServiceApplication';
|
||||||
import SkipOnBoardingModal from 'container/ServiceTable/SkipOnBoardModal';
|
|
||||||
import useErrorNotification from 'hooks/useErrorNotification';
|
|
||||||
import { useQueryService } from 'hooks/useQueryService';
|
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
import { Tags } from 'types/reducer/trace';
|
|
||||||
|
|
||||||
function Metrics(): JSX.Element {
|
function Metrics(): JSX.Element {
|
||||||
const { minTime, maxTime, selectedTime } = useSelector<
|
|
||||||
AppState,
|
|
||||||
GlobalReducer
|
|
||||||
>((state) => state.globalTime);
|
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { queries } = useResourceAttribute();
|
|
||||||
const [skipOnboarding, setSkipOnboarding] = useState(
|
|
||||||
localStorageGet(SKIP_ONBOARDING) === 'true',
|
|
||||||
);
|
|
||||||
|
|
||||||
const onContinueClick = (): void => {
|
|
||||||
localStorageSet(SKIP_ONBOARDING, 'true');
|
|
||||||
setSkipOnboarding(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedTags = useMemo(
|
|
||||||
() => (convertRawQueriesToTraceSelectedTags(queries, '') as Tags[]) || [],
|
|
||||||
[queries],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, error, isLoading, isError } = useQueryService({
|
|
||||||
minTime,
|
|
||||||
maxTime,
|
|
||||||
selectedTime,
|
|
||||||
selectedTags,
|
|
||||||
});
|
|
||||||
|
|
||||||
useErrorNotification(error);
|
|
||||||
|
|
||||||
if (
|
|
||||||
data?.length === 0 &&
|
|
||||||
isLoading === false &&
|
|
||||||
!skipOnboarding &&
|
|
||||||
isError === true
|
|
||||||
) {
|
|
||||||
return <SkipOnBoardingModal onContinueClick={onContinueClick} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" style={{ width: '100%' }}>
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
<ReleaseNote path={location.pathname} />
|
<ReleaseNote path={location.pathname} />
|
||||||
|
|
||||||
<ResourceAttributesFilter />
|
<ResourceAttributesFilter />
|
||||||
<ServicesTable services={data || []} isLoading={isLoading} />
|
<ServicesApplication />
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { AxiosError } from 'axios';
|
||||||
import { Tags } from 'types/reducer/trace';
|
import { Tags } from 'types/reducer/trace';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -17,3 +18,9 @@ export interface ServicesList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PayloadProps = ServicesList[];
|
export type PayloadProps = ServicesList[];
|
||||||
|
|
||||||
|
export interface QueryServiceProps {
|
||||||
|
data: PayloadProps | undefined;
|
||||||
|
error: AxiosError | null;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user