mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 05:45:56 +08:00
Refactor the Metric Application Layer (#2984)
* refactor: seperate the dependency of top overview query * refactor: added error handle for api call using usequery * refactor: update api layar and condition in component * fix: onDragSelect re-render all graph data * refactor: removed console * refactor: corrected names and updated implemented required condition * fix: the api call issue * refactor: removed useeffect * refactor: reverted the unnecessary changes * refactor: removed the login from service level * refactor: removed the unwanted code * refactor: reverted the unwanted changes in getDashboardVariable * refactor: instead of useQuery used useQueries * refactor: changed the dependencies of useQuery key * refactor: linter fixes * refactor: delete the unrequired files * fix: generecity of the type * fix: moved the type to component * fix: move the logic from container layer to pages layer * refactor: optimised some part of the code * refactor: review changes * refactor: optimised the checks * refactor: checking if the dependency data loaded in full view * refactor: resolve the error of props in overview.ts * refactor: small changes * refactor: enforced the typecasting of constant variable * refactor: refactoring in some of the changes are updated * refactor: refactoring in some of the changes are updated * refactor: removed the extra parameter from useGetQueryRange * refactor: revert the changes back for tab * refactor: metrics application is updated * chore: loading condition is updated for full view component * chore: moved the serviceDataProps type to api layer * chore: message name is updated --------- Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com> Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
parent
08d496e314
commit
98745fc307
@ -7,7 +7,7 @@ export const ServicesTablePage = Loadable(
|
|||||||
export const ServiceMetricsPage = Loadable(
|
export const ServiceMetricsPage = Loadable(
|
||||||
() =>
|
() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "ServiceMetricsPage" */ 'pages/MetricApplication'
|
/* webpackChunkName: "ServiceMetricsPage" */ 'pages/MetricsApplication'
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,30 +1,16 @@
|
|||||||
import axios from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { PayloadProps, Props } from 'types/api/metrics/getServiceOverview';
|
import { PayloadProps, Props } from 'types/api/metrics/getServiceOverview';
|
||||||
|
|
||||||
const getServiceOverview = async (
|
const getServiceOverview = async (props: Props): Promise<PayloadProps> => {
|
||||||
props: Props,
|
const response = await axios.post(`/service/overview`, {
|
||||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
start: `${props.start}`,
|
||||||
try {
|
end: `${props.end}`,
|
||||||
const response = await axios.post(`/service/overview`, {
|
service: props.service,
|
||||||
start: `${props.start}`,
|
step: props.step,
|
||||||
end: `${props.end}`,
|
tags: props.selectedTags,
|
||||||
service: props.service,
|
});
|
||||||
step: props.step,
|
|
||||||
tags: props.selectedTags,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return response.data;
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getServiceOverview;
|
export default getServiceOverview;
|
||||||
|
@ -1,24 +1,12 @@
|
|||||||
import axios from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { PayloadProps, Props } from 'types/api/metrics/getTopLevelOperations';
|
|
||||||
|
|
||||||
const getTopLevelOperations = async (
|
const getTopLevelOperations = async (): Promise<ServiceDataProps> => {
|
||||||
props: Props,
|
const response = await axios.post(`/service/top_level_operations`);
|
||||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
return response.data;
|
||||||
try {
|
};
|
||||||
const response = await axios.post(`/service/top_level_operations`);
|
|
||||||
|
|
||||||
return {
|
export type ServiceDataProps = {
|
||||||
statusCode: 200,
|
[serviceName: string]: string[];
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data[props.service],
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getTopLevelOperations;
|
export default getTopLevelOperations;
|
||||||
|
@ -1,29 +1,15 @@
|
|||||||
import axios from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { PayloadProps, Props } from 'types/api/metrics/getTopOperations';
|
import { PayloadProps, Props } from 'types/api/metrics/getTopOperations';
|
||||||
|
|
||||||
const getTopOperations = async (
|
const getTopOperations = async (props: Props): Promise<PayloadProps> => {
|
||||||
props: Props,
|
const response = await axios.post(`/service/top_operations`, {
|
||||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
start: `${props.start}`,
|
||||||
try {
|
end: `${props.end}`,
|
||||||
const response = await axios.post(`/service/top_operations`, {
|
service: props.service,
|
||||||
start: `${props.start}`,
|
tags: props.selectedTags,
|
||||||
end: `${props.end}`,
|
});
|
||||||
service: props.service,
|
|
||||||
tags: props.selectedTags,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return response.data;
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getTopOperations;
|
export default getTopOperations;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
const SOMETHING_WENT_WRONG = 'Something went wrong';
|
||||||
|
|
||||||
const getVersion = 'version';
|
const getVersion = 'version';
|
||||||
|
|
||||||
export { getVersion };
|
export { getVersion, SOMETHING_WENT_WRONG };
|
||||||
|
@ -26,6 +26,7 @@ function FullView({
|
|||||||
name,
|
name,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
onDragSelect,
|
onDragSelect,
|
||||||
|
isDependedDataLoaded = false,
|
||||||
}: FullViewProps): JSX.Element {
|
}: FullViewProps): JSX.Element {
|
||||||
const { selectedTime: globalSelectedTime } = useSelector<
|
const { selectedTime: globalSelectedTime } = useSelector<
|
||||||
AppState,
|
AppState,
|
||||||
@ -61,6 +62,7 @@ function FullView({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
queryKey,
|
queryKey,
|
||||||
|
enabled: !isDependedDataLoaded,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -76,9 +78,7 @@ function FullView({
|
|||||||
[response],
|
[response],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLoading = response.isLoading === true;
|
if (response.status === 'idle' || response.status === 'loading') {
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return <Spinner height="100%" size="large" tip="Loading..." />;
|
return <Spinner height="100%" size="large" tip="Loading..." />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +123,7 @@ interface FullViewProps {
|
|||||||
name: string;
|
name: string;
|
||||||
yAxisUnit?: string;
|
yAxisUnit?: string;
|
||||||
onDragSelect?: (start: number, end: number) => void;
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
|
isDependedDataLoaded?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
FullView.defaultProps = {
|
FullView.defaultProps = {
|
||||||
@ -130,6 +131,7 @@ FullView.defaultProps = {
|
|||||||
onClickHandler: undefined,
|
onClickHandler: undefined,
|
||||||
yAxisUnit: undefined,
|
yAxisUnit: undefined,
|
||||||
onDragSelect: undefined,
|
onDragSelect: undefined,
|
||||||
|
isDependedDataLoaded: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FullView;
|
export default FullView;
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
|
import { Typography } from 'antd';
|
||||||
|
import getServiceOverview from 'api/metrics/getServiceOverview';
|
||||||
|
import getTopLevelOperations, {
|
||||||
|
ServiceDataProps,
|
||||||
|
} from 'api/metrics/getTopLevelOperations';
|
||||||
|
import getTopOperations from 'api/metrics/getTopOperations';
|
||||||
|
import axios from 'axios';
|
||||||
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
||||||
import Graph from 'components/Graph';
|
import Graph from 'components/Graph';
|
||||||
|
import Spinner from 'components/Spinner';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
|
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
|
||||||
@ -12,16 +20,22 @@ import {
|
|||||||
} from 'hooks/useResourceAttribute/utils';
|
} from 'hooks/useResourceAttribute/utils';
|
||||||
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
|
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
|
||||||
import { colors } from 'lib/getRandomColor';
|
import { colors } from 'lib/getRandomColor';
|
||||||
|
import getStep from 'lib/getStep';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useQueries, UseQueryResult } from 'react-query';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { UpdateTimeInterval } from 'store/actions';
|
import { UpdateTimeInterval } from 'store/actions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { PayloadProps } from 'types/api/metrics/getServiceOverview';
|
||||||
|
import { PayloadProps as PayloadPropsTopOpertions } from 'types/api/metrics/getTopOperations';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import MetricReducer from 'types/reducer/metrics';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
import { Tags } from 'types/reducer/trace';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
import { SOMETHING_WENT_WRONG } from '../../../constants/api';
|
||||||
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
|
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
|
||||||
import {
|
import {
|
||||||
errorPercentage,
|
errorPercentage,
|
||||||
@ -37,9 +51,17 @@ import {
|
|||||||
} from './util';
|
} from './util';
|
||||||
|
|
||||||
function Application(): JSX.Element {
|
function Application(): JSX.Element {
|
||||||
|
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
const { servicename } = useParams<{ servicename?: string }>();
|
const { servicename } = useParams<{ servicename?: string }>();
|
||||||
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
|
const { queries } = useResourceAttribute();
|
||||||
|
const selectedTags = useMemo(
|
||||||
|
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
|
||||||
|
[queries],
|
||||||
|
);
|
||||||
|
|
||||||
const handleSetTimeStamp = useCallback((selectTime: number) => {
|
const handleSetTimeStamp = useCallback((selectTime: number) => {
|
||||||
setSelectedTimeStamp(selectTime);
|
setSelectedTimeStamp(selectTime);
|
||||||
@ -64,12 +86,53 @@ function Application(): JSX.Element {
|
|||||||
[handleSetTimeStamp],
|
[handleSetTimeStamp],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { topOperations, serviceOverview, topLevelOperations } = useSelector<
|
const queryResult = useQueries<
|
||||||
AppState,
|
[
|
||||||
MetricReducer
|
UseQueryResult<PayloadProps>,
|
||||||
>((state) => state.metrics);
|
UseQueryResult<PayloadPropsTopOpertions>,
|
||||||
|
UseQueryResult<ServiceDataProps>,
|
||||||
|
]
|
||||||
|
>([
|
||||||
|
{
|
||||||
|
queryKey: [servicename, selectedTags, minTime, maxTime],
|
||||||
|
queryFn: (): Promise<PayloadProps> =>
|
||||||
|
getServiceOverview({
|
||||||
|
service: servicename || '',
|
||||||
|
start: minTime,
|
||||||
|
end: maxTime,
|
||||||
|
step: getStep({
|
||||||
|
start: minTime,
|
||||||
|
end: maxTime,
|
||||||
|
inputFormat: 'ns',
|
||||||
|
}),
|
||||||
|
selectedTags,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryKey: [minTime, maxTime, servicename, selectedTags],
|
||||||
|
queryFn: (): Promise<PayloadPropsTopOpertions> =>
|
||||||
|
getTopOperations({
|
||||||
|
service: servicename || '',
|
||||||
|
start: minTime,
|
||||||
|
end: maxTime,
|
||||||
|
selectedTags,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryKey: [servicename, minTime, maxTime, selectedTags],
|
||||||
|
queryFn: (): Promise<ServiceDataProps> => getTopLevelOperations(),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const { queries } = useResourceAttribute();
|
const serviceOverview = queryResult[0].data;
|
||||||
|
const serviceOverviewError = queryResult[0].error;
|
||||||
|
const serviceOverviewIsError = queryResult[0].isError;
|
||||||
|
const serviceOverviewIsLoading = queryResult[0].isLoading;
|
||||||
|
const topOperations = queryResult[1].data;
|
||||||
|
const topLevelOperations = queryResult[2].data;
|
||||||
|
const topLevelOperationsError = queryResult[2].error;
|
||||||
|
const topLevelOperationsIsError = queryResult[2].isError;
|
||||||
|
const topLevelOperationsIsLoading = queryResult[2].isLoading;
|
||||||
|
|
||||||
const selectedTraceTags: string = JSON.stringify(
|
const selectedTraceTags: string = JSON.stringify(
|
||||||
convertRawQueriesToTraceSelectedTags(queries) || [],
|
convertRawQueriesToTraceSelectedTags(queries) || [],
|
||||||
@ -89,7 +152,9 @@ function Application(): JSX.Element {
|
|||||||
builder: operationPerSec({
|
builder: operationPerSec({
|
||||||
servicename,
|
servicename,
|
||||||
tagFilterItems,
|
tagFilterItems,
|
||||||
topLevelOperations,
|
topLevelOperations: topLevelOperations
|
||||||
|
? topLevelOperations[servicename || '']
|
||||||
|
: [],
|
||||||
}),
|
}),
|
||||||
clickhouse_sql: [],
|
clickhouse_sql: [],
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
@ -105,7 +170,9 @@ function Application(): JSX.Element {
|
|||||||
builder: errorPercentage({
|
builder: errorPercentage({
|
||||||
servicename,
|
servicename,
|
||||||
tagFilterItems,
|
tagFilterItems,
|
||||||
topLevelOperations,
|
topLevelOperations: topLevelOperations
|
||||||
|
? topLevelOperations[servicename || '']
|
||||||
|
: [],
|
||||||
}),
|
}),
|
||||||
clickhouse_sql: [],
|
clickhouse_sql: [],
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
@ -158,8 +225,12 @@ function Application(): JSX.Element {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const dataSets = useMemo(
|
const dataSets = useMemo(() => {
|
||||||
() => [
|
if (!serviceOverview) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
data: serviceOverview.map((e) =>
|
data: serviceOverview.map((e) =>
|
||||||
parseFloat(convertToNanoSecondsToSecond(e.p99)),
|
parseFloat(convertToNanoSecondsToSecond(e.p99)),
|
||||||
@ -178,19 +249,25 @@ function Application(): JSX.Element {
|
|||||||
),
|
),
|
||||||
...generalChartDataProperties('p50 Latency', 2),
|
...generalChartDataProperties('p50 Latency', 2),
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
[generalChartDataProperties, serviceOverview],
|
}, [generalChartDataProperties, serviceOverview]);
|
||||||
);
|
|
||||||
|
|
||||||
const data = useMemo(
|
const data = useMemo(() => {
|
||||||
() => ({
|
if (!serviceOverview) {
|
||||||
|
return {
|
||||||
|
datasets: [],
|
||||||
|
labels: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
datasets: dataSets,
|
datasets: dataSets,
|
||||||
labels: serviceOverview.map(
|
labels: serviceOverview.map(
|
||||||
(e) => new Date(parseFloat(convertToNanoSecondsToSecond(e.timestamp))),
|
(e) => new Date(parseFloat(convertToNanoSecondsToSecond(e.timestamp))),
|
||||||
),
|
),
|
||||||
}),
|
};
|
||||||
[serviceOverview, dataSets],
|
}, [serviceOverview, dataSets]);
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row gutter={24}>
|
<Row gutter={24}>
|
||||||
@ -208,18 +285,33 @@ function Application(): JSX.Element {
|
|||||||
View Traces
|
View Traces
|
||||||
</Button>
|
</Button>
|
||||||
<Card>
|
<Card>
|
||||||
<GraphTitle>Latency</GraphTitle>
|
{serviceOverviewIsError ? (
|
||||||
<GraphContainer>
|
<Typography>
|
||||||
<Graph
|
{axios.isAxiosError(serviceOverviewError)
|
||||||
animate={false}
|
? serviceOverviewError.response?.data
|
||||||
onClickHandler={handleGraphClick('Service')}
|
: SOMETHING_WENT_WRONG}
|
||||||
name="service_latency"
|
</Typography>
|
||||||
type="line"
|
) : (
|
||||||
data={data}
|
<>
|
||||||
yAxisUnit="ms"
|
<GraphTitle>Latency</GraphTitle>
|
||||||
onDragSelect={onDragSelect}
|
{serviceOverviewIsLoading && (
|
||||||
/>
|
<Spinner size="large" tip="Loading..." height="40vh" />
|
||||||
</GraphContainer>
|
)}
|
||||||
|
{!serviceOverviewIsLoading && (
|
||||||
|
<GraphContainer>
|
||||||
|
<Graph
|
||||||
|
animate={false}
|
||||||
|
onClickHandler={handleGraphClick('Service')}
|
||||||
|
name="service_latency"
|
||||||
|
type="line"
|
||||||
|
data={data}
|
||||||
|
yAxisUnit="ms"
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
@ -237,17 +329,28 @@ function Application(): JSX.Element {
|
|||||||
View Traces
|
View Traces
|
||||||
</Button>
|
</Button>
|
||||||
<Card>
|
<Card>
|
||||||
<GraphTitle>Rate (ops/s)</GraphTitle>
|
{topLevelOperationsIsError ? (
|
||||||
<GraphContainer>
|
<Typography>
|
||||||
<FullView
|
{axios.isAxiosError(topLevelOperationsError)
|
||||||
name="operations_per_sec"
|
? topLevelOperationsError.response?.data
|
||||||
fullViewOptions={false}
|
: SOMETHING_WENT_WRONG}
|
||||||
onClickHandler={handleGraphClick('Rate')}
|
</Typography>
|
||||||
widget={operationPerSecWidget}
|
) : (
|
||||||
yAxisUnit="ops"
|
<>
|
||||||
onDragSelect={onDragSelect}
|
<GraphTitle>Rate (ops/s)</GraphTitle>
|
||||||
/>
|
<GraphContainer>
|
||||||
</GraphContainer>
|
<FullView
|
||||||
|
name="operations_per_sec"
|
||||||
|
fullViewOptions={false}
|
||||||
|
onClickHandler={handleGraphClick('Rate')}
|
||||||
|
widget={operationPerSecWidget}
|
||||||
|
yAxisUnit="ops"
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
isDependedDataLoaded={topLevelOperationsIsLoading}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -265,23 +368,34 @@ function Application(): JSX.Element {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<GraphTitle>Error Percentage</GraphTitle>
|
{topLevelOperationsIsError ? (
|
||||||
<GraphContainer>
|
<Typography>
|
||||||
<FullView
|
{axios.isAxiosError(topLevelOperationsError)
|
||||||
name="error_percentage_%"
|
? topLevelOperationsError.response?.data
|
||||||
fullViewOptions={false}
|
: SOMETHING_WENT_WRONG}
|
||||||
onClickHandler={handleGraphClick('Error')}
|
</Typography>
|
||||||
widget={errorPercentageWidget}
|
) : (
|
||||||
yAxisUnit="%"
|
<>
|
||||||
onDragSelect={onDragSelect}
|
<GraphTitle>Error Percentage</GraphTitle>
|
||||||
/>
|
<GraphContainer>
|
||||||
</GraphContainer>
|
<FullView
|
||||||
|
name="error_percentage_%"
|
||||||
|
fullViewOptions={false}
|
||||||
|
onClickHandler={handleGraphClick('Error')}
|
||||||
|
widget={errorPercentageWidget}
|
||||||
|
yAxisUnit="%"
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
|
isDependedDataLoaded={topLevelOperationsIsLoading}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Card>
|
<Card>
|
||||||
<TopOperationsTable data={topOperations} />
|
<TopOperationsTable data={topOperations || []} />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
import RouteTab from 'components/RouteTab';
|
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
|
|
||||||
import history from 'lib/history';
|
|
||||||
import { memo, useMemo } from 'react';
|
|
||||||
import { generatePath, useParams } from 'react-router-dom';
|
|
||||||
import { useLocation } from 'react-use';
|
|
||||||
|
|
||||||
import DBCall from './Tabs/DBCall';
|
|
||||||
import External from './Tabs/External';
|
|
||||||
import Overview from './Tabs/Overview';
|
|
||||||
|
|
||||||
function OverViewTab(): JSX.Element {
|
|
||||||
return <Overview />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function DbCallTab(): JSX.Element {
|
|
||||||
return <DBCall />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ExternalTab(): JSX.Element {
|
|
||||||
return <External />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ServiceMetrics(): JSX.Element {
|
|
||||||
const { search } = useLocation();
|
|
||||||
const { servicename } = useParams<{ servicename: string }>();
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(search);
|
|
||||||
const tab = searchParams.get('tab');
|
|
||||||
|
|
||||||
const overMetrics = 'Overview Metrics';
|
|
||||||
const dbCallMetrics = 'Database Calls';
|
|
||||||
const externalMetrics = 'External Calls';
|
|
||||||
|
|
||||||
const getActiveKey = (): string => {
|
|
||||||
switch (tab) {
|
|
||||||
case null: {
|
|
||||||
return overMetrics;
|
|
||||||
}
|
|
||||||
case dbCallMetrics: {
|
|
||||||
return dbCallMetrics;
|
|
||||||
}
|
|
||||||
case externalMetrics: {
|
|
||||||
return externalMetrics;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return overMetrics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeKey = getActiveKey();
|
|
||||||
|
|
||||||
const routes = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
Component: OverViewTab,
|
|
||||||
name: overMetrics,
|
|
||||||
route: `${generatePath(ROUTES.SERVICE_METRICS, {
|
|
||||||
servicename,
|
|
||||||
})}?tab=${overMetrics}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Component: DbCallTab,
|
|
||||||
name: dbCallMetrics,
|
|
||||||
route: `${generatePath(ROUTES.SERVICE_METRICS, {
|
|
||||||
servicename,
|
|
||||||
})}?tab=${dbCallMetrics}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Component: ExternalTab,
|
|
||||||
name: externalMetrics,
|
|
||||||
route: `${generatePath(ROUTES.SERVICE_METRICS, {
|
|
||||||
servicename,
|
|
||||||
})}?tab=${externalMetrics}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[servicename],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ResourceAttributesFilter />
|
|
||||||
<RouteTab routes={routes} history={history} activeKey={activeKey} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(ServiceMetrics);
|
|
@ -1,76 +0,0 @@
|
|||||||
import { Typography } from 'antd';
|
|
||||||
import Spinner from 'components/Spinner';
|
|
||||||
import MetricsApplicationContainer from 'container/MetricsApplication';
|
|
||||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
|
||||||
import { useEffect, useMemo } from 'react';
|
|
||||||
import { connect, useSelector } from 'react-redux';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import {
|
|
||||||
GetInitialData,
|
|
||||||
GetInitialDataProps,
|
|
||||||
} from 'store/actions/metrics/getInitialData';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
import MetricReducer from 'types/reducer/metrics';
|
|
||||||
import { Tags } from 'types/reducer/trace';
|
|
||||||
|
|
||||||
function MetricsApplication({ getInitialData }: MetricsProps): JSX.Element {
|
|
||||||
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
|
||||||
(state) => state.globalTime,
|
|
||||||
);
|
|
||||||
const { error, errorMessage, metricsApplicationLoading } = useSelector<
|
|
||||||
AppState,
|
|
||||||
MetricReducer
|
|
||||||
>((state) => state.metrics);
|
|
||||||
|
|
||||||
const { servicename } = useParams<ServiceProps>();
|
|
||||||
const { queries } = useResourceAttribute();
|
|
||||||
|
|
||||||
const selectedTags = useMemo(
|
|
||||||
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
|
|
||||||
[queries],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (servicename !== undefined) {
|
|
||||||
getInitialData({
|
|
||||||
serviceName: servicename,
|
|
||||||
maxTime,
|
|
||||||
minTime,
|
|
||||||
selectedTags,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [servicename, getInitialData, maxTime, minTime, selectedTags]);
|
|
||||||
|
|
||||||
if (metricsApplicationLoading) {
|
|
||||||
return <Spinner tip="Loading..." />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <Typography>{errorMessage}</Typography>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <MetricsApplicationContainer />;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DispatchProps {
|
|
||||||
getInitialData: (props: GetInitialDataProps) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ServiceProps {
|
|
||||||
servicename?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
getInitialData: bindActionCreators(GetInitialData, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
type MetricsProps = DispatchProps;
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(MetricsApplication);
|
|
8
frontend/src/pages/MetricsApplication/config.ts
Normal file
8
frontend/src/pages/MetricsApplication/config.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { MetricsApplicationTab } from './types';
|
||||||
|
|
||||||
|
export const TAB_KEYS_VS_METRICS_APPLICATION_KEY = {
|
||||||
|
[MetricsApplicationTab.DB_CALL_METRICS]: MetricsApplicationTab.DB_CALL_METRICS,
|
||||||
|
[MetricsApplicationTab.EXTERNAL_METRICS]:
|
||||||
|
MetricsApplicationTab.EXTERNAL_METRICS,
|
||||||
|
[MetricsApplicationTab.OVER_METRICS]: MetricsApplicationTab.OVER_METRICS,
|
||||||
|
};
|
54
frontend/src/pages/MetricsApplication/index.tsx
Normal file
54
frontend/src/pages/MetricsApplication/index.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import RouteTab from 'components/RouteTab';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import DBCall from 'container/MetricsApplication/Tabs/DBCall';
|
||||||
|
import External from 'container/MetricsApplication/Tabs/External';
|
||||||
|
import Overview from 'container/MetricsApplication/Tabs/Overview';
|
||||||
|
import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
|
||||||
|
import history from 'lib/history';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { generatePath, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { MetricsApplicationTab, TAB_KEY_VS_LABEL } from './types';
|
||||||
|
import useMetricsApplicationTabKey from './useMetricsApplicationTabKey';
|
||||||
|
|
||||||
|
function MetricsApplication(): JSX.Element {
|
||||||
|
const { servicename } = useParams<{ servicename: string }>();
|
||||||
|
|
||||||
|
const activeKey = useMetricsApplicationTabKey();
|
||||||
|
|
||||||
|
const routes = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Component: Overview,
|
||||||
|
name: TAB_KEY_VS_LABEL[MetricsApplicationTab.OVER_METRICS],
|
||||||
|
route: `${generatePath(ROUTES.SERVICE_METRICS, {
|
||||||
|
servicename,
|
||||||
|
})}?tab=${MetricsApplicationTab.OVER_METRICS}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Component: DBCall,
|
||||||
|
name: TAB_KEY_VS_LABEL[MetricsApplicationTab.DB_CALL_METRICS],
|
||||||
|
route: `${generatePath(ROUTES.SERVICE_METRICS, {
|
||||||
|
servicename,
|
||||||
|
})}?tab=${MetricsApplicationTab.DB_CALL_METRICS}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Component: External,
|
||||||
|
name: TAB_KEY_VS_LABEL[MetricsApplicationTab.EXTERNAL_METRICS],
|
||||||
|
route: `${generatePath(ROUTES.SERVICE_METRICS, {
|
||||||
|
servicename,
|
||||||
|
})}?tab=${MetricsApplicationTab.EXTERNAL_METRICS}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[servicename],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ResourceAttributesFilter />
|
||||||
|
<RouteTab routes={routes} history={history} activeKey={activeKey} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MetricsApplication;
|
11
frontend/src/pages/MetricsApplication/types.ts
Normal file
11
frontend/src/pages/MetricsApplication/types.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export enum MetricsApplicationTab {
|
||||||
|
OVER_METRICS = 'OVER_METRICS',
|
||||||
|
DB_CALL_METRICS = 'DB_CALL_METRICS',
|
||||||
|
EXTERNAL_METRICS = 'EXTERNAL_METRICS',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TAB_KEY_VS_LABEL = {
|
||||||
|
[MetricsApplicationTab.OVER_METRICS]: 'Overview',
|
||||||
|
[MetricsApplicationTab.DB_CALL_METRICS]: 'DB Call Metrics',
|
||||||
|
[MetricsApplicationTab.EXTERNAL_METRICS]: 'External Metrics',
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
|
|
||||||
|
import { TAB_KEY_VS_LABEL } from './types';
|
||||||
|
import { getMetricsApplicationKey } from './utils';
|
||||||
|
|
||||||
|
const useMetricsApplicationTabKey = (): string => {
|
||||||
|
const urlParams = useUrlQuery();
|
||||||
|
|
||||||
|
const tab = urlParams.get('tab');
|
||||||
|
|
||||||
|
return TAB_KEY_VS_LABEL[getMetricsApplicationKey(tab)];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useMetricsApplicationTabKey;
|
17
frontend/src/pages/MetricsApplication/utils.ts
Normal file
17
frontend/src/pages/MetricsApplication/utils.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { TAB_KEYS_VS_METRICS_APPLICATION_KEY } from './config';
|
||||||
|
import { MetricsApplicationTab } from './types';
|
||||||
|
|
||||||
|
export const isMetricsApplicationTab = (
|
||||||
|
tab: string,
|
||||||
|
): tab is MetricsApplicationTab =>
|
||||||
|
Object.values(MetricsApplicationTab).includes(tab as MetricsApplicationTab);
|
||||||
|
|
||||||
|
export const getMetricsApplicationKey = (
|
||||||
|
tab: string | null,
|
||||||
|
): MetricsApplicationTab => {
|
||||||
|
if (tab && isMetricsApplicationTab(tab)) {
|
||||||
|
return TAB_KEYS_VS_METRICS_APPLICATION_KEY[tab];
|
||||||
|
}
|
||||||
|
|
||||||
|
return MetricsApplicationTab.OVER_METRICS;
|
||||||
|
};
|
@ -1,137 +0,0 @@
|
|||||||
// import getDBOverView from 'api/metrics/getDBOverView';
|
|
||||||
// import getExternalAverageDuration from 'api/metrics/getExternalAverageDuration';
|
|
||||||
// import getExternalError from 'api/metrics/getExternalError';
|
|
||||||
// import getExternalService from 'api/metrics/getExternalService';
|
|
||||||
import getServiceOverview from 'api/metrics/getServiceOverview';
|
|
||||||
import getTopLevelOperations from 'api/metrics/getTopLevelOperations';
|
|
||||||
import getTopOperations from 'api/metrics/getTopOperations';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import GetMinMax from 'lib/getMinMax';
|
|
||||||
import getStep from 'lib/getStep';
|
|
||||||
import { Dispatch } from 'redux';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { Props } from 'types/api/metrics/getDBOverview';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
import { Tags } from 'types/reducer/trace';
|
|
||||||
|
|
||||||
export const GetInitialData = (
|
|
||||||
props: GetInitialDataProps,
|
|
||||||
): ((
|
|
||||||
dispatch: Dispatch<AppActions>,
|
|
||||||
getState: () => AppState,
|
|
||||||
) => void) => async (dispatch, getState): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const { globalTime } = getState();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description This is because we keeping the store as source of truth
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
props.maxTime !== globalTime.maxTime &&
|
|
||||||
props.minTime !== globalTime.minTime
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: 'GET_INITIAL_APPLICATION_LOADING',
|
|
||||||
});
|
|
||||||
|
|
||||||
const { maxTime, minTime } = GetMinMax(globalTime.selectedTime, [
|
|
||||||
globalTime.minTime / 1000000,
|
|
||||||
globalTime.maxTime / 1000000,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [
|
|
||||||
// getDBOverViewResponse,
|
|
||||||
// getExternalAverageDurationResponse,
|
|
||||||
// getExternalErrorResponse,
|
|
||||||
// getExternalServiceResponse,
|
|
||||||
getServiceOverviewResponse,
|
|
||||||
getTopOperationsResponse,
|
|
||||||
getTopLevelOperationsResponse,
|
|
||||||
] = await Promise.all([
|
|
||||||
// getDBOverView({
|
|
||||||
// ...props,
|
|
||||||
// }),
|
|
||||||
// getExternalAverageDuration({
|
|
||||||
// ...props,
|
|
||||||
// }),
|
|
||||||
// getExternalError({
|
|
||||||
// ...props,
|
|
||||||
// }),
|
|
||||||
// getExternalService({
|
|
||||||
// ...props,
|
|
||||||
// }),
|
|
||||||
getServiceOverview({
|
|
||||||
end: maxTime,
|
|
||||||
service: props.serviceName,
|
|
||||||
start: minTime,
|
|
||||||
step: getStep({ start: minTime, end: maxTime, inputFormat: 'ns' }),
|
|
||||||
selectedTags: props.selectedTags,
|
|
||||||
}),
|
|
||||||
getTopOperations({
|
|
||||||
end: maxTime,
|
|
||||||
service: props.serviceName,
|
|
||||||
start: minTime,
|
|
||||||
selectedTags: props.selectedTags,
|
|
||||||
}),
|
|
||||||
getTopLevelOperations({
|
|
||||||
service: props.serviceName,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (
|
|
||||||
// getDBOverViewResponse.statusCode === 200 &&
|
|
||||||
// getExternalAverageDurationResponse.statusCode === 200 &&
|
|
||||||
// getExternalErrorResponse.statusCode === 200 &&
|
|
||||||
// getExternalServiceResponse.statusCode === 200 &&
|
|
||||||
getServiceOverviewResponse.statusCode === 200 &&
|
|
||||||
getTopOperationsResponse.statusCode === 200 &&
|
|
||||||
getTopLevelOperationsResponse.statusCode === 200
|
|
||||||
) {
|
|
||||||
dispatch({
|
|
||||||
type: 'GET_INTIAL_APPLICATION_DATA',
|
|
||||||
payload: {
|
|
||||||
// dbOverView: getDBOverViewResponse.payload,
|
|
||||||
// externalAverageDuration: getExternalAverageDurationResponse.payload,
|
|
||||||
// externalError: getExternalErrorResponse.payload,
|
|
||||||
// externalService: getExternalServiceResponse.payload,
|
|
||||||
serviceOverview: getServiceOverviewResponse.payload,
|
|
||||||
topOperations: getTopOperationsResponse.payload,
|
|
||||||
topLevelOperations: getTopLevelOperationsResponse.payload,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dispatch({
|
|
||||||
type: 'GET_INITIAL_APPLICATION_ERROR',
|
|
||||||
payload: {
|
|
||||||
errorMessage:
|
|
||||||
getTopOperationsResponse.error ||
|
|
||||||
getServiceOverviewResponse.error ||
|
|
||||||
getTopLevelOperationsResponse.error ||
|
|
||||||
// getExternalServiceResponse.error ||
|
|
||||||
// getExternalErrorResponse.error ||
|
|
||||||
// getExternalAverageDurationResponse.error ||
|
|
||||||
// getDBOverViewResponse.error ||
|
|
||||||
'Something went wrong',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
dispatch({
|
|
||||||
type: 'GET_INITIAL_APPLICATION_ERROR',
|
|
||||||
payload: {
|
|
||||||
errorMessage: (error as AxiosError).toString() || 'Something went wrong',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface GetInitialDataProps {
|
|
||||||
serviceName: Props['service'];
|
|
||||||
maxTime: GlobalReducer['maxTime'];
|
|
||||||
minTime: GlobalReducer['minTime'];
|
|
||||||
selectedTags: Tags[];
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ import {
|
|||||||
} from 'types/actions/logs';
|
} from 'types/actions/logs';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
const intitalState: GlobalReducer = {
|
const initialState: GlobalReducer = {
|
||||||
maxTime: Date.now() * 1000000,
|
maxTime: Date.now() * 1000000,
|
||||||
minTime: (Date.now() - 15 * 60 * 1000) * 1000000,
|
minTime: (Date.now() - 15 * 60 * 1000) * 1000000,
|
||||||
loading: true,
|
loading: true,
|
||||||
@ -24,7 +24,7 @@ const intitalState: GlobalReducer = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const globalTimeReducer = (
|
const globalTimeReducer = (
|
||||||
state = intitalState,
|
state = initialState,
|
||||||
action: GlobalTimeAction,
|
action: GlobalTimeAction,
|
||||||
): GlobalReducer => {
|
): GlobalReducer => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user