Key Operation to Metrics using USE_SPAN_METRIC feature flag (#3188)

* 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: added clickable link for navigating to traces

* chore: separated types

* chore: removed unnecessary comments

* refactor: one on one mapping for DBCallQueries

* refactor: seperated types and one on one mapping for externalQueries

* refactor: separate type from metricsPagesQueriesFactory

* refactor: separated types and one on one mapping for overviewQueries

* refactor: remove the type inforcement from TopOperationQueries.ts

* refactor: one on one mapping in TopOperationQueries.ts

* refactor: one on one mapping and remove the unwanted code

* refactor: shifted logic of navigating to traces to utils

* refactor: separated renderColumnCell from the TopOperationMetric component

* refactor: generic tableRenderer

* refactor: made getTableColumnRenderer more generic

* chore: title is updated

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Rajat Dabade 2023-07-28 21:54:36 +05:30 committed by GitHub
parent 5c83d133a2
commit bc4a4edc7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 676 additions and 202 deletions

View File

@ -1,17 +1,19 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { Widgets } from 'types/api/dashboard/getAll';
import { v4 } from 'uuid';
export const getWidgetQueryBuilder = (
query: Widgets['query'],
import { GetWidgetQueryBuilderProps } from './types';
export const getWidgetQueryBuilder = ({
query,
title = '',
): Widgets => ({
panelTypes,
}: GetWidgetQueryBuilderProps): Widgets => ({
description: '',
id: v4(),
isStacked: false,
nullZeroValues: '',
opacity: '0',
panelTypes: PANEL_TYPES.TIME_SERIES,
panelTypes,
query,
timePreferance: 'GLOBAL_TIME',
title,

View File

@ -1,7 +1,11 @@
import { OPERATORS } from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, QueryBuilderData } from 'types/common/queryBuilder';
import {
DataSource,
MetricAggregateOperator,
QueryBuilderData,
} from 'types/common/queryBuilder';
import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant';
import { IServiceName } from '../Tabs/types';
@ -44,13 +48,14 @@ export const databaseCallsRPS = ({
];
const legends = [legend];
const dataSource = DataSource.METRICS;
return getQueryBuilderQueries({
autocompleteData,
groupBy,
legends,
filterItems,
dataSource: DataSource.METRICS,
dataSource,
});
};
@ -85,17 +90,36 @@ export const databaseCallsAvgDuration = ({
},
...tagFilterItems,
];
const additionalItemsB = additionalItemsA;
return getQueryBuilderQuerieswithFormula({
const autocompleteData: BaseAutocompleteData[] = [
autocompleteDataA,
autocompleteDataB,
];
const additionalItems: TagFilterItem[][] = [
additionalItemsA,
additionalItemsB,
legend: '',
disabled: true,
expression: FORMULA.DATABASE_CALLS_AVG_DURATION,
legendFormula: 'Average Duration',
additionalItemsA,
];
const legends = ['', ''];
const disabled = [true, true];
const legendFormula = 'Average Duration';
const expression = FORMULA.DATABASE_CALLS_AVG_DURATION;
const aggregateOperators = [
MetricAggregateOperator.SUM,
MetricAggregateOperator.SUM,
];
const dataSource = DataSource.METRICS;
return getQueryBuilderQuerieswithFormula({
autocompleteData,
additionalItems,
legends,
disabled,
expression,
legendFormula,
aggregateOperators,
dataSource,
});
};

View File

@ -1,10 +1,17 @@
import { OPERATORS } from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, QueryBuilderData } from 'types/common/queryBuilder';
import {
DataSource,
MetricAggregateOperator,
QueryBuilderData,
} from 'types/common/queryBuilder';
import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant';
import { IServiceName } from '../Tabs/types';
import {
ExternalCallDurationByAddressProps,
ExternalCallProps,
} from '../Tabs/types';
import {
getQueryBuilderQueries,
getQueryBuilderQuerieswithFormula,
@ -36,6 +43,7 @@ export const externalCallErrorPercent = ({
isColumn: true,
type: null,
};
const additionalItemsA: TagFilterItem[] = [
{
id: '',
@ -71,23 +79,38 @@ export const externalCallErrorPercent = ({
type: MetricsType.Resource,
},
op: OPERATORS.IN,
value: [`${servicename}`],
value: [servicename],
},
...tagFilterItems,
];
const legendFormula = legend;
const expression = FORMULA.ERROR_PERCENTAGE;
const disabled = true;
return getQueryBuilderQuerieswithFormula({
const autocompleteData: BaseAutocompleteData[] = [
autocompleteDataA,
autocompleteDataB,
];
const additionalItems: TagFilterItem[][] = [
additionalItemsA,
additionalItemsB,
legend,
];
const legends = Array(2).fill(legend);
const aggregateOperators = Array(2).fill(MetricAggregateOperator.SUM);
const disabled = Array(2).fill(true);
const dataSource = DataSource.METRICS;
return getQueryBuilderQuerieswithFormula({
autocompleteData,
additionalItems,
legends,
groupBy,
disabled,
expression,
legendFormula,
aggregateOperators,
dataSource,
});
};
@ -107,10 +130,11 @@ export const externalCallDuration = ({
key: WidgetKeys.SignozExternalCallLatencyCount,
type: null,
};
const expression = FORMULA.DATABASE_CALLS_AVG_DURATION;
const legendFormula = 'Average Duration';
const legend = '';
const disabled = true;
const disabled = Array(2).fill(true);
const additionalItemsA: TagFilterItem[] = [
{
id: '',
@ -125,17 +149,29 @@ export const externalCallDuration = ({
},
...tagFilterItems,
];
const additionalItemsB = additionalItemsA;
return getQueryBuilderQuerieswithFormula({
const autocompleteData: BaseAutocompleteData[] = [
autocompleteDataA,
autocompleteDataB,
];
const additionalItems: TagFilterItem[][] = [
additionalItemsA,
additionalItemsB,
legend,
additionalItemsA,
];
const legends = Array(2).fill(legend);
const aggregateOperators = Array(2).fill(MetricAggregateOperator.SUM);
return getQueryBuilderQuerieswithFormula({
autocompleteData,
additionalItems,
legends,
disabled,
expression,
legendFormula,
aggregateOperators,
dataSource: DataSource.METRICS,
});
};
@ -169,13 +205,15 @@ export const externalCallRpsByAddress = ({
],
];
const legends: string[] = [legend];
const legends = [legend];
const dataSource = DataSource.METRICS;
return getQueryBuilderQueries({
autocompleteData,
groupBy,
legends,
filterItems,
dataSource: DataSource.METRICS,
dataSource,
});
};
@ -198,7 +236,7 @@ export const externalCallDurationByAddress = ({
};
const expression = FORMULA.DATABASE_CALLS_AVG_DURATION;
const legendFormula = legend;
const disabled = true;
const disabled = [true, true];
const additionalItemsA: TagFilterItem[] = [
{
id: '',
@ -213,26 +251,30 @@ export const externalCallDurationByAddress = ({
},
...tagFilterItems,
];
const additionalItemsB = additionalItemsA;
return getQueryBuilderQuerieswithFormula({
const autocompleteData: BaseAutocompleteData[] = [
autocompleteDataA,
autocompleteDataB,
];
const additionalItems: TagFilterItem[][] = [
additionalItemsA,
additionalItemsB,
legend,
additionalItemsA,
];
const legends = Array(2).fill(legend);
const aggregateOperators = Array(2).fill(MetricAggregateOperator.SUM_RATE);
const dataSource = DataSource.METRICS;
return getQueryBuilderQuerieswithFormula({
autocompleteData,
additionalItems,
legends,
groupBy,
disabled,
expression,
legendFormula,
aggregateOperators,
dataSource,
});
};
interface ExternalCallDurationByAddressProps extends ExternalCallProps {
legend: string;
}
export interface ExternalCallProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
}

View File

@ -1,20 +1,21 @@
import {
alphabet,
initialFormulaBuilderFormValues,
initialQueryBuilderFormValuesMap,
} from 'constants/queryBuilder';
import getStep from 'lib/getStep';
import store from 'store';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import {
IBuilderQuery,
TagFilterItem,
} from 'types/api/queryBuilder/queryBuilderData';
import {
DataSource,
MetricAggregateOperator,
QueryBuilderData,
} from 'types/common/queryBuilder';
import {
BuilderQueriesProps,
BuilderQuerieswithFormulaProps,
} from '../Tabs/types';
export const getQueryBuilderQueries = ({
autocompleteData,
groupBy = [],
@ -61,15 +62,15 @@ export const getQueryBuilderQueries = ({
});
export const getQueryBuilderQuerieswithFormula = ({
autocompleteDataA,
autocompleteDataB,
additionalItemsA,
additionalItemsB,
legend,
autocompleteData,
additionalItems,
legends,
groupBy = [],
disabled,
expression,
legendFormula,
aggregateOperators,
dataSource,
}: BuilderQuerieswithFormulaProps): QueryBuilderData => ({
queryFormulas: [
{
@ -78,66 +79,25 @@ export const getQueryBuilderQuerieswithFormula = ({
legend: legendFormula,
},
],
queryData: [
{
...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled,
groupBy,
legend,
aggregateAttribute: autocompleteDataA,
reduceTo: 'sum',
filters: {
items: additionalItemsA,
op: 'AND',
},
stepInterval: getStep({
end: store.getState().globalTime.maxTime,
inputFormat: 'ns',
start: store.getState().globalTime.minTime,
}),
queryData: autocompleteData.map((_, index) => ({
...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: aggregateOperators[index],
disabled: disabled[index],
groupBy,
legend: legends[index],
aggregateAttribute: autocompleteData[index],
queryName: alphabet[index],
expression: alphabet[index],
reduceTo: 'sum',
filters: {
items: additionalItems[index],
op: 'AND',
},
{
...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled,
groupBy,
legend,
aggregateAttribute: autocompleteDataB,
queryName: 'B',
expression: 'B',
reduceTo: 'sum',
filters: {
items: additionalItemsB,
op: 'AND',
},
stepInterval: getStep({
end: store.getState().globalTime.maxTime,
inputFormat: 'ns',
start: store.getState().globalTime.minTime,
}),
},
],
stepInterval: getStep({
end: store.getState().globalTime.maxTime,
inputFormat: 'ns',
start: store.getState().globalTime.minTime,
}),
dataSource,
})),
});
interface BuilderQueriesProps {
autocompleteData: BaseAutocompleteData[];
groupBy?: BaseAutocompleteData[];
legends: string[];
filterItems: TagFilterItem[][];
aggregateOperator?: string[];
dataSource: DataSource;
queryNameAndExpression?: string[];
}
interface BuilderQuerieswithFormulaProps {
autocompleteDataA: BaseAutocompleteData;
autocompleteDataB: BaseAutocompleteData;
legend: string;
disabled: boolean;
groupBy?: BaseAutocompleteData[];
expression: string;
legendFormula: string;
additionalItemsA: TagFilterItem[];
additionalItemsB: TagFilterItem[];
}

View File

@ -1,7 +1,11 @@
import { OPERATORS } from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, QueryBuilderData } from 'types/common/queryBuilder';
import {
DataSource,
MetricAggregateOperator,
QueryBuilderData,
} from 'types/common/queryBuilder';
import {
DataType,
@ -14,7 +18,7 @@ import {
QUERYNAME_AND_EXPRESSION,
WidgetKeys,
} from '../constant';
import { IServiceName } from '../Tabs/types';
import { LatencyProps, OperationPerSecProps } from '../Tabs/types';
import {
getQueryBuilderQueries,
getQueryBuilderQuerieswithFormula,
@ -35,9 +39,7 @@ export const latency = ({
type: isSpanMetricEnable ? null : MetricsType.Tag,
};
const autocompleteData: BaseAutocompleteData[] = Array(3).fill(
newAutoCompleteData,
);
const autocompleteData = Array(3).fill(newAutoCompleteData);
const filterItem: TagFilterItem[] = [
{
@ -65,17 +67,21 @@ export const latency = ({
...tagFilterItems,
];
const filterItems: TagFilterItem[][] = Array(3).fill([...filterItem]);
const filterItems = Array(3).fill([...filterItem]);
const legends = LATENCY_AGGREGATEOPERATOR;
const aggregateOperator = isSpanMetricEnable
? LATENCY_AGGREGATEOPERATOR_SPAN_METRICS
: LATENCY_AGGREGATEOPERATOR;
const dataSource = isSpanMetricEnable ? DataSource.METRICS : DataSource.TRACES;
const queryNameAndExpression = QUERYNAME_AND_EXPRESSION;
return getQueryBuilderQueries({
autocompleteData,
legends: LATENCY_AGGREGATEOPERATOR,
legends,
filterItems,
aggregateOperator: isSpanMetricEnable
? LATENCY_AGGREGATEOPERATOR_SPAN_METRICS
: LATENCY_AGGREGATEOPERATOR,
dataSource: isSpanMetricEnable ? DataSource.METRICS : DataSource.TRACES,
queryNameAndExpression: QUERYNAME_AND_EXPRESSION,
aggregateOperator,
dataSource,
queryNameAndExpression,
});
};
@ -121,11 +127,14 @@ export const operationPerSec = ({
],
];
const legends = OPERATION_LEGENDS;
const dataSource = DataSource.METRICS;
return getQueryBuilderQueries({
autocompleteData,
legends: OPERATION_LEGENDS,
legends,
filterItems,
dataSource: DataSource.METRICS,
dataSource,
});
};
@ -146,6 +155,9 @@ export const errorPercentage = ({
isColumn: true,
type: null,
};
const autocompleteData = [autocompleteDataA, autocompleteDataB];
const additionalItemsA: TagFilterItem[] = [
{
id: '',
@ -209,27 +221,25 @@ export const errorPercentage = ({
...tagFilterItems,
];
const additionalItems = [additionalItemsA, additionalItemsB];
const legends = [GraphTitle.ERROR_PERCENTAGE];
const disabled = [true, true];
const expression = FORMULA.ERROR_PERCENTAGE;
const legendFormula = GraphTitle.ERROR_PERCENTAGE;
const aggregateOperators = [
MetricAggregateOperator.SUM_RATE,
MetricAggregateOperator.SUM_RATE,
];
const dataSource = DataSource.METRICS;
return getQueryBuilderQuerieswithFormula({
autocompleteDataA,
autocompleteDataB,
additionalItemsA,
additionalItemsB,
legend: GraphTitle.ERROR_PERCENTAGE,
disabled: true,
expression: FORMULA.ERROR_PERCENTAGE,
legendFormula: GraphTitle.ERROR_PERCENTAGE,
autocompleteData,
additionalItems,
legends,
disabled,
expression,
legendFormula,
aggregateOperators,
dataSource,
});
};
export interface OperationPerSecProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
topLevelOperations: string[];
}
export interface LatencyProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
isSpanMetricEnable?: boolean;
topLevelOperationsRoute: string[];
}

View File

@ -0,0 +1,142 @@
import { OPERATORS } from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import {
DataSource,
MetricAggregateOperator,
QueryBuilderData,
} from 'types/common/queryBuilder';
import {
DataType,
GraphTitle,
KeyOperationTableHeader,
MetricsType,
WidgetKeys,
} from '../constant';
import { TopOperationQueryFactoryProps } from '../Tabs/types';
import { getQueryBuilderQuerieswithFormula } from './MetricsPageQueriesFactory';
export const topOperationQueries = ({
servicename,
}: TopOperationQueryFactoryProps): QueryBuilderData => {
const latencyAutoCompleteData: BaseAutocompleteData = {
key: WidgetKeys.Signoz_latency_bucket,
dataType: DataType.FLOAT64,
isColumn: true,
type: null,
};
const errorRateAutoCompleteData: BaseAutocompleteData = {
key: WidgetKeys.SignozCallsTotal,
dataType: DataType.FLOAT64,
isColumn: true,
type: null,
};
const numOfCallAutoCompleteData: BaseAutocompleteData = {
key: WidgetKeys.SignozLatencyCount,
dataType: DataType.FLOAT64,
isColumn: true,
type: null,
};
const latencyAndNumberOfCallAdditionalItems: TagFilterItem[] = [
{
id: '',
key: {
key: WidgetKeys.Service_name,
dataType: DataType.STRING,
isColumn: false,
type: MetricsType.Resource,
},
value: [servicename],
op: OPERATORS.IN,
},
];
const errorRateAdditionalItemsA: TagFilterItem[] = [
{
id: '',
key: {
dataType: DataType.STRING,
isColumn: false,
key: WidgetKeys.Service_name,
type: MetricsType.Resource,
},
op: OPERATORS.IN,
value: [servicename],
},
{
id: '',
key: {
dataType: DataType.INT64,
isColumn: false,
key: WidgetKeys.StatusCode,
type: MetricsType.Tag,
},
op: OPERATORS.IN,
value: ['STATUS_CODE_ERROR'],
},
];
const errorRateAdditionalItemsB = latencyAndNumberOfCallAdditionalItems;
const groupBy: BaseAutocompleteData[] = [
{
dataType: DataType.STRING,
isColumn: false,
key: WidgetKeys.Operation,
type: MetricsType.Tag,
},
];
const autocompleteData = [
latencyAutoCompleteData,
latencyAutoCompleteData,
latencyAutoCompleteData,
errorRateAutoCompleteData,
errorRateAutoCompleteData,
numOfCallAutoCompleteData,
];
const additionalItems = [
latencyAndNumberOfCallAdditionalItems,
latencyAndNumberOfCallAdditionalItems,
latencyAndNumberOfCallAdditionalItems,
errorRateAdditionalItemsA,
errorRateAdditionalItemsB,
latencyAndNumberOfCallAdditionalItems,
];
const disabled = [false, false, false, true, true, false];
const legends = [
KeyOperationTableHeader.P50,
KeyOperationTableHeader.P90,
KeyOperationTableHeader.P99,
KeyOperationTableHeader.ERROR_RATE,
KeyOperationTableHeader.ERROR_RATE,
KeyOperationTableHeader.NUM_OF_CALLS,
];
const aggregateOperators = [
MetricAggregateOperator.HIST_QUANTILE_50,
MetricAggregateOperator.HIST_QUANTILE_90,
MetricAggregateOperator.HIST_QUANTILE_99,
MetricAggregateOperator.SUM_RATE,
MetricAggregateOperator.SUM_RATE,
MetricAggregateOperator.SUM_RATE,
];
const expression = 'D*100/E';
const legendFormula = GraphTitle.ERROR_PERCENTAGE;
const dataSource = DataSource.METRICS;
return getQueryBuilderQuerieswithFormula({
autocompleteData,
additionalItems,
disabled,
legends,
aggregateOperators,
expression,
legendFormula,
dataSource,
groupBy,
});
};

View File

@ -1,4 +1,5 @@
import { Col } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder';
import Graph from 'container/GridGraphLayout/Graph/';
import {
databaseCallsAvgDuration,
@ -50,8 +51,8 @@ function DBCall(): JSX.Element {
const databaseCallsRPSWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: databaseCallsRPS({
@ -62,14 +63,15 @@ function DBCall(): JSX.Element {
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.DATABASE_CALLS_RPS,
),
title: GraphTitle.DATABASE_CALLS_RPS,
panelTypes: PANEL_TYPES.TIME_SERIES,
}),
[servicename, tagFilterItems],
);
const databaseCallsAverageDurationWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: databaseCallsAvgDuration({
@ -79,8 +81,9 @@ function DBCall(): JSX.Element {
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.DATABASE_CALLS_AVG_DURATION,
),
title: GraphTitle.DATABASE_CALLS_AVG_DURATION,
panelTypes: PANEL_TYPES.TIME_SERIES,
}),
[servicename, tagFilterItems],
);

View File

@ -1,4 +1,5 @@
import { Col } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder';
import Graph from 'container/GridGraphLayout/Graph/';
import {
externalCallDuration,
@ -41,8 +42,8 @@ function External(): JSX.Element {
const externalCallErrorWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: externalCallErrorPercent({
@ -53,8 +54,9 @@ function External(): JSX.Element {
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
),
title: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
panelTypes: PANEL_TYPES.TIME_SERIES,
}),
[servicename, tagFilterItems],
);
@ -65,8 +67,8 @@ function External(): JSX.Element {
const externalCallDurationWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: externalCallDuration({
@ -76,15 +78,16 @@ function External(): JSX.Element {
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.EXTERNAL_CALL_DURATION,
),
title: GraphTitle.EXTERNAL_CALL_DURATION,
panelTypes: PANEL_TYPES.TIME_SERIES,
}),
[servicename, tagFilterItems],
);
const externalCallRPSWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: externalCallRpsByAddress({
@ -95,15 +98,16 @@ function External(): JSX.Element {
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
),
title: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
panelTypes: PANEL_TYPES.TIME_SERIES,
}),
[servicename, tagFilterItems],
);
const externalCallDurationAddressWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: externalCallDurationByAddress({
@ -114,8 +118,9 @@ function External(): JSX.Element {
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
),
title: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
panelTypes: PANEL_TYPES.TIME_SERIES,
}),
[servicename, tagFilterItems],
);

View File

@ -2,10 +2,13 @@ import getTopLevelOperations, {
ServiceDataProps,
} from 'api/metrics/getTopLevelOperations';
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { routeConfig } from 'container/SideNav/config';
import { getQueryString } from 'container/SideNav/helper';
import useFeatureFlag from 'hooks/useFeatureFlag';
import useResourceAttribute from 'hooks/useResourceAttribute';
import {
convertRawQueriesToTraceSelectedTags,
@ -29,10 +32,11 @@ import {
errorPercentage,
operationPerSec,
} from '../MetricsPageQueries/OverviewQueries';
import { Col, Row } from '../styles';
import { Card, Col, Row } from '../styles';
import ServiceOverview from './Overview/ServiceOverview';
import TopLevelOperation from './Overview/TopLevelOperations';
import TopOperation from './Overview/TopOperation';
import TopOperationMetrics from './Overview/TopOperationMetrics';
import { Button } from './styles';
import { IServiceName } from './types';
import {
@ -53,6 +57,8 @@ function Application(): JSX.Element {
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
[queries],
);
const isSpanMetricEnabled = useFeatureFlag(FeatureKeys.USE_SPAN_METRICS)
?.active;
const handleSetTimeStamp = useCallback((selectTime: number) => {
setSelectedTimeStamp(selectTime);
@ -104,8 +110,8 @@ function Application(): JSX.Element {
const operationPerSecWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: operationPerSec({
@ -116,15 +122,16 @@ function Application(): JSX.Element {
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.RATE_PER_OPS,
),
title: GraphTitle.RATE_PER_OPS,
panelTypes: PANEL_TYPES.TIME_SERIES,
}),
[servicename, tagFilterItems, topLevelOperationsRoute],
);
const errorPercentageWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: errorPercentage({
@ -135,8 +142,9 @@ function Application(): JSX.Element {
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.ERROR_PERCENTAGE,
),
title: GraphTitle.ERROR_PERCENTAGE,
panelTypes: PANEL_TYPES.TIME_SERIES,
}),
[servicename, tagFilterItems, topLevelOperationsRoute],
);
@ -239,7 +247,9 @@ function Application(): JSX.Element {
</Col>
<Col span={12}>
<TopOperation />
<Card>
{isSpanMetricEnabled ? <TopOperationMetrics /> : <TopOperation />}
</Card>
</Col>
</Row>
</>

View File

@ -1,4 +1,5 @@
import { FeatureKeys } from 'constants/features';
import { PANEL_TYPES } from 'constants/queryBuilder';
import Graph from 'container/GridGraphLayout/Graph/';
import { GraphTitle } from 'container/MetricsApplication/constant';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
@ -31,8 +32,8 @@ function ServiceOverview({
const latencyWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: latency({
@ -44,8 +45,9 @@ function ServiceOverview({
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.LATENCY,
),
title: GraphTitle.LATENCY,
panelTypes: PANEL_TYPES.TIME_SERIES,
}),
[servicename, tagFilterItems, isSpanMetricEnable, topLevelOperationsRoute],
);

View File

@ -0,0 +1,39 @@
import { Tooltip, Typography } from 'antd';
import { navigateToTrace } from 'container/MetricsApplication/utils';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
function ColumnWithLink({
servicename,
minTime,
maxTime,
selectedTraceTags,
record,
}: LinkColumnProps): JSX.Element {
const text = record.toString();
const handleOnClick = (operation: string) => (): void => {
navigateToTrace({
servicename,
operation,
minTime,
maxTime,
selectedTraceTags,
});
};
return (
<Tooltip placement="topLeft" title={text}>
<Typography.Link onClick={handleOnClick(text)}>{text}</Typography.Link>
</Tooltip>
);
}
interface LinkColumnProps {
servicename: string;
minTime: number;
maxTime: number;
selectedTraceTags: string;
record: RowData;
}
export default ColumnWithLink;

View File

@ -0,0 +1,11 @@
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { ReactNode } from 'react';
import { TableRendererProps } from '../../types';
export const getTableColumnRenderer = ({
columnName,
renderFunction,
}: TableRendererProps): Record<string, (record: RowData) => ReactNode> => ({
[columnName]: renderFunction,
});

View File

@ -1,6 +1,5 @@
import getTopOperations from 'api/metrics/getTopOperations';
import Spinner from 'components/Spinner';
import { Card } from 'container/MetricsApplication/styles';
import TopOperationsTable from 'container/MetricsApplication/TopOperationsTable';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
@ -35,11 +34,13 @@ function TopOperation(): JSX.Element {
}),
});
const topOperationData = data || [];
return (
<Card>
<>
{isLoading && <Spinner size="large" tip="Loading..." height="40vh" />}
{!isLoading && <TopOperationsTable data={data || []} />}
</Card>
{!isLoading && <TopOperationsTable data={topOperationData} />}
</>
);
}

View File

@ -0,0 +1,122 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
import { topOperationQueries } from 'container/MetricsApplication/MetricsPageQueries/TopOperationQueries';
import { QueryTable } from 'container/QueryTable';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { isEmpty } from 'lodash-es';
import { ReactNode, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 as uuid } from 'uuid';
import { IServiceName } from '../types';
import { title } from './config';
import ColumnWithLink from './TableRenderer/ColumnWithLink';
import { getTableColumnRenderer } from './TableRenderer/TableColumnRenderer';
function TopOperationMetrics(): JSX.Element {
const { servicename } = useParams<IServiceName>();
const [errorMessage, setErrorMessage] = useState<string | undefined>('');
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const { queries } = useResourceAttribute();
const selectedTraceTags = JSON.stringify(
convertRawQueriesToTraceSelectedTags(queries) || [],
);
const keyOperationWidget = useMemo(
() =>
getWidgetQueryBuilder({
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: topOperationQueries({
servicename,
}),
clickhouse_sql: [],
id: uuid(),
},
panelTypes: PANEL_TYPES.TABLE,
}),
[servicename],
);
const updatedQuery = useStepInterval(keyOperationWidget.query);
const isEmptyWidget = useMemo(
() => keyOperationWidget.id === 'empty' || isEmpty(keyOperationWidget),
[keyOperationWidget],
);
const { data, isLoading } = useGetQueryRange(
{
selectedTime: keyOperationWidget?.timePreferance,
graphType: keyOperationWidget?.panelTypes,
query: updatedQuery,
globalSelectedInterval,
variables: getDashboardVariables(),
},
{
queryKey: [
`GetMetricsQueryRange-${keyOperationWidget?.timePreferance}-${globalSelectedInterval}-${keyOperationWidget?.id}`,
keyOperationWidget,
maxTime,
minTime,
globalSelectedInterval,
],
keepPreviousData: true,
enabled: !isEmptyWidget,
refetchOnMount: false,
onError: (error) => {
setErrorMessage(error.message);
},
},
);
const queryTableData = data?.payload.data.newResult.data.result || [];
const renderColumnCell = useMemo(
() =>
getTableColumnRenderer({
columnName: 'operation',
renderFunction: (record: RowData): ReactNode => (
<ColumnWithLink
servicename={servicename}
minTime={minTime}
maxTime={maxTime}
selectedTraceTags={selectedTraceTags}
record={record}
/>
),
}),
[servicename, minTime, maxTime, selectedTraceTags],
);
if (errorMessage) {
return <div>{errorMessage}</div>;
}
return (
<QueryTable
title={title}
query={updatedQuery}
queryTableData={queryTableData}
loading={isLoading}
renderColumnCell={renderColumnCell}
/>
);
}
export default TopOperationMetrics;

View File

@ -0,0 +1 @@
export const title = (): string => 'Key Operations';

View File

@ -1,3 +1,62 @@
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { ReactNode } from 'react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
export interface IServiceName {
servicename: string;
}
export interface TopOperationQueryFactoryProps {
servicename: IServiceName['servicename'];
}
export interface ExternalCallDurationByAddressProps extends ExternalCallProps {
legend: string;
}
export interface ExternalCallProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
}
export interface BuilderQueriesProps {
autocompleteData: BaseAutocompleteData[];
groupBy?: BaseAutocompleteData[];
legends: string[];
filterItems: TagFilterItem[][];
aggregateOperator?: string[];
dataSource: DataSource;
queryNameAndExpression?: string[];
}
export interface BuilderQuerieswithFormulaProps {
autocompleteData: BaseAutocompleteData[];
legends: string[];
disabled: boolean[];
groupBy?: BaseAutocompleteData[];
expression: string;
legendFormula: string;
additionalItems: TagFilterItem[][];
aggregateOperators: MetricAggregateOperator[];
dataSource: DataSource;
}
export interface OperationPerSecProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
topLevelOperations: string[];
}
export interface LatencyProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
isSpanMetricEnable?: boolean;
topLevelOperationsRoute: string[];
}
export interface TableRendererProps {
columnName: string;
renderFunction: (record: RowData) => ReactNode;
}

View File

@ -1,17 +1,14 @@
import { Tooltip, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { ResizeTable } from 'components/ResizeTable';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import history from 'lib/history';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getErrorRate } from './utils';
import { getErrorRate, navigateToTrace } from './utils';
function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
@ -28,16 +25,15 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element {
const params = useParams<{ servicename: string }>();
const handleOnClick = (operation: string): void => {
const urlParams = new URLSearchParams();
const { servicename } = params;
urlParams.set(QueryParams.startTime, (minTime / 1000000).toString());
urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString());
history.push(
`${
ROUTES.TRACE
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1`,
);
navigateToTrace({
servicename,
operation,
minTime,
maxTime,
selectedTraceTags,
});
};
const columns: ColumnsType<TopOperationList> = [

View File

@ -28,6 +28,14 @@ export enum GraphTitle {
EXTERNAL_CALL_DURATION_BY_ADDRESS = 'External Call duration(by Address)',
}
export enum KeyOperationTableHeader {
P50 = 'P50',
P90 = 'P90',
P99 = 'P99',
NUM_OF_CALLS = 'Number of Calls',
ERROR_RATE = 'Error Rate',
}
export enum DataType {
STRING = 'string',
FLOAT64 = 'float64',

View File

@ -0,0 +1,15 @@
import { Widgets } from 'types/api/dashboard/getAll';
export interface GetWidgetQueryBuilderProps {
query: Widgets['query'];
title?: string;
panelTypes: Widgets['panelTypes'];
}
export interface NavigateToTraceProps {
servicename: string;
operation: string;
minTime: number;
maxTime: number;
selectedTraceTags: string;
}

View File

@ -1,4 +1,26 @@
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import history from 'lib/history';
import { TopOperationList } from './TopOperationsTable';
import { NavigateToTraceProps } from './types';
export const getErrorRate = (list: TopOperationList): number =>
(list.errorCount / list.numCalls) * 100;
export const navigateToTrace = ({
servicename,
operation,
minTime,
maxTime,
selectedTraceTags,
}: NavigateToTraceProps): void => {
const urlParams = new URLSearchParams();
urlParams.set(QueryParams.startTime, (minTime / 1000000).toString());
urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString());
history.push(
`${
ROUTES.TRACE
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1`,
);
};