mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-10 22:29:00 +08:00
Feat (UI) :Trace Filter page is updated (#684)
* dayjs and less loader is added * webpack config is added * moment is removed * useDebounceFunction hook is made * old components and reducer is removed * search is updated * changes are upadted for the trace page as skeleton is ready * chore: method is change from dayjs * convertObject into params is updated * initial filters are updated * initial and final filter issue is fixed * selection of the filter is updated * filters are now able to selected * checkbox disable when loading is in progress * chore: getFilter filename is updated * feat: clear all and exapanded filter is updated * chore: clearAll and expand panel is updated * feat: useClickOutSide hook is added * chore: get filter url becomes encoded * chore: get tag filters is added * feat: search tags is wip * bug: global max,min on change bug is resolved * chore: getInitial filter is updated * chore: expand panel is updated * chore: get filter is updated * chore: code smells is updated * feat: loader is added in the panel header to show the loading * chore: search tags in wip * chore: button style is updated * chore: search in wip * chore: search ui is updated from the global state * chore: search in wip * chore: search is updated * chore: getSpansAggregate section is updated * useOutside click is updated * useclickoutside hook is updated * useclickoutside hook is updated * parsing is updated * initial filter is updated * feat: trace table is updated * chore: trace table is updated * chore: useClickout side is updated for the search panel * feat: unneccesary re-render and code is removed * chore: trace table is updated * custom component is removed and used antd search component * error state is updated over search component * chore: search bar is updated * chore: left panel search and table component connection is updated * chore: trace filter config is updated * chore: for graph reducer is updated * chore: graph is updated * chore: table is updated * chore: spans is updated * chore: reducer is updated * chore: graph component is updated * chore: number of graph condition is updated * chore: input and range slider is now sync * chore: duration is updated * chore: clearAllFilter is updated * chore: duration slider is updated * chore: duration is updated and panel body loading is updated * chore: slider container is added to add padding from left to right * chore: Select filter is updated * chore: duration filter is updated * chore: Divider is added * chore: none option is added in both the dropdown * chore: icon are updated * chore: added padding in the pages component * chore: none is updated * chore: antd notification is added in the redux action * chore: some of the changes are updated * chore: display value is updated for the filter panel heading * chore: calulation is memorised * chore: utils function are updated in trace reducer * chore: getFilters are updated * tracetable is updated * chore: actions is updated * chore: metrics application is updated * chore: search on clear action is updated * chore: serviceName panel position is updated * chore: added the label in the duration * bug: edge case is fixed * chore: some more changes are updated * chore: some more changes are updated * chore: clear all is fixed * chore: panel heading caret is updated * chore: checkbox is updated * chore: isError handler is updated over initial render * chore: traces is updated * fix: tag search is updated * chore: loading is added in the trace table and soring is introduced in the trace table * bug: multiple render for the key is fixed * Bug(UI): new suggestion is updated * feat: isTraceFilterEnum function is made * bug: new changes are updated * chore: get Filter is updated * chore: application metrics params is updated * chore: error is added in the application metrics * chore: filters is updated * chore: expand panel edge case is updated * chore: expand panel is updated and utls: updateUrl function is updated * chore: reset trace state when unmounted * chore: getFilter action is updated * chore: api duration is updated * chore: useEffect dependency is updated * chore: filter is updated with the new arch * bug: trace table issue is resolved * chore: application rps url is updated for trace * chore: duration filter is updated * chore: search key is updated * chore: filter is added in the search url * bug: filter is fixed * bug: filter is fixed * bug: filter is fixed * chore: reset trace data when unmounted * chore: TopEnd point is added * chore: getInitialSpanAggregate action is updated * chore: application url is updated * chore: no tags placeholder is updated * chore: flow from customer is now fixed * chore: search is updated * chore: select all button is removed * chore: prev filter is removed to show the result * chore: config is updated * chore: checkbox component is updated * chore: span filter is updated * chore: graph issue is resolved * chore: selected is updated * chore: all filter are selected * feat: new trace page is updated * chore: utils is updated * feat: trace filter page is updated * chore: duration is now fixed * chore: duration clear filter is added * chore: onClickCheck is updated * chore: trace filter page is updated * bug: some of bugs are resolved * chore: duration body is updated * chore: topEndPoint and application query is updated * chore: user selection is updated in the duration filter * chore: panel duration is updated * chore: panel duration is updated * chore: duration bug is solved * chore: function display value is updated
This commit is contained in:
parent
2de6574835
commit
be8ec756c6
@ -42,11 +42,14 @@
|
||||
"d3": "^6.2.0",
|
||||
"d3-flame-graph": "^3.1.1",
|
||||
"d3-tip": "^0.9.1",
|
||||
"dayjs": "^1.10.7",
|
||||
"dotenv": "8.2.0",
|
||||
"file-loader": "6.1.1",
|
||||
"history": "4.10.1",
|
||||
"html-webpack-plugin": "5.1.0",
|
||||
"jest": "26.6.0",
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^10.2.0",
|
||||
"mini-css-extract-plugin": "2.4.5",
|
||||
"monaco-editor": "^0.30.0",
|
||||
"react": "17.0.0",
|
||||
|
@ -19,7 +19,7 @@ export const ServiceMapPage = Loadable(
|
||||
);
|
||||
|
||||
export const TraceDetailPages = Loadable(
|
||||
() => import(/* webpackChunkName: "TraceDetailPage" */ 'pages/TraceDetails'),
|
||||
() => import(/* webpackChunkName: "TraceDetailPage" */ 'pages/Trace'),
|
||||
);
|
||||
|
||||
export const TraceGraphPage = Loadable(
|
||||
|
@ -3,14 +3,13 @@ import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/getGroups';
|
||||
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
|
||||
const getGroups = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const queryParams = Object.keys(props)
|
||||
.map((e) => `${e}=${props[e]}`)
|
||||
.join('&');
|
||||
const queryParams = convertObjectIntoParams(props);
|
||||
|
||||
const response = await AxiosAlertManagerInstance.get(
|
||||
`/alerts/groups?${queryParams}`,
|
||||
|
48
frontend/src/api/trace/getFilters.ts
Normal file
48
frontend/src/api/trace/getFilters.ts
Normal file
@ -0,0 +1,48 @@
|
||||
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/trace/getFilters';
|
||||
import omitBy from 'lodash-es/omitBy';
|
||||
|
||||
const getFilters = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const duration =
|
||||
omitBy(props.other, (_, key) => !key.startsWith('duration')) || [];
|
||||
|
||||
const nonDuration = omitBy(props.other, (_, key) =>
|
||||
key.startsWith('duration'),
|
||||
);
|
||||
|
||||
const exclude: string[] = [];
|
||||
|
||||
props.isFilterExclude.forEach((value, key) => {
|
||||
if (value) {
|
||||
exclude.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
const response = await axios.post<PayloadProps>(`/getSpanFilters`, {
|
||||
start: props.start,
|
||||
end: props.end,
|
||||
getFilters: props.getFilters,
|
||||
...nonDuration,
|
||||
maxDuration: String((duration['duration'] || [])[0] || ''),
|
||||
minDuration: String((duration['duration'] || [])[1] || ''),
|
||||
exclude: exclude,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getFilters;
|
@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/trace/getServiceList';
|
||||
|
||||
const getServiceList = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get('/services/list');
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getServiceList;
|
@ -1,24 +0,0 @@
|
||||
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/trace/getServiceOperation';
|
||||
|
||||
const getServiceOperation = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/service/${props.service}/operations`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getServiceOperation;
|
@ -1,26 +0,0 @@
|
||||
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/trace/getSpans';
|
||||
|
||||
const getSpans = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/spans?&start=${props.start}&end=${props.end}&kind=${props.kind}&lookback=${props.lookback}&maxDuration=${props.maxDuration}&minDuration=${props.minDuration}&operation=${props.operation}&service=${props.service}&limit=${props.limit}&tags=${props.tags}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getSpans;
|
@ -1,26 +0,0 @@
|
||||
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/trace/getSpanAggregate';
|
||||
|
||||
const getSpansAggregate = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/spans/aggregates?start=${props.start}&end=${props.end}&aggregation_option=${props.aggregation_option}&dimension=${props.dimension}&kind=${props.kind}&maxDuration=${props.maxDuration}&minDuration=${props.minDuration}&operation=${props.operation}&service=${props.service}&step=${props.step}&tags=${props.tags}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getSpansAggregate;
|
59
frontend/src/api/trace/getSpans.ts
Normal file
59
frontend/src/api/trace/getSpans.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import omitBy from 'lodash-es/omitBy';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/trace/getSpans';
|
||||
|
||||
const getSpans = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
||||
Key: e.Key[0],
|
||||
Operator: e.Operator,
|
||||
Values: e.Values,
|
||||
}));
|
||||
|
||||
const exclude: string[] = [];
|
||||
|
||||
props.isFilterExclude.forEach((value, key) => {
|
||||
if (value) {
|
||||
exclude.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
const other = Object.fromEntries(props.selectedFilter);
|
||||
|
||||
const duration = omitBy(other, (_, key) => !key.startsWith('duration')) || [];
|
||||
|
||||
const nonDuration = omitBy(other, (_, key) => key.startsWith('duration'));
|
||||
|
||||
const response = await axios.post<PayloadProps>(
|
||||
`/getFilteredSpans/aggregates`,
|
||||
{
|
||||
start: String(props.start),
|
||||
end: String(props.end),
|
||||
function: props.function,
|
||||
groupBy: props.groupBy,
|
||||
step: props.step,
|
||||
tags: updatedSelectedTags,
|
||||
...nonDuration,
|
||||
maxDuration: String((duration['duration'] || [])[0] || ''),
|
||||
minDuration: String((duration['duration'] || [])[1] || ''),
|
||||
exclude,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getSpans;
|
60
frontend/src/api/trace/getSpansAggregate.ts
Normal file
60
frontend/src/api/trace/getSpansAggregate.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import omitBy from 'lodash-es/omitBy';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/trace/getSpanAggregate';
|
||||
import { TraceFilterEnum } from 'types/reducer/trace';
|
||||
|
||||
const getSpanAggregate = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const preProps = {
|
||||
start: String(props.start),
|
||||
end: String(props.end),
|
||||
limit: props.limit,
|
||||
offset: props.offset,
|
||||
};
|
||||
|
||||
const exclude: TraceFilterEnum[] = [];
|
||||
|
||||
props.isFilterExclude.forEach((value, key) => {
|
||||
if (value) {
|
||||
exclude.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
const updatedSelectedTags = props.selectedTags.map((e) => ({
|
||||
Key: e.Key[0],
|
||||
Operator: e.Operator,
|
||||
Values: e.Values,
|
||||
}));
|
||||
|
||||
const other = Object.fromEntries(props.selectedFilter);
|
||||
|
||||
const duration = omitBy(other, (_, key) => !key.startsWith('duration')) || [];
|
||||
|
||||
const nonDuration = omitBy(other, (_, key) => key.startsWith('duration'));
|
||||
|
||||
const response = await axios.post<PayloadProps>(`/getFilteredSpans`, {
|
||||
...preProps,
|
||||
tags: updatedSelectedTags,
|
||||
...nonDuration,
|
||||
maxDuration: String((duration['duration'] || [])[0] || ''),
|
||||
minDuration: String((duration['duration'] || [])[1] || ''),
|
||||
exclude,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getSpanAggregate;
|
38
frontend/src/api/trace/getTagFilter.ts
Normal file
38
frontend/src/api/trace/getTagFilter.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { omitBy } from 'lodash-es';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/trace/getTagFilters';
|
||||
|
||||
const getTagFilters = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const duration =
|
||||
omitBy(props.other, (_, key) => !key.startsWith('duration')) || [];
|
||||
|
||||
const nonDuration = omitBy(props.other, (_, key) =>
|
||||
key.startsWith('duration'),
|
||||
);
|
||||
|
||||
const response = await axios.post<PayloadProps>(`/getTagFilters`, {
|
||||
start: String(props.start),
|
||||
end: String(props.end),
|
||||
...nonDuration,
|
||||
maxDuration: String((duration['duration'] || [])[0] || ''),
|
||||
minDuration: String((duration['duration'] || [])[1] || ''),
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getTagFilters;
|
@ -1,24 +0,0 @@
|
||||
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/trace/getTags';
|
||||
|
||||
const getTags = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/tags?service=${props.service}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getTags;
|
7
frontend/src/components/DatePicker/index.tsx
Normal file
7
frontend/src/components/DatePicker/index.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import { Dayjs } from 'dayjs';
|
||||
import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs';
|
||||
import generatePicker from 'antd/es/date-picker/generatePicker';
|
||||
|
||||
const DatePicker = generatePicker<Dayjs>(dayjsGenerateConfig);
|
||||
|
||||
export default DatePicker;
|
@ -148,7 +148,7 @@ const Graph = ({
|
||||
|
||||
useEffect(() => {
|
||||
buildChart();
|
||||
}, [buildChart]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ height: '85%' }}>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { DatePicker, Modal } from 'antd';
|
||||
import { Moment } from 'moment';
|
||||
import moment from 'moment';
|
||||
import { Modal } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
export type DateTimeRangeType = [Moment | null, Moment | null] | null;
|
||||
export type DateTimeRangeType = [Dayjs | null, Dayjs | null] | null;
|
||||
import DatePicker from 'components/DatePicker';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
@ -20,8 +20,8 @@ const CustomDateTimeModal = ({
|
||||
setCustomDateTimeRange(date_time);
|
||||
}
|
||||
|
||||
function disabledDate(current: Moment): boolean {
|
||||
if (current > moment()) {
|
||||
function disabledDate(current: Dayjs): boolean {
|
||||
if (current > dayjs()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -8,7 +8,7 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
import { LOCAL_STORAGE } from 'constants/localStorage';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import moment from 'moment';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
@ -37,26 +37,18 @@ const DateTimeSelection = ({
|
||||
|
||||
const getTime = useCallback((): [number, number] | undefined => {
|
||||
if (searchEndTime && searchStartTime) {
|
||||
const startMoment = moment(
|
||||
const startDate = dayjs(
|
||||
new Date(parseInt(getTimeString(searchStartTime), 10)),
|
||||
);
|
||||
const endMoment = moment(
|
||||
new Date(parseInt(getTimeString(searchEndTime), 10)),
|
||||
);
|
||||
const endDate = dayjs(new Date(parseInt(getTimeString(searchEndTime), 10)));
|
||||
|
||||
return [
|
||||
startMoment.toDate().getTime() || 0,
|
||||
endMoment.toDate().getTime() || 0,
|
||||
];
|
||||
return [startDate.toDate().getTime() || 0, endDate.toDate().getTime() || 0];
|
||||
}
|
||||
if (localstorageStartTime && localstorageEndTime) {
|
||||
const startMoment = moment(localstorageStartTime);
|
||||
const endMoment = moment(localstorageEndTime);
|
||||
const startDate = dayjs(localstorageStartTime);
|
||||
const endDate = dayjs(localstorageEndTime);
|
||||
|
||||
return [
|
||||
startMoment.toDate().getTime() || 0,
|
||||
endMoment.toDate().getTime() || 0,
|
||||
];
|
||||
return [startDate.toDate().getTime() || 0, endDate.toDate().getTime() || 0];
|
||||
}
|
||||
return undefined;
|
||||
}, [
|
||||
@ -66,8 +58,8 @@ const DateTimeSelection = ({
|
||||
searchStartTime,
|
||||
]);
|
||||
|
||||
const [startTime, setStartTime] = useState<moment.Moment>();
|
||||
const [endTime, setEndTime] = useState<moment.Moment>();
|
||||
const [startTime, setStartTime] = useState<Dayjs>();
|
||||
const [endTime, setEndTime] = useState<Dayjs>();
|
||||
|
||||
const [options, setOptions] = useState(getOptions(location.pathname));
|
||||
const [refreshButtonHidden, setRefreshButtonHidden] = useState<boolean>(false);
|
||||
@ -136,8 +128,8 @@ const DateTimeSelection = ({
|
||||
};
|
||||
|
||||
const getInputLabel = (
|
||||
startTime?: moment.Moment,
|
||||
endTime?: moment.Moment,
|
||||
startTime?: Dayjs,
|
||||
endTime?: Dayjs,
|
||||
timeInterval: Time = '15min',
|
||||
): string | Time => {
|
||||
if (startTime && endTime && timeInterval === 'custom') {
|
||||
@ -153,18 +145,18 @@ const DateTimeSelection = ({
|
||||
};
|
||||
|
||||
const onLastRefreshHandler = useCallback(() => {
|
||||
const currentTime = moment();
|
||||
const currentTime = dayjs();
|
||||
|
||||
const lastRefresh = moment(
|
||||
const lastRefresh = dayjs(
|
||||
selectedTimeInterval === 'custom' ? minTime / 1000000 : maxTime / 1000000,
|
||||
);
|
||||
const duration = moment.duration(currentTime.diff(lastRefresh));
|
||||
|
||||
const secondsDiff = Math.floor(duration.asSeconds());
|
||||
const minutedDiff = Math.floor(duration.asMinutes());
|
||||
const hoursDiff = Math.floor(duration.asHours());
|
||||
const daysDiff = Math.floor(duration.asDays());
|
||||
const monthsDiff = Math.floor(duration.asMonths());
|
||||
const secondsDiff = currentTime.diff(lastRefresh, 'seconds');
|
||||
|
||||
const minutedDiff = currentTime.diff(lastRefresh, 'minutes');
|
||||
const hoursDiff = currentTime.diff(lastRefresh, 'hours');
|
||||
const daysDiff = currentTime.diff(lastRefresh, 'days');
|
||||
const monthsDiff = currentTime.diff(lastRefresh, 'months');
|
||||
|
||||
if (monthsDiff > 0) {
|
||||
return `Last refresh -${monthsDiff} months ago`;
|
||||
@ -242,8 +234,8 @@ const DateTimeSelection = ({
|
||||
|
||||
const [preStartTime = 0, preEndTime = 0] = getTime() || [];
|
||||
|
||||
setStartTime(moment(preStartTime));
|
||||
setEndTime(moment(preEndTime));
|
||||
setStartTime(dayjs(preStartTime));
|
||||
setEndTime(dayjs(preEndTime));
|
||||
|
||||
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
||||
}, [
|
||||
@ -318,8 +310,3 @@ const mapDispatchToProps = (
|
||||
type Props = DispatchProps & RouteComponentProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(withRouter(DateTimeSelection));
|
||||
|
||||
// DateTimeSelection.whyDidYouRender = {
|
||||
// logOnDifferentValues: true,
|
||||
// customName: 'DateTimeSelection',
|
||||
// };
|
||||
|
@ -32,11 +32,12 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => {
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
||||
if (servicename) {
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
||||
}
|
||||
|
||||
history.push(`${ROUTES.TRACE}?${urlParams.toString()}`);
|
||||
history.replace(
|
||||
`${
|
||||
ROUTES.TRACE
|
||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["ok","error"]}&filterToFetchData=["duration","status","serviceName"]&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&isSelectedFilterSkipped=true`,
|
||||
);
|
||||
};
|
||||
|
||||
const onClickhandler = async (
|
||||
@ -85,12 +86,12 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => {
|
||||
const urlParams = new URLSearchParams();
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
||||
if (servicename) {
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
||||
}
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.error, 'true');
|
||||
|
||||
history.push(`${ROUTES.TRACE}?${urlParams.toString()}`);
|
||||
history.replace(
|
||||
`${
|
||||
ROUTES.TRACE
|
||||
}?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&userSelectedFilter={"status":["error"],"serviceName":["${servicename}"]}&isSelectedFilterSkipped=true`,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -28,12 +28,12 @@ const TopEndpointsTable = (props: TopEndpointsTableProps): JSX.Element => {
|
||||
METRICS_PAGE_QUERY_PARAM.endTime,
|
||||
(maxTime / 1000000).toString(),
|
||||
);
|
||||
if (servicename) {
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
||||
}
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.operation, operation);
|
||||
|
||||
history.push(`${ROUTES.TRACE}?${urlParams.toString()}`);
|
||||
history.push(
|
||||
`${
|
||||
ROUTES.TRACE
|
||||
}?${urlParams.toString()}&selected={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&isSelectedFilterSkipped=true&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&isSelectedFilterSkipped=true`,
|
||||
);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<DataProps> = [
|
||||
|
@ -0,0 +1,191 @@
|
||||
import React, { useState } from 'react';
|
||||
import { CheckBoxContainer } from './styles';
|
||||
import { Checkbox, notification, Typography } from 'antd';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { SelectedTraceFilter } from 'store/actions/trace/selectTraceFilter';
|
||||
import AppActions from 'types/actions';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { getFilter, updateURL } from 'store/actions/trace/util';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
import { AxiosError } from 'axios';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { UPDATE_ALL_FILTERS } from 'types/actions/trace';
|
||||
|
||||
const CheckBoxComponent = (props: CheckBoxProps): JSX.Element => {
|
||||
const {
|
||||
selectedFilter,
|
||||
filterLoading,
|
||||
filterToFetchData,
|
||||
spansAggregate,
|
||||
selectedTags,
|
||||
filter,
|
||||
userSelectedFilter,
|
||||
isFilterExclude,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const isUserSelected =
|
||||
(userSelectedFilter.get(props.name) || []).find(
|
||||
(e) => e === props.keyValue,
|
||||
) !== undefined;
|
||||
|
||||
const onCheckHandler = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const newSelectedMap = new Map(selectedFilter);
|
||||
const preUserSelectedMap = new Map(userSelectedFilter);
|
||||
const preIsFilterExclude = new Map(isFilterExclude);
|
||||
|
||||
const isTopicPresent = preUserSelectedMap.get(props.name);
|
||||
|
||||
// append the value
|
||||
if (!isTopicPresent) {
|
||||
preUserSelectedMap.set(props.name, [props.keyValue]);
|
||||
} else {
|
||||
const isValuePresent =
|
||||
isTopicPresent.find((e) => e === props.keyValue) !== undefined;
|
||||
|
||||
// check the value if present then remove the value or isChecked
|
||||
if (isValuePresent) {
|
||||
preUserSelectedMap.set(
|
||||
props.name,
|
||||
isTopicPresent.filter((e) => e !== props.keyValue),
|
||||
);
|
||||
} else {
|
||||
// if not present add into the array of string
|
||||
preUserSelectedMap.set(props.name, [...isTopicPresent, props.keyValue]);
|
||||
}
|
||||
}
|
||||
|
||||
if (newSelectedMap.get(props.name)?.find((e) => e === props.keyValue)) {
|
||||
newSelectedMap.set(props.name, [
|
||||
...(newSelectedMap.get(props.name) || []).filter(
|
||||
(e) => e !== props.keyValue,
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
newSelectedMap.set(props.name, [
|
||||
...new Set([...(newSelectedMap.get(props.name) || []), props.keyValue]),
|
||||
]);
|
||||
}
|
||||
|
||||
if (preIsFilterExclude.get(props.name) !== false) {
|
||||
preIsFilterExclude.set(props.name, true);
|
||||
}
|
||||
|
||||
const response = await getFilters({
|
||||
other: Object.fromEntries(newSelectedMap),
|
||||
end: String(globalTime.maxTime),
|
||||
start: String(globalTime.minTime),
|
||||
getFilters: filterToFetchData.filter((e) => e !== props.name),
|
||||
isFilterExclude: preIsFilterExclude,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
const updatedFilter = getFilter(response.payload);
|
||||
|
||||
updatedFilter.forEach((value, key) => {
|
||||
if (key !== 'duration' && props.name !== key) {
|
||||
preUserSelectedMap.set(key, Object.keys(value));
|
||||
}
|
||||
});
|
||||
|
||||
updatedFilter.set(props.name, {
|
||||
[`${props.keyValue}`]: '-1',
|
||||
...(filter.get(props.name) || {}),
|
||||
...(updatedFilter.get(props.name) || {}),
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ALL_FILTERS,
|
||||
payload: {
|
||||
selectedTags,
|
||||
current: spansAggregate.currentPage,
|
||||
filter: updatedFilter,
|
||||
filterToFetchData,
|
||||
selectedFilter: newSelectedMap,
|
||||
userSelected: preUserSelectedMap,
|
||||
isFilterExclude: preIsFilterExclude,
|
||||
},
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
updateURL(
|
||||
newSelectedMap,
|
||||
filterToFetchData,
|
||||
spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
updatedFilter,
|
||||
preIsFilterExclude,
|
||||
preUserSelectedMap,
|
||||
);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: (error as AxiosError).toString() || 'Something went wrong',
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isCheckBoxSelected = isUserSelected;
|
||||
|
||||
return (
|
||||
<CheckBoxContainer>
|
||||
<Checkbox
|
||||
disabled={isLoading || filterLoading}
|
||||
onClick={onCheckHandler}
|
||||
checked={isCheckBoxSelected}
|
||||
defaultChecked
|
||||
key={props.keyValue}
|
||||
>
|
||||
{props.keyValue}
|
||||
</Checkbox>
|
||||
{isCheckBoxSelected ? (
|
||||
<Typography>{props.value}</Typography>
|
||||
) : (
|
||||
<Typography>-</Typography>
|
||||
)}
|
||||
</CheckBoxContainer>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
selectedTraceFilter: (props: {
|
||||
topic: TraceFilterEnum;
|
||||
value: string;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
interface CheckBoxProps extends DispatchProps {
|
||||
keyValue: string;
|
||||
value: string;
|
||||
name: TraceFilterEnum;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
selectedTraceFilter: bindActionCreators(SelectedTraceFilter, dispatch),
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(CheckBoxComponent);
|
@ -0,0 +1,11 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const CheckBoxContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
`;
|
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
import CheckBoxComponent from '../Common/Checkbox';
|
||||
|
||||
const CommonCheckBox = (props: CommonCheckBoxProps): JSX.Element => {
|
||||
const { filter } = useSelector<AppState, TraceReducer>(
|
||||
(state) => state.traces,
|
||||
);
|
||||
|
||||
const status = filter.get(props.name) || {};
|
||||
|
||||
const statusObj = Object.keys(status);
|
||||
|
||||
return (
|
||||
<>
|
||||
{statusObj.map((e) => (
|
||||
<CheckBoxComponent
|
||||
key={e}
|
||||
{...{
|
||||
name: props.name,
|
||||
keyValue: e,
|
||||
value: status[e],
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface CommonCheckBoxProps {
|
||||
name: TraceFilterEnum;
|
||||
}
|
||||
|
||||
export default CommonCheckBox;
|
@ -0,0 +1,212 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Input, Slider } from 'antd';
|
||||
import { Container, InputContainer, Text } from './styles';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { getFilter, updateURL } from 'store/actions/trace/util';
|
||||
import dayjs from 'dayjs';
|
||||
import durationPlugin from 'dayjs/plugin/duration';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_ALL_FILTERS } from 'types/actions/trace';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { SliderRangeProps } from 'antd/lib/slider';
|
||||
|
||||
dayjs.extend(durationPlugin);
|
||||
|
||||
const getMs = (value: string) => {
|
||||
return dayjs
|
||||
.duration({
|
||||
milliseconds: parseInt(value, 10) / 1000000,
|
||||
})
|
||||
.format('SSS');
|
||||
};
|
||||
|
||||
const Duration = (): JSX.Element => {
|
||||
const {
|
||||
filter,
|
||||
selectedFilter,
|
||||
filterToFetchData,
|
||||
spansAggregate,
|
||||
selectedTags,
|
||||
userSelectedFilter,
|
||||
isFilterExclude,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const getDuration = () => {
|
||||
const selectedDuration = selectedFilter.get('duration');
|
||||
|
||||
if (selectedDuration) {
|
||||
return {
|
||||
maxDuration: selectedDuration[0],
|
||||
minDuration: selectedDuration[1],
|
||||
};
|
||||
}
|
||||
|
||||
return filter.get('duration') || {};
|
||||
};
|
||||
|
||||
const duration = getDuration();
|
||||
|
||||
const maxDuration = duration['maxDuration'] || '0';
|
||||
const minDuration = duration['minDuration'] || '0';
|
||||
|
||||
const [localMax, setLocalMax] = useState<string>(maxDuration);
|
||||
const [localMin, setLocalMin] = useState<string>(minDuration);
|
||||
|
||||
const defaultValue = [parseFloat(minDuration), parseFloat(maxDuration)];
|
||||
|
||||
const updatedUrl = async (min: number, max: number) => {
|
||||
const preSelectedFilter = new Map(selectedFilter);
|
||||
const preUserSelected = new Map(userSelectedFilter);
|
||||
|
||||
preSelectedFilter.set('duration', [String(max), String(min)]);
|
||||
|
||||
console.log('on the update Url');
|
||||
const response = await getFilters({
|
||||
end: String(globalTime.maxTime),
|
||||
getFilters: filterToFetchData,
|
||||
other: Object.fromEntries(preSelectedFilter),
|
||||
start: String(globalTime.minTime),
|
||||
isFilterExclude,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
const preFilter = getFilter(response.payload);
|
||||
|
||||
preFilter.forEach((value, key) => {
|
||||
if (key !== 'duration') {
|
||||
preUserSelected.set(key, Object.keys(value));
|
||||
}
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ALL_FILTERS,
|
||||
payload: {
|
||||
current: spansAggregate.currentPage,
|
||||
filter: preFilter,
|
||||
filterToFetchData,
|
||||
selectedFilter: preSelectedFilter,
|
||||
selectedTags,
|
||||
userSelected: preUserSelected,
|
||||
isFilterExclude,
|
||||
},
|
||||
});
|
||||
|
||||
updateURL(
|
||||
preSelectedFilter,
|
||||
filterToFetchData,
|
||||
spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
preFilter,
|
||||
isFilterExclude,
|
||||
userSelectedFilter,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onRangeSliderHandler = (number: [number, number]) => {
|
||||
const [min, max] = number;
|
||||
|
||||
setLocalMin(min.toString());
|
||||
setLocalMax(max.toString());
|
||||
};
|
||||
|
||||
const debouncedFunction = useDebouncedFn(
|
||||
(min, max) => {
|
||||
console.log('debounce function');
|
||||
updatedUrl(min, max);
|
||||
},
|
||||
500,
|
||||
undefined,
|
||||
[],
|
||||
);
|
||||
|
||||
const onChangeMaxHandler: React.ChangeEventHandler<HTMLInputElement> = (
|
||||
event,
|
||||
) => {
|
||||
const value = event.target.value;
|
||||
const min = parseFloat(localMin);
|
||||
const max = parseFloat(value) * 1000000;
|
||||
|
||||
console.log('on change in max');
|
||||
|
||||
onRangeSliderHandler([min, max]);
|
||||
debouncedFunction(min, max);
|
||||
};
|
||||
|
||||
const onChangeMinHandler: React.ChangeEventHandler<HTMLInputElement> = (
|
||||
event,
|
||||
) => {
|
||||
const value = event.target.value;
|
||||
const min = parseFloat(value) * 1000000;
|
||||
const max = parseFloat(localMax);
|
||||
onRangeSliderHandler([min, max]);
|
||||
console.log('on change in min');
|
||||
debouncedFunction(min, max);
|
||||
};
|
||||
|
||||
const onRangeHandler: SliderRangeProps['onChange'] = ([min, max]) => {
|
||||
updatedUrl(min, max);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Container>
|
||||
<InputContainer>
|
||||
<Text>Min</Text>
|
||||
</InputContainer>
|
||||
<Input
|
||||
addonAfter="ms"
|
||||
onChange={onChangeMinHandler}
|
||||
value={getMs(localMin)}
|
||||
/>
|
||||
|
||||
<InputContainer>
|
||||
<Text>Max</Text>
|
||||
</InputContainer>
|
||||
<Input
|
||||
addonAfter="ms"
|
||||
onChange={onChangeMaxHandler}
|
||||
value={getMs(localMax)}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
<Container>
|
||||
<Slider
|
||||
defaultValue={[defaultValue[0], defaultValue[1]]}
|
||||
min={parseFloat((filter.get('duration') || {})['minDuration'])}
|
||||
max={parseFloat((filter.get('duration') || {})['maxDuration'])}
|
||||
range
|
||||
tipFormatter={(value) => {
|
||||
if (value === undefined) {
|
||||
return '';
|
||||
}
|
||||
return <div>{`${getMs(value.toString())}ms`}</div>;
|
||||
}}
|
||||
onChange={([min, max]) => {
|
||||
onRangeSliderHandler([min, max]);
|
||||
}}
|
||||
onAfterChange={onRangeHandler}
|
||||
// onAfterChange={([min, max]) => {
|
||||
// const returnFunction = debounce((min, max) => updatedUrl(min, max));
|
||||
|
||||
// returnFunction(min, max);
|
||||
// }}
|
||||
value={[parseFloat(localMin), parseFloat(localMax)]}
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Duration;
|
@ -0,0 +1,27 @@
|
||||
import styled from 'styled-components';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
export const DurationText = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
min-height: 8vh;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const InputContainer = styled.div`
|
||||
width: 100%;
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.2rem;
|
||||
`;
|
||||
|
||||
export const Text = styled(Typography)`
|
||||
&&& {
|
||||
text-align: left;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
`;
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
import { Card } from 'antd';
|
||||
|
||||
import Duration from './Duration';
|
||||
import CommonCheckBox from './CommonCheckBox';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import Spinner from 'components/Spinner';
|
||||
|
||||
const PanelBody = (props: PanelBodyProps): JSX.Element => {
|
||||
const { type } = props;
|
||||
|
||||
const { filterLoading } = useSelector<AppState, TraceReducer>(
|
||||
(state) => state.traces,
|
||||
);
|
||||
|
||||
if (filterLoading) {
|
||||
return (
|
||||
<Card bordered={false}>
|
||||
<Spinner height="10vh" tip="Loading.." />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card bordered={false}>
|
||||
{type === 'duration' ? <Duration /> : <CommonCheckBox name={type} />}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
interface PanelBodyProps {
|
||||
type: TraceFilterEnum;
|
||||
}
|
||||
|
||||
export default PanelBody;
|
@ -0,0 +1,3 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div``;
|
@ -0,0 +1,317 @@
|
||||
import React, { useState } from 'react';
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Card, Typography, Divider, notification } from 'antd';
|
||||
|
||||
import {
|
||||
ButtonComponent,
|
||||
ButtonContainer,
|
||||
Container,
|
||||
IconContainer,
|
||||
TextCotainer,
|
||||
} from './styles';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
const { Text } = Typography;
|
||||
|
||||
import { AllPanelHeading } from 'types/reducer/trace';
|
||||
import getFilters from 'api/trace/getFilters';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getFilter, updateURL } from 'store/actions/trace/util';
|
||||
import AppActions from 'types/actions';
|
||||
import { Dispatch } from 'redux';
|
||||
import { UPDATE_ALL_FILTERS } from 'types/actions/trace';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
const PanelHeading = (props: PanelHeadingProps): JSX.Element => {
|
||||
const {
|
||||
filterLoading,
|
||||
filterToFetchData,
|
||||
selectedFilter,
|
||||
spansAggregate,
|
||||
selectedTags,
|
||||
filter,
|
||||
isFilterExclude,
|
||||
userSelectedFilter,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const isDefaultOpen =
|
||||
filterToFetchData.find((e) => e === props.name) !== undefined;
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const global = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const onExpandHandler: React.MouseEventHandler<HTMLDivElement> = async (e) => {
|
||||
try {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
setIsLoading(true);
|
||||
let updatedFilterData: TraceReducer['filterToFetchData'] = [];
|
||||
const getprepdatedSelectedFilter = new Map(selectedFilter);
|
||||
const getPreUserSelected = new Map(userSelectedFilter);
|
||||
|
||||
if (!isDefaultOpen) {
|
||||
updatedFilterData = [props.name];
|
||||
} else {
|
||||
// removing the selected filter
|
||||
updatedFilterData = [
|
||||
...filterToFetchData.filter((name) => name !== props.name),
|
||||
];
|
||||
getprepdatedSelectedFilter.delete(props.name);
|
||||
getPreUserSelected.delete(props.name);
|
||||
}
|
||||
|
||||
const response = await getFilters({
|
||||
end: String(global.maxTime),
|
||||
start: String(global.minTime),
|
||||
getFilters: updatedFilterData,
|
||||
other: Object.fromEntries(getprepdatedSelectedFilter),
|
||||
isFilterExclude,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
const updatedFilter = getFilter(response.payload);
|
||||
|
||||
// is closed
|
||||
if (!isDefaultOpen) {
|
||||
// getprepdatedSelectedFilter.set(
|
||||
// props.name,
|
||||
// Object.keys(updatedFilter.get(props.name) || {}),
|
||||
// );
|
||||
|
||||
getPreUserSelected.set(
|
||||
props.name,
|
||||
Object.keys(updatedFilter.get(props.name) || {}),
|
||||
);
|
||||
|
||||
updatedFilterData = [...filterToFetchData, props.name];
|
||||
}
|
||||
|
||||
// now append the non prop.name trace filter enum over the list
|
||||
// selectedFilter.forEach((value, key) => {
|
||||
// if (key !== props.name) {
|
||||
// getprepdatedSelectedFilter.set(key, value);
|
||||
// }
|
||||
// });
|
||||
|
||||
getPreUserSelected.forEach((value, key) => {
|
||||
if (key !== props.name) {
|
||||
getPreUserSelected.set(key, value);
|
||||
}
|
||||
});
|
||||
filter.forEach((value, key) => {
|
||||
if (key !== props.name) {
|
||||
updatedFilter.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ALL_FILTERS,
|
||||
payload: {
|
||||
current: spansAggregate.currentPage,
|
||||
filter: updatedFilter,
|
||||
filterToFetchData: updatedFilterData,
|
||||
selectedFilter: getprepdatedSelectedFilter,
|
||||
selectedTags,
|
||||
userSelected: getPreUserSelected,
|
||||
isFilterExclude,
|
||||
},
|
||||
});
|
||||
|
||||
updateURL(
|
||||
getprepdatedSelectedFilter,
|
||||
updatedFilterData,
|
||||
spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
updatedFilter,
|
||||
isFilterExclude,
|
||||
getPreUserSelected,
|
||||
);
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: (error as AxiosError).toString() || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onClearAllHandler = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const updatedFilter = new Map(selectedFilter);
|
||||
const preUserSelected = new Map(userSelectedFilter);
|
||||
|
||||
updatedFilter.delete(props.name);
|
||||
preUserSelected.delete(props.name);
|
||||
|
||||
const postIsFilterExclude = new Map(isFilterExclude);
|
||||
|
||||
postIsFilterExclude.set(props.name, false);
|
||||
|
||||
const response = await getFilters({
|
||||
end: String(global.maxTime),
|
||||
start: String(global.minTime),
|
||||
getFilters: filterToFetchData,
|
||||
other: Object.fromEntries(preUserSelected),
|
||||
isFilterExclude: postIsFilterExclude,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200 && response.payload) {
|
||||
const getUpatedFilter = getFilter(response.payload);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ALL_FILTERS,
|
||||
payload: {
|
||||
current: spansAggregate.currentPage,
|
||||
filter: getUpatedFilter,
|
||||
filterToFetchData,
|
||||
selectedFilter: updatedFilter,
|
||||
selectedTags,
|
||||
userSelected: preUserSelected,
|
||||
isFilterExclude: postIsFilterExclude,
|
||||
},
|
||||
});
|
||||
|
||||
updateURL(
|
||||
updatedFilter,
|
||||
filterToFetchData,
|
||||
spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
getUpatedFilter,
|
||||
postIsFilterExclude,
|
||||
preUserSelected,
|
||||
);
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: (error as AxiosError).toString(),
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// const onSelectAllHandler = async () => {
|
||||
// try {
|
||||
// setIsLoading(true);
|
||||
// const preFilter = new Map(filter);
|
||||
// const preSelectedFilter = new Map(selectedFilter);
|
||||
|
||||
// preSelectedFilter.set(
|
||||
// props.name,
|
||||
// Object.keys(preFilter.get(props.name) || {}),
|
||||
// );
|
||||
|
||||
// const response = await getFilters({
|
||||
// end: String(global.maxTime),
|
||||
// start: String(global.minTime),
|
||||
// getFilters: filterToFetchData,
|
||||
// other: Object.fromEntries(preSelectedFilter),
|
||||
// });
|
||||
|
||||
// if (response.statusCode === 200 && response.payload) {
|
||||
// const getUpatedFilter = getFilter(response.payload);
|
||||
|
||||
// preSelectedFilter.set(
|
||||
// props.name,
|
||||
// Object.keys(getUpatedFilter.get(props.name) || {}),
|
||||
// );
|
||||
|
||||
// dispatch({
|
||||
// type: UPDATE_ALL_FILTERS,
|
||||
// payload: {
|
||||
// current: spansAggregate.currentPage,
|
||||
// filter: preFilter,
|
||||
// filterToFetchData,
|
||||
// selectedFilter: preSelectedFilter,
|
||||
// selectedTags,
|
||||
// },
|
||||
// });
|
||||
|
||||
// updateURL(
|
||||
// preSelectedFilter,
|
||||
// filterToFetchData,
|
||||
// spansAggregate.currentPage,
|
||||
// selectedTags,
|
||||
// preFilter,
|
||||
// );
|
||||
// }
|
||||
// setIsLoading(false);
|
||||
// } catch (error) {
|
||||
// setIsLoading(false);
|
||||
|
||||
// notification.error({
|
||||
// message: (error as AxiosError).toString(),
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.name !== 'duration' && <Divider plain style={{ margin: 0 }} />}
|
||||
|
||||
<Card bordered={false}>
|
||||
<Container
|
||||
disabled={filterLoading || isLoading}
|
||||
aria-disabled={filterLoading || isLoading}
|
||||
aria-expanded={props.isOpen}
|
||||
>
|
||||
<TextCotainer onClick={onExpandHandler}>
|
||||
<IconContainer>
|
||||
{!props.isOpen ? <RightOutlined /> : <DownOutlined />}
|
||||
</IconContainer>
|
||||
|
||||
<Text style={{ textTransform: 'capitalize' }} ellipsis>
|
||||
{AllPanelHeading.find((e) => e.key === props.name)?.displayValue || ''}
|
||||
</Text>
|
||||
</TextCotainer>
|
||||
|
||||
{props.name !== 'duration' && (
|
||||
<ButtonContainer>
|
||||
{/* <ButtonComponent
|
||||
aria-disabled={isLoading || filterLoading}
|
||||
disabled={isLoading || filterLoading}
|
||||
onClick={onSelectAllHandler}
|
||||
type="link"
|
||||
>
|
||||
Select All
|
||||
</ButtonComponent> */}
|
||||
|
||||
<ButtonComponent
|
||||
aria-disabled={isLoading || filterLoading}
|
||||
disabled={isLoading || filterLoading}
|
||||
onClick={onClearAllHandler}
|
||||
type="link"
|
||||
>
|
||||
Clear All
|
||||
</ButtonComponent>
|
||||
</ButtonContainer>
|
||||
)}
|
||||
</Container>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface PanelHeadingProps {
|
||||
name: TraceFilterEnum;
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default PanelHeading;
|
@ -0,0 +1,49 @@
|
||||
import { Button } from 'antd';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export const Container = styled.div<Props>`
|
||||
&&& {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-left: 0.5rem;
|
||||
min-height: 5vh;
|
||||
|
||||
cursor: ${({ disabled }) => disabled && 'not-allowed'};
|
||||
|
||||
${({ disabled }) =>
|
||||
disabled &&
|
||||
css`
|
||||
opacity: 0.5;
|
||||
`}
|
||||
}
|
||||
`;
|
||||
|
||||
export const IconContainer = styled.div`
|
||||
&&& {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TextCotainer = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ButtonComponent = styled(Button)`
|
||||
&&& {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
}
|
||||
`;
|
28
frontend/src/container/Trace/Filters/Panel/index.tsx
Normal file
28
frontend/src/container/Trace/Filters/Panel/index.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import PanelBody from './PanelBody';
|
||||
import PanelHeading from './PanelHeading';
|
||||
|
||||
const Panel = (props: PanelProps): JSX.Element => {
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const isDefaultOpen =
|
||||
traces.filterToFetchData.find((e) => e === props.name) !== undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelHeading name={props.name} isOpen={isDefaultOpen} />
|
||||
|
||||
{isDefaultOpen && <PanelBody type={props.name} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface PanelProps {
|
||||
name: TraceFilterEnum;
|
||||
}
|
||||
|
||||
export default Panel;
|
27
frontend/src/container/Trace/Filters/index.tsx
Normal file
27
frontend/src/container/Trace/Filters/index.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { TraceFilterEnum } from 'types/reducer/trace';
|
||||
|
||||
import Panel from './Panel';
|
||||
|
||||
export const AllTraceFilterEnum: TraceFilterEnum[] = [
|
||||
'duration',
|
||||
'status',
|
||||
'serviceName',
|
||||
'operation',
|
||||
'component',
|
||||
'httpCode',
|
||||
'httpHost',
|
||||
'httpMethod',
|
||||
'httpRoute',
|
||||
'httpUrl',
|
||||
];
|
||||
|
||||
const Filters = (): JSX.Element => (
|
||||
<React.Fragment>
|
||||
{AllTraceFilterEnum.map((panelName) => (
|
||||
<Panel key={panelName} name={panelName} />
|
||||
))}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
export default Filters;
|
20
frontend/src/container/Trace/Filters/styles.ts
Normal file
20
frontend/src/container/Trace/Filters/styles.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Button, Input } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const DurationContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const InputComponent = styled(Input)`
|
||||
&&& {
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CheckBoxContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
123
frontend/src/container/Trace/Graph/config.ts
Normal file
123
frontend/src/container/Trace/Graph/config.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { ChartData, ChartDataset, ChartDatasetProperties } from 'chart.js';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import dayjs from 'dayjs';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
|
||||
function transposeArray(array: number[][], arrayLength: number) {
|
||||
let newArray: number[][] = [];
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
newArray.push([]);
|
||||
}
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
for (let j = 0; j < arrayLength; j++) {
|
||||
newArray[j]?.push(array[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
return newArray;
|
||||
}
|
||||
|
||||
export const getChartData = (
|
||||
data: TraceReducer['spansGraph']['payload'],
|
||||
): ChartData<'line'> => {
|
||||
const allDataPoints = data.items;
|
||||
|
||||
const chartDataset: ChartDatasetProperties<'line', number[]> = {
|
||||
data: [],
|
||||
type: 'line',
|
||||
};
|
||||
|
||||
const chartLabels: ChartData<'line'>['labels'] = [];
|
||||
|
||||
Object.keys(allDataPoints).forEach((timestamp) => {
|
||||
const key = allDataPoints[timestamp];
|
||||
if (key.value) {
|
||||
chartDataset.data.push(key.value);
|
||||
const date = dayjs(key.timestamp / 1000000);
|
||||
chartLabels.push(date.toDate().getTime());
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
...chartDataset,
|
||||
borderWidth: 1.5,
|
||||
spanGaps: true,
|
||||
borderColor: colors[0] || 'red',
|
||||
showLine: true,
|
||||
pointRadius: 0,
|
||||
},
|
||||
],
|
||||
labels: chartLabels,
|
||||
};
|
||||
};
|
||||
|
||||
export const getChartDataforGroupBy = (
|
||||
props: TraceReducer['spansGraph']['payload'],
|
||||
): ChartData => {
|
||||
const items = props.items;
|
||||
|
||||
const chartData: ChartData = {
|
||||
datasets: [],
|
||||
labels: [],
|
||||
};
|
||||
|
||||
let max = 0;
|
||||
|
||||
const allGroupBy = Object.keys(items).map((e) => items[e].groupBy);
|
||||
|
||||
Object.keys(allGroupBy).map((e) => {
|
||||
const length = Object.keys(allGroupBy[e]).length;
|
||||
|
||||
if (length >= max) {
|
||||
max = length;
|
||||
}
|
||||
});
|
||||
|
||||
const numberOfGraphs = max;
|
||||
|
||||
const spansGraph: number[][] = [];
|
||||
|
||||
const names: string[] = [];
|
||||
|
||||
// number of data points
|
||||
Object.keys(items).forEach((item) => {
|
||||
const spanData = items[item];
|
||||
const date = dayjs(Number(item) / 1000000)
|
||||
.toDate()
|
||||
.getTime();
|
||||
|
||||
chartData.labels?.push(date);
|
||||
|
||||
const groupBy = spanData.groupBy;
|
||||
const preData: number[] = [];
|
||||
|
||||
if (groupBy) {
|
||||
Object.keys(groupBy).forEach((key) => {
|
||||
const value = groupBy[key];
|
||||
preData.push(value);
|
||||
names.push(key);
|
||||
});
|
||||
|
||||
spansGraph.push(preData);
|
||||
}
|
||||
});
|
||||
|
||||
const updatedName = [...new Set(names)];
|
||||
|
||||
transposeArray(spansGraph, numberOfGraphs).forEach((values, index) => {
|
||||
chartData.datasets.push({
|
||||
data: values.map((e) => e || 0),
|
||||
borderWidth: 1.5,
|
||||
spanGaps: true,
|
||||
borderColor: colors[index] || 'red',
|
||||
showLine: true,
|
||||
pointRadius: 0,
|
||||
label: updatedName[index],
|
||||
});
|
||||
});
|
||||
|
||||
return chartData;
|
||||
};
|
48
frontend/src/container/Trace/Graph/index.tsx
Normal file
48
frontend/src/container/Trace/Graph/index.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import Graph from 'components/Graph';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { Container } from './styles';
|
||||
import { Typography } from 'antd';
|
||||
import { getChartData, getChartDataforGroupBy } from './config';
|
||||
|
||||
const TraceGraph = () => {
|
||||
const { spansGraph, selectedGroupBy } = useSelector<AppState, TraceReducer>(
|
||||
(state) => state.traces,
|
||||
);
|
||||
|
||||
const { loading, error, errorMessage, payload } = spansGraph;
|
||||
|
||||
const ChartData = useMemo(() => {
|
||||
return selectedGroupBy.length === 0
|
||||
? getChartData(payload)
|
||||
: getChartDataforGroupBy(payload);
|
||||
}, [payload]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Container center>
|
||||
<Typography>{errorMessage || 'Something went wrong'}</Typography>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading || payload === undefined) {
|
||||
return (
|
||||
<Container>
|
||||
<Spinner height={'20vh'} size="small" tip="Loading..." />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Graph data={ChartData} name="traceGraphph" type="line" />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default TraceGraph;
|
19
frontend/src/container/Trace/Graph/styles.ts
Normal file
19
frontend/src/container/Trace/Graph/styles.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
interface Props {
|
||||
center?: boolean;
|
||||
}
|
||||
|
||||
export const Container = styled.div<Props>`
|
||||
height: 25vh;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
${({ center }) =>
|
||||
center &&
|
||||
css`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`}
|
||||
`;
|
102
frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx
Normal file
102
frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import { AutoComplete, AutoCompleteProps, Input, notification } from 'antd';
|
||||
import getTagFilters from 'api/trace/getTagFilter';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
const TagsKey = (props: TagsKeysProps): JSX.Element => {
|
||||
const [selectLoading, setSelectLoading] = useState<boolean>(false);
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [selectedKey, setSelectedKey] = useState<string>(props.tag.Key[0] || '');
|
||||
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const [options, setOptions] = useState<AutoCompleteProps['options']>([]);
|
||||
|
||||
const onSearchHandler = async () => {
|
||||
try {
|
||||
setSelectLoading(true);
|
||||
const response = await getTagFilters({
|
||||
start: globalTime.minTime,
|
||||
end: globalTime.maxTime,
|
||||
other: Object.fromEntries(traces.selectedFilter),
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
if (response.payload === null) {
|
||||
setOptions([
|
||||
{
|
||||
value: '',
|
||||
label: 'No tags available',
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
setOptions(
|
||||
response.payload.map((e) => ({
|
||||
value: e.tagKeys,
|
||||
label: e.tagKeys,
|
||||
})),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
setSelectLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: 'Something went wrong',
|
||||
});
|
||||
setSelectLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onSearchHandler();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
dropdownClassName="certain-category-search-dropdown"
|
||||
dropdownMatchSelectWidth={500}
|
||||
style={{ width: 300 }}
|
||||
options={options}
|
||||
value={selectedKey}
|
||||
onChange={(value) => {
|
||||
if (options && options.find((option) => option.value === value)) {
|
||||
setSelectedKey(value);
|
||||
|
||||
props.setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, props.index),
|
||||
{
|
||||
Key: [value],
|
||||
Operator: props.tag.Operator,
|
||||
Values: props.tag.Values,
|
||||
},
|
||||
...tags.slice(props.index + 1, tags.length),
|
||||
]);
|
||||
} else {
|
||||
setSelectedKey('');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Input disabled={selectLoading} placeholder="Please select" />
|
||||
</AutoComplete>
|
||||
);
|
||||
};
|
||||
|
||||
interface TagsKeysProps {
|
||||
index: number;
|
||||
tag: FlatArray<TraceReducer['selectedTags'], 1>;
|
||||
setLocalSelectedTags: React.Dispatch<
|
||||
React.SetStateAction<TraceReducer['selectedTags']>
|
||||
>;
|
||||
}
|
||||
|
||||
export default TagsKey;
|
130
frontend/src/container/Trace/Search/AllTags/Tag/index.tsx
Normal file
130
frontend/src/container/Trace/Search/AllTags/Tag/index.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Select } from 'antd';
|
||||
import {
|
||||
Container,
|
||||
IconContainer,
|
||||
SelectComponent,
|
||||
ValueSelect,
|
||||
} from './styles';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { SelectValue } from 'antd/lib/select';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import AppActions from 'types/actions';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { UpdateSelectedTags } from 'store/actions/trace/updateTagsSelected';
|
||||
import TagsKey from './TagKey';
|
||||
const { Option } = Select;
|
||||
|
||||
type Tags = FlatArray<TraceReducer['selectedTags'], 1>['Operator'];
|
||||
|
||||
const AllMenu: AllMenu[] = [
|
||||
{
|
||||
key: 'in',
|
||||
value: 'IN',
|
||||
},
|
||||
{
|
||||
key: 'not in',
|
||||
value: 'NOT IN',
|
||||
},
|
||||
];
|
||||
|
||||
interface AllMenu {
|
||||
key: Tags | '';
|
||||
value: string;
|
||||
}
|
||||
|
||||
const SingleTags = (props: AllTagsProps): JSX.Element => {
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
const {
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
Values: selectedValues,
|
||||
} = props.tag;
|
||||
|
||||
const onDeleteTagHandler = (index: number) => {
|
||||
props.onCloseHandler(index);
|
||||
};
|
||||
|
||||
const onChangeOperatorHandler = (key: SelectValue) => {
|
||||
props.setLocalSelectedTags([
|
||||
...traces.selectedTags.slice(0, props.index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Values: selectedValues,
|
||||
Operator: key as Tags,
|
||||
},
|
||||
...traces.selectedTags.slice(props.index + 1, traces.selectedTags.length),
|
||||
]);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
<TagsKey
|
||||
index={props.index}
|
||||
tag={props.tag}
|
||||
setLocalSelectedTags={props.setLocalSelectedTags}
|
||||
/>
|
||||
|
||||
<SelectComponent
|
||||
onChange={onChangeOperatorHandler}
|
||||
value={AllMenu.find((e) => e.key === selectedOperator)?.value || ''}
|
||||
>
|
||||
{AllMenu.map((e) => (
|
||||
<Option key={e.key} value={e.value}>
|
||||
{e.value}
|
||||
</Option>
|
||||
))}
|
||||
</SelectComponent>
|
||||
|
||||
<ValueSelect
|
||||
value={selectedValues}
|
||||
onChange={(value) => {
|
||||
props.setLocalSelectedTags((tags) => [
|
||||
...tags.slice(0, props.index),
|
||||
{
|
||||
Key: selectedKey,
|
||||
Operator: selectedOperator,
|
||||
Values: value as string[],
|
||||
},
|
||||
...tags.slice(props.index + 1, tags.length),
|
||||
]);
|
||||
}}
|
||||
mode="tags"
|
||||
/>
|
||||
|
||||
<IconContainer
|
||||
role={'button'}
|
||||
onClick={() => onDeleteTagHandler(props.index)}
|
||||
>
|
||||
<CloseOutlined />
|
||||
</IconContainer>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
updateSelectedTags: (props: TraceReducer['selectedTags']) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateSelectedTags: bindActionCreators(UpdateSelectedTags, dispatch),
|
||||
});
|
||||
|
||||
interface AllTagsProps extends DispatchProps {
|
||||
onCloseHandler: (index: number) => void;
|
||||
index: number;
|
||||
tag: FlatArray<TraceReducer['selectedTags'], 1>;
|
||||
setLocalSelectedTags: React.Dispatch<
|
||||
React.SetStateAction<TraceReducer['selectedTags']>
|
||||
>;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(SingleTags);
|
39
frontend/src/container/Trace/Search/AllTags/Tag/styles.ts
Normal file
39
frontend/src/container/Trace/Search/AllTags/Tag/styles.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import styled from 'styled-components';
|
||||
import { Button, Select, Space } from 'antd';
|
||||
|
||||
export const SpaceComponent = styled(Space)`
|
||||
&&& {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SelectComponent = styled(Select)`
|
||||
&&& {
|
||||
min-width: 170px;
|
||||
margin-right: 21.91px;
|
||||
margin-left: 21.92px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ValueSelect = styled(Select)`
|
||||
&&& {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Container = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const IconContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
margin-left: 1.125rem;
|
||||
`;
|
158
frontend/src/container/Trace/Search/AllTags/index.tsx
Normal file
158
frontend/src/container/Trace/Search/AllTags/index.tsx
Normal file
@ -0,0 +1,158 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Button, Space, Typography } from 'antd';
|
||||
import { CaretRightFilled } from '@ant-design/icons';
|
||||
import {
|
||||
Container,
|
||||
ButtonContainer,
|
||||
CurrentTagsContainer,
|
||||
Wrapper,
|
||||
ErrorContainer,
|
||||
} from './styles';
|
||||
import Tags from './Tag';
|
||||
const { Text } = Typography;
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import AppActions from 'types/actions';
|
||||
import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError';
|
||||
import { parseTagsToQuery } from '../util';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { UpdateTagVisiblity } from 'store/actions/trace/updateTagPanelVisiblity';
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
const AllTags = ({
|
||||
updateTagIsError,
|
||||
onChangeHandler,
|
||||
updateTagVisiblity,
|
||||
updateFilters,
|
||||
}: AllTagsProps): JSX.Element => {
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const [localSelectedTags, setLocalSelectedTags] = useState<
|
||||
TraceReducer['selectedTags']
|
||||
>(traces.selectedTags);
|
||||
|
||||
const onTagAddHandler = () => {
|
||||
setLocalSelectedTags((tags) => [
|
||||
...tags,
|
||||
{
|
||||
Key: [],
|
||||
Operator: 'in',
|
||||
Values: [],
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEqual(traces.selectedTags, localSelectedTags)) {
|
||||
setLocalSelectedTags(traces.selectedTags);
|
||||
}
|
||||
}, [traces.selectedTags]);
|
||||
|
||||
const onCloseHandler = (index: number) => {
|
||||
setLocalSelectedTags([
|
||||
...localSelectedTags.slice(0, index),
|
||||
...localSelectedTags.slice(index + 1, localSelectedTags.length),
|
||||
]);
|
||||
};
|
||||
|
||||
const onRunQueryHandler = () => {
|
||||
const parsedQuery = parseTagsToQuery(localSelectedTags);
|
||||
|
||||
if (parsedQuery.isError) {
|
||||
updateTagIsError(true);
|
||||
} else {
|
||||
onChangeHandler(parsedQuery.payload);
|
||||
updateFilters(localSelectedTags);
|
||||
updateTagIsError(false);
|
||||
updateTagVisiblity(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onResetHandler = () => {
|
||||
setLocalSelectedTags([]);
|
||||
};
|
||||
|
||||
if (traces.isTagModalError) {
|
||||
return (
|
||||
<ErrorContainer>
|
||||
<Paragraph style={{ color: '#E89A3C' }}>
|
||||
Unrecognised query format. Please reset your query by clicking `X` in the
|
||||
search bar above.
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph style={{ color: '#E89A3C' }}>
|
||||
Please click on the search bar to get a drop down to select relevant tags
|
||||
</Paragraph>
|
||||
</ErrorContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
<Wrapper>
|
||||
<Typography>Tags</Typography>
|
||||
|
||||
<CurrentTagsContainer>
|
||||
{localSelectedTags.map((tags, index) => (
|
||||
<Tags
|
||||
key={index}
|
||||
tag={tags}
|
||||
index={index}
|
||||
onCloseHandler={() => onCloseHandler(index)}
|
||||
setLocalSelectedTags={setLocalSelectedTags}
|
||||
/>
|
||||
))}
|
||||
</CurrentTagsContainer>
|
||||
|
||||
<Space wrap direction="horizontal">
|
||||
<Button type="primary" onClick={onTagAddHandler} icon={<PlusOutlined />}>
|
||||
Add Tags Filter
|
||||
</Button>
|
||||
|
||||
<Text ellipsis>
|
||||
Results will include spans with ALL the specified tags ( Rows are `anded`
|
||||
)
|
||||
</Text>
|
||||
</Space>
|
||||
</Wrapper>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button onClick={onResetHandler}>Reset</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onRunQueryHandler}
|
||||
icon={<CaretRightFilled />}
|
||||
>
|
||||
Run Query
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
updateTagIsError: (value: boolean) => void;
|
||||
updateTagVisiblity: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch),
|
||||
updateTagVisiblity: bindActionCreators(UpdateTagVisiblity, dispatch),
|
||||
});
|
||||
|
||||
interface AllTagsProps extends DispatchProps {
|
||||
updateFilters: (tags: TraceReducer['selectedTags']) => void;
|
||||
onChangeHandler: (search: string) => void;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(AllTags);
|
56
frontend/src/container/Trace/Search/AllTags/styles.ts
Normal file
56
frontend/src/container/Trace/Search/AllTags/styles.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import styled from 'styled-components';
|
||||
import { Card } from 'antd';
|
||||
|
||||
export const Container = styled(Card)`
|
||||
top: 120%;
|
||||
min-height: 20vh;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
|
||||
.ant-card-body {
|
||||
padding-bottom: 0;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ErrorContainer = styled(Card)`
|
||||
top: 120%;
|
||||
min-height: 20vh;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
&&& {
|
||||
padding-right: 2rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
background-color: #303030;
|
||||
padding-top: 11px;
|
||||
padding-bottom: 11px;
|
||||
padding-right: 38.01px;
|
||||
|
||||
margin-top: 1rem;
|
||||
|
||||
> button:nth-child(1) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CurrentTagsContainer = styled.div`
|
||||
margin-bottom: 1rem;
|
||||
`;
|
163
frontend/src/container/Trace/Search/index.tsx
Normal file
163
frontend/src/container/Trace/Search/index.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Space } from 'antd';
|
||||
import { Container, SearchComponent } from './styles';
|
||||
import useClickOutside from 'hooks/useClickOutside';
|
||||
import Tags from './AllTags';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import AppActions from 'types/actions';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { UpdateTagVisiblity } from 'store/actions/trace/updateTagPanelVisiblity';
|
||||
import { parseQueryToTags, parseTagsToQuery } from './util';
|
||||
import { UpdateTagIsError } from 'store/actions/trace/updateIsTagsError';
|
||||
import { CaretRightFilled } from '@ant-design/icons';
|
||||
import { updateURL } from 'store/actions/trace/util';
|
||||
import { UPDATE_ALL_FILTERS } from 'types/actions/trace';
|
||||
|
||||
const Search = ({
|
||||
updateTagVisiblity,
|
||||
updateTagIsError,
|
||||
}: SearchProps): JSX.Element => {
|
||||
const traces = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const [value, setValue] = useState<string>('');
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
useEffect(() => {
|
||||
if (traces.filterLoading) {
|
||||
const initialTags = parseTagsToQuery(traces.selectedTags);
|
||||
if (!initialTags.isError) {
|
||||
setValue(initialTags.payload);
|
||||
}
|
||||
}
|
||||
}, [traces.selectedTags, traces.filterLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (value.length === 0 && traces.isTagModalError) {
|
||||
updateTagIsError(false);
|
||||
}
|
||||
}, [traces.isTagModalError, value]);
|
||||
|
||||
const tagRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useClickOutside(tagRef, (e: HTMLElement) => {
|
||||
// using this hack as overlay span is voilating this condition
|
||||
if (
|
||||
e.nodeName === 'svg' ||
|
||||
e.nodeName === 'path' ||
|
||||
e.nodeName === 'span' ||
|
||||
e.nodeName === 'button'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
e.nodeName === 'DIV' &&
|
||||
![
|
||||
'ant-select-item-option-content',
|
||||
'ant-empty-image',
|
||||
'ant-select-item',
|
||||
'ant-col',
|
||||
'ant-select-item-option-active',
|
||||
].find((p) => p.indexOf(e.className) !== -1) &&
|
||||
!(e.ariaSelected === 'true') &&
|
||||
traces.isTagModalOpen
|
||||
) {
|
||||
updateTagVisiblity(false);
|
||||
}
|
||||
});
|
||||
|
||||
const onChangeHandler = (search: string) => {
|
||||
setValue(search);
|
||||
};
|
||||
|
||||
const setIsTagsModalHandler = (value: boolean) => {
|
||||
updateTagVisiblity(value);
|
||||
};
|
||||
|
||||
const onFocusHandler: React.FocusEventHandler<HTMLInputElement> = (e) => {
|
||||
e.preventDefault();
|
||||
setIsTagsModalHandler(true);
|
||||
};
|
||||
|
||||
const updateFilters = async (selectedTags: TraceReducer['selectedTags']) => {
|
||||
dispatch({
|
||||
type: UPDATE_ALL_FILTERS,
|
||||
payload: {
|
||||
selectedTags,
|
||||
current: traces.spansAggregate.currentPage,
|
||||
filter: traces.filter,
|
||||
filterToFetchData: traces.filterToFetchData,
|
||||
selectedFilter: traces.selectedFilter,
|
||||
userSelected: traces.userSelectedFilter,
|
||||
isFilterExclude: traces.isFilterExclude,
|
||||
},
|
||||
});
|
||||
|
||||
updateURL(
|
||||
traces.selectedFilter,
|
||||
traces.filterToFetchData,
|
||||
traces.spansAggregate.currentPage,
|
||||
selectedTags,
|
||||
traces.filter,
|
||||
traces.isFilterExclude,
|
||||
traces.userSelectedFilter,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Container ref={tagRef}>
|
||||
<SearchComponent
|
||||
onChange={(event) => onChangeHandler(event.target.value)}
|
||||
value={value}
|
||||
allowClear
|
||||
disabled={traces.filterLoading}
|
||||
onFocus={onFocusHandler}
|
||||
placeholder="Click to filter by tags"
|
||||
type={'search'}
|
||||
enterButton={<CaretRightFilled />}
|
||||
onSearch={(string) => {
|
||||
if (string.length === 0) {
|
||||
updateTagVisiblity(false);
|
||||
updateFilters([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const { isError, payload } = parseQueryToTags(string);
|
||||
|
||||
if (isError) {
|
||||
updateTagIsError(true);
|
||||
} else {
|
||||
updateTagIsError(false);
|
||||
updateTagVisiblity(false);
|
||||
updateFilters(payload);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{traces.isTagModalOpen && (
|
||||
<Tags updateFilters={updateFilters} onChangeHandler={onChangeHandler} />
|
||||
)}
|
||||
</Container>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
updateTagVisiblity: (value: boolean) => void;
|
||||
updateTagIsError: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateTagVisiblity: bindActionCreators(UpdateTagVisiblity, dispatch),
|
||||
updateTagIsError: bindActionCreators(UpdateTagIsError, dispatch),
|
||||
});
|
||||
|
||||
type SearchProps = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Search);
|
17
frontend/src/container/Trace/Search/styles.ts
Normal file
17
frontend/src/container/Trace/Search/styles.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import styled from 'styled-components';
|
||||
import { Input } from 'antd';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const SearchComponent = styled(Search)`
|
||||
.ant-btn-primary {
|
||||
svg {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
`;
|
84
frontend/src/container/Trace/Search/util.ts
Normal file
84
frontend/src/container/Trace/Search/util.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
type Tags = TraceReducer['selectedTags'];
|
||||
|
||||
interface PayloadProps<T> {
|
||||
isError: boolean;
|
||||
payload: T;
|
||||
}
|
||||
|
||||
export const parseQueryToTags = (query: string): PayloadProps<Tags> => {
|
||||
let isError = false;
|
||||
|
||||
const noOfTags = query.split(' AND ');
|
||||
|
||||
const tags: Tags = noOfTags.map((filter) => {
|
||||
const isInPresent = filter.includes('IN');
|
||||
const isNotInPresent = filter.includes('NOT_IN');
|
||||
|
||||
if (!isNotInPresent || !isInPresent) {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
const splitBy = isNotInPresent ? 'NOT_IN' : isInPresent ? 'IN' : '';
|
||||
|
||||
if (splitBy.length === 0) {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
const filteredtags = filter.split(splitBy).map((e) => e.trim());
|
||||
|
||||
if (filteredtags.length !== 2) {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
const filterForTags = filteredtags[1];
|
||||
|
||||
if (!filterForTags) {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
const removingFirstAndLastBrackets = `${filterForTags?.slice(1, -1)}`;
|
||||
|
||||
const noofFilters = removingFirstAndLastBrackets.split(',');
|
||||
|
||||
noofFilters.forEach((e) => {
|
||||
const firstChar = e.charAt(0);
|
||||
const lastChar = e.charAt(e.length - 1);
|
||||
|
||||
if (!(firstChar === '"' && lastChar === '"')) {
|
||||
isError = true;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
Key: [filteredtags[0]],
|
||||
Values: noofFilters,
|
||||
Operator: splitBy as FlatArray<Tags, 1>['Operator'],
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
isError,
|
||||
payload: tags,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseTagsToQuery = (tags: Tags): PayloadProps<string> => {
|
||||
let isError = false;
|
||||
|
||||
const payload = tags
|
||||
.map(({ Values, Key, Operator }) => {
|
||||
if (Key[0] === undefined) {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
return `${Key[0]} ${Operator} (${Values.map((e) => `"${e}"`).join(',')})`;
|
||||
})
|
||||
.join(' AND ');
|
||||
|
||||
return {
|
||||
isError,
|
||||
payload,
|
||||
};
|
||||
};
|
91
frontend/src/container/Trace/TraceGraphFilter/config.ts
Normal file
91
frontend/src/container/Trace/TraceGraphFilter/config.ts
Normal file
@ -0,0 +1,91 @@
|
||||
interface Dropdown {
|
||||
key: string;
|
||||
displayValue: string;
|
||||
}
|
||||
|
||||
export const groupBy: Dropdown[] = [
|
||||
{
|
||||
key: '',
|
||||
displayValue: 'None',
|
||||
},
|
||||
{
|
||||
key: 'serviceName',
|
||||
displayValue: 'Service Name',
|
||||
},
|
||||
|
||||
{
|
||||
displayValue: 'Operation',
|
||||
key: 'operation',
|
||||
},
|
||||
{
|
||||
displayValue: 'HTTP url',
|
||||
key: 'httpUrl',
|
||||
},
|
||||
{
|
||||
displayValue: 'HTTP method',
|
||||
key: 'httpMethod',
|
||||
},
|
||||
{
|
||||
displayValue: 'HTTP host',
|
||||
key: 'httpHost',
|
||||
},
|
||||
{
|
||||
displayValue: 'HTTP route',
|
||||
key: 'httpRoute',
|
||||
},
|
||||
{
|
||||
displayValue: 'HTTP status code',
|
||||
key: 'httpCode',
|
||||
},
|
||||
{
|
||||
displayValue: 'Database name',
|
||||
key: 'dbName',
|
||||
},
|
||||
{
|
||||
displayValue: 'Database operation',
|
||||
key: 'dbSystem',
|
||||
},
|
||||
{
|
||||
displayValue: 'Messaging System',
|
||||
key: 'msgSystem',
|
||||
},
|
||||
{
|
||||
displayValue: 'Messaging Operation',
|
||||
key: 'msgOperation',
|
||||
},
|
||||
{
|
||||
displayValue: 'Component',
|
||||
key: 'component',
|
||||
},
|
||||
];
|
||||
|
||||
export const functions: Dropdown[] = [
|
||||
{ displayValue: 'Count', key: 'count' },
|
||||
{ displayValue: 'Rate per sec', key: 'ratePerSec' },
|
||||
{ displayValue: 'Sum(duration in ns)', key: 'sum' },
|
||||
{ displayValue: 'Avg(duration in ns)', key: 'avg' },
|
||||
{
|
||||
displayValue: 'Max(duration in ns)',
|
||||
key: 'max',
|
||||
},
|
||||
{
|
||||
displayValue: 'Min(duration in ns)',
|
||||
key: 'min',
|
||||
},
|
||||
{
|
||||
displayValue: '50th percentile(duration in ns)',
|
||||
key: 'p50',
|
||||
},
|
||||
{
|
||||
displayValue: '90th percentile(duration in ns',
|
||||
key: 'p90',
|
||||
},
|
||||
{
|
||||
displayValue: '95th percentile(duration in ns)',
|
||||
key: 'p95',
|
||||
},
|
||||
{
|
||||
displayValue: '99th percentile(duration in ns)',
|
||||
key: 'p99',
|
||||
},
|
||||
];
|
85
frontend/src/container/Trace/TraceGraphFilter/index.tsx
Normal file
85
frontend/src/container/Trace/TraceGraphFilter/index.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import { Space, SelectProps } from 'antd';
|
||||
import { functions, groupBy } from './config';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
UPDATE_SELECTED_FUNCTION,
|
||||
UPDATE_SELECTED_GROUP_BY,
|
||||
} from 'types/actions/trace';
|
||||
import { Dispatch } from 'redux';
|
||||
import { SelectComponent } from './styles';
|
||||
import { SelectValue } from 'antd/lib/select';
|
||||
|
||||
const { Option } = SelectComponent;
|
||||
|
||||
const TraceGraphFilter = () => {
|
||||
const { selectedFunction, selectedGroupBy } = useSelector<
|
||||
AppState,
|
||||
TraceReducer
|
||||
>((state) => state.traces);
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const onClickSelectedFunctionHandler: SelectProps<SelectValue>['onChange'] = (
|
||||
ev,
|
||||
) => {
|
||||
const selected = functions.find((e) => e.key === ev);
|
||||
if (selected) {
|
||||
dispatch({
|
||||
type: UPDATE_SELECTED_FUNCTION,
|
||||
payload: {
|
||||
selectedFunction: selected.key,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onClickSelectedGroupByHandler: SelectProps<SelectValue>['onChange'] = (
|
||||
ev,
|
||||
) => {
|
||||
const selected = groupBy.find((e) => e.key === ev);
|
||||
if (selected) {
|
||||
dispatch({
|
||||
type: UPDATE_SELECTED_GROUP_BY,
|
||||
payload: {
|
||||
selectedGroupBy: selected.key,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<label>Function</label>
|
||||
|
||||
<SelectComponent
|
||||
dropdownMatchSelectWidth
|
||||
value={functions.find((e) => selectedFunction === e.key)?.displayValue}
|
||||
onChange={onClickSelectedFunctionHandler}
|
||||
>
|
||||
{functions.map((value) => (
|
||||
<Option value={value.key} key={value.key}>
|
||||
{value.displayValue}
|
||||
</Option>
|
||||
))}
|
||||
</SelectComponent>
|
||||
|
||||
<label>Group By</label>
|
||||
<SelectComponent
|
||||
dropdownMatchSelectWidth
|
||||
value={groupBy.find((e) => selectedGroupBy === e.key)?.displayValue}
|
||||
onChange={onClickSelectedGroupByHandler}
|
||||
>
|
||||
{groupBy.map((value) => (
|
||||
<Option value={value.key} key={value.key}>
|
||||
{value.displayValue}
|
||||
</Option>
|
||||
))}
|
||||
</SelectComponent>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export default TraceGraphFilter;
|
9
frontend/src/container/Trace/TraceGraphFilter/styles.ts
Normal file
9
frontend/src/container/Trace/TraceGraphFilter/styles.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Select } from 'antd';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const SelectComponent = styled(Select)`
|
||||
&&& {
|
||||
min-width: 10rem;
|
||||
}
|
||||
`;
|
155
frontend/src/container/Trace/TraceTable/index.tsx
Normal file
155
frontend/src/container/Trace/TraceTable/index.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
import React from 'react';
|
||||
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import { TableProps, Tag } from 'antd';
|
||||
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
GetSpansAggregate,
|
||||
GetSpansAggregateProps,
|
||||
} from 'store/actions/trace/getInitialSpansAggregate';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import dayjs from 'dayjs';
|
||||
import duration from 'dayjs/plugin/duration';
|
||||
import history from 'lib/history';
|
||||
import ROUTES from 'constants/routes';
|
||||
dayjs.extend(duration);
|
||||
|
||||
const TraceTable = ({ getSpansAggregate }: TraceProps) => {
|
||||
const {
|
||||
spansAggregate,
|
||||
selectedFilter,
|
||||
selectedTags,
|
||||
filterLoading,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const { loading, total } = spansAggregate;
|
||||
|
||||
type TableType = FlatArray<TraceReducer['spansAggregate']['data'], 1>;
|
||||
|
||||
const columns: ColumnsType<TableType> = [
|
||||
{
|
||||
title: 'Date',
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
render: (value: TableType['timestamp']) => {
|
||||
const day = dayjs(value);
|
||||
return <div>{day.format('DD/MM/YYYY HH:MM:ss A')}</div>;
|
||||
},
|
||||
sorter: (a, b) => dayjs(a.timestamp).diff(dayjs(b.timestamp)),
|
||||
},
|
||||
{
|
||||
title: 'Service',
|
||||
dataIndex: 'serviceName',
|
||||
key: 'serviceName',
|
||||
sorter: (a, b) => a.serviceName.length - b.serviceName.length,
|
||||
},
|
||||
{
|
||||
title: 'Operation',
|
||||
dataIndex: 'operation',
|
||||
key: 'operation',
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
dataIndex: 'durationNano',
|
||||
key: 'durationNano',
|
||||
sorter: (a, b) => a.durationNano - b.durationNano,
|
||||
render: (value: TableType['durationNano']) => {
|
||||
return (
|
||||
<div>
|
||||
{`${dayjs
|
||||
.duration({ milliseconds: value / 1000000 })
|
||||
.asMilliseconds()} ms`}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Method',
|
||||
dataIndex: 'httpMethod',
|
||||
key: 'httpMethod',
|
||||
render: (value: TableType['httpMethod']) => {
|
||||
if (value.length === 0) {
|
||||
return <div>-</div>;
|
||||
}
|
||||
return <Tag color="magenta">{value}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Status Code',
|
||||
dataIndex: 'httpCode',
|
||||
key: 'httpCode',
|
||||
sorter: (a, b) => a.httpCode.length - b.httpCode.length,
|
||||
render: (value: TableType['httpCode']) => {
|
||||
if (value.length === 0) {
|
||||
return <div>-</div>;
|
||||
}
|
||||
return <Tag color="magenta">{value}</Tag>;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const onChangeHandler: TableProps<TableType>['onChange'] = (props) => {
|
||||
if (props.current && props.pageSize) {
|
||||
getSpansAggregate({
|
||||
maxTime: globalTime.maxTime,
|
||||
minTime: globalTime.minTime,
|
||||
selectedFilter,
|
||||
current: props.current,
|
||||
pageSize: props.pageSize,
|
||||
selectedTags,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Table
|
||||
onChange={onChangeHandler}
|
||||
dataSource={spansAggregate.data}
|
||||
loading={loading || filterLoading}
|
||||
columns={columns}
|
||||
onRow={(record) => ({
|
||||
onClick: () => {
|
||||
history.push({
|
||||
pathname: ROUTES.TRACE + '/' + record.traceID,
|
||||
state: {
|
||||
spanId: record.spanID,
|
||||
},
|
||||
});
|
||||
},
|
||||
})}
|
||||
size="middle"
|
||||
rowKey={'timestamp'}
|
||||
pagination={{
|
||||
current: spansAggregate.currentPage,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
responsive: true,
|
||||
position: ['bottomLeft'],
|
||||
total: total,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
getSpansAggregate: (props: GetSpansAggregateProps) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getSpansAggregate: bindActionCreators(GetSpansAggregate, dispatch),
|
||||
});
|
||||
|
||||
type TraceProps = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TraceTable);
|
@ -1,44 +0,0 @@
|
||||
import Graph from 'components/Graph';
|
||||
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
import React, { memo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { CustomGraphContainer } from './styles';
|
||||
|
||||
const TraceCustomGraph = ({
|
||||
spansAggregate,
|
||||
}: TraceCustomGraphProps): JSX.Element => {
|
||||
const { selectedEntity } = useSelector<AppState, TraceReducer>(
|
||||
(state) => state.trace,
|
||||
);
|
||||
|
||||
return (
|
||||
<CustomGraphContainer>
|
||||
<Graph
|
||||
type="line"
|
||||
data={{
|
||||
labels: spansAggregate.map((s) => new Date(s.timestamp / 1000000)),
|
||||
datasets: [
|
||||
{
|
||||
data: spansAggregate.map((e) =>
|
||||
selectedEntity === 'duration'
|
||||
? parseFloat(convertToNanoSecondsToSecond(e.value))
|
||||
: e.value,
|
||||
),
|
||||
borderColor: colors[0],
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</CustomGraphContainer>
|
||||
);
|
||||
};
|
||||
|
||||
interface TraceCustomGraphProps {
|
||||
spansAggregate: TraceReducer['spansAggregate'];
|
||||
}
|
||||
|
||||
export default memo(TraceCustomGraph);
|
@ -1,56 +0,0 @@
|
||||
export const entity = [
|
||||
{
|
||||
title: 'Calls',
|
||||
key: 'calls',
|
||||
dataindex: 'calls',
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
key: 'duration',
|
||||
dataindex: 'duration',
|
||||
},
|
||||
{
|
||||
title: 'Error',
|
||||
key: 'error',
|
||||
dataindex: 'error',
|
||||
},
|
||||
{
|
||||
title: 'Status Code',
|
||||
key: 'status_code',
|
||||
dataindex: 'status_code',
|
||||
},
|
||||
];
|
||||
|
||||
export const aggregation_options = [
|
||||
{
|
||||
linked_entity: 'calls',
|
||||
default_selected: { title: 'count', dataindex: 'count' },
|
||||
options_available: [
|
||||
{ title: 'Count', dataindex: 'count' },
|
||||
{ title: 'Rate (per sec)', dataindex: 'rate_per_sec' },
|
||||
],
|
||||
},
|
||||
{
|
||||
linked_entity: 'duration',
|
||||
default_selected: { title: 'p99', dataindex: 'p99' },
|
||||
// options_available: [ {title:'Avg', dataindex:'avg'}, {title:'Max', dataindex:'max'},{title:'Min', dataindex:'min'}, {title:'p50', dataindex:'p50'},{title:'p95', dataindex:'p95'}, {title:'p95', dataindex:'p95'}]
|
||||
options_available: [
|
||||
{ title: 'p50', dataindex: 'p50' },
|
||||
{ title: 'p95', dataindex: 'p95' },
|
||||
{ title: 'p99', dataindex: 'p99' },
|
||||
],
|
||||
},
|
||||
{
|
||||
linked_entity: 'error',
|
||||
default_selected: { title: 'count', dataindex: 'count' },
|
||||
options_available: [
|
||||
{ title: 'count', dataindex: 'count' },
|
||||
{ title: 'Rate (per sec)', dataindex: 'rate_per_sec' },
|
||||
],
|
||||
},
|
||||
{
|
||||
linked_entity: 'status_code',
|
||||
default_selected: { title: 'count', dataindex: 'count' },
|
||||
options_available: [{ title: 'count', dataindex: 'count' }],
|
||||
},
|
||||
];
|
@ -1,127 +0,0 @@
|
||||
import { Form, Select } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import React from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
const { Option } = Select;
|
||||
import { Store } from 'rc-field-form/lib/interface';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import {
|
||||
GetTraceVisualAggregates,
|
||||
GetTraceVisualAggregatesProps,
|
||||
} from 'store/actions/trace/getTraceVisualAgrregates';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { aggregation_options, entity } from './config';
|
||||
import { Card, CustomVisualizationsTitle, FormItem, Space } from './styles';
|
||||
import TraceCustomGraph from './TraceCustomGraph';
|
||||
|
||||
const TraceCustomVisualisation = ({
|
||||
getTraceVisualAggregates,
|
||||
}: TraceCustomVisualisationProps): JSX.Element => {
|
||||
const {
|
||||
selectedEntity,
|
||||
spansLoading,
|
||||
selectedAggOption,
|
||||
spansAggregate,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.trace);
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
if (spansLoading) {
|
||||
return <Spinner tip="Loading..." height="40vh" />;
|
||||
}
|
||||
|
||||
const handleFormValuesChange = (changedValues: Store): void => {
|
||||
const formFieldName = Object.keys(changedValues)[0];
|
||||
if (formFieldName === 'entity') {
|
||||
const temp_entity = aggregation_options.filter(
|
||||
(item) => item.linked_entity === changedValues[formFieldName],
|
||||
)[0];
|
||||
|
||||
form.setFieldsValue({
|
||||
agg_options: temp_entity.default_selected.title,
|
||||
});
|
||||
|
||||
const values = form.getFieldsValue(['agg_options', 'entity']);
|
||||
|
||||
getTraceVisualAggregates({
|
||||
selectedAggOption: values.agg_options,
|
||||
selectedEntity: values.entity,
|
||||
});
|
||||
}
|
||||
|
||||
if (formFieldName === 'agg_options') {
|
||||
getTraceVisualAggregates({
|
||||
selectedAggOption: changedValues[formFieldName],
|
||||
selectedEntity,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CustomVisualizationsTitle>Custom Visualizations</CustomVisualizationsTitle>
|
||||
<Form
|
||||
form={form}
|
||||
onValuesChange={handleFormValuesChange}
|
||||
initialValues={{
|
||||
entity: selectedEntity,
|
||||
agg_options: selectedAggOption,
|
||||
chart_style: 'line',
|
||||
interval: '5m',
|
||||
group_by: 'none',
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<FormItem name="entity">
|
||||
<Select style={{ width: 120 }} allowClear>
|
||||
{entity.map((item) => (
|
||||
<Option key={item.key} value={item.dataindex}>
|
||||
{item.title}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="agg_options">
|
||||
<Select style={{ width: 120 }} allowClear>
|
||||
{aggregation_options
|
||||
.filter((item) => item.linked_entity === selectedEntity)[0]
|
||||
.options_available.map((item) => (
|
||||
<Option key={item.dataindex} value={item.dataindex}>
|
||||
{item.title}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Space>
|
||||
</Form>
|
||||
|
||||
<TraceCustomGraph
|
||||
{...{
|
||||
spansAggregate,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
getTraceVisualAggregates: (props: GetTraceVisualAggregatesProps) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getTraceVisualAggregates: bindActionCreators(
|
||||
GetTraceVisualAggregates,
|
||||
dispatch,
|
||||
),
|
||||
});
|
||||
|
||||
type TraceCustomVisualisationProps = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TraceCustomVisualisation);
|
@ -1,34 +0,0 @@
|
||||
import {
|
||||
Card as CardComponent,
|
||||
Form,
|
||||
Space as SpaceComponent,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const CustomGraphContainer = styled.div`
|
||||
height: 30vh;
|
||||
`;
|
||||
|
||||
export const Card = styled(CardComponent)`
|
||||
.ant-card-body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CustomVisualizationsTitle = styled(Typography)`
|
||||
margin-bottom: 1rem;
|
||||
`;
|
||||
|
||||
export const FormItem = styled(Form.Item)`
|
||||
&&& {
|
||||
margin: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Space = styled(SpaceComponent)`
|
||||
&&& {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
`;
|
@ -1,182 +0,0 @@
|
||||
import { Tag } from 'antd';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import React from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { TagItem } from 'store/actions';
|
||||
import { UpdateSelectedTags } from 'store/actions/trace';
|
||||
import {
|
||||
UpdateSelectedData,
|
||||
UpdateSelectedDataProps,
|
||||
} from 'store/actions/trace/updateSelectedData';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { Card } from './styles';
|
||||
|
||||
const Filter = ({
|
||||
updatedQueryParams,
|
||||
updateSelectedData,
|
||||
updateSelectedTags,
|
||||
}: FilterProps): JSX.Element => {
|
||||
const {
|
||||
selectedService,
|
||||
selectedOperation,
|
||||
selectedLatency,
|
||||
selectedTags,
|
||||
selectedKind,
|
||||
selectedEntity,
|
||||
selectedAggOption,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.trace);
|
||||
|
||||
function handleCloseTag(value: string): void {
|
||||
if (value === 'service') {
|
||||
updatedQueryParams([''], [METRICS_PAGE_QUERY_PARAM.service]);
|
||||
updateSelectedData({
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
selectedOperation,
|
||||
selectedService: '',
|
||||
});
|
||||
}
|
||||
if (value === 'operation') {
|
||||
updatedQueryParams([''], [METRICS_PAGE_QUERY_PARAM.operation]);
|
||||
updateSelectedData({
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
selectedOperation: '',
|
||||
selectedService,
|
||||
});
|
||||
}
|
||||
if (value === 'maxLatency') {
|
||||
updatedQueryParams([''], [METRICS_PAGE_QUERY_PARAM.latencyMax]);
|
||||
updateSelectedData({
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
selectedKind,
|
||||
selectedLatency: {
|
||||
min: selectedLatency.min,
|
||||
max: '',
|
||||
},
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
});
|
||||
}
|
||||
if (value === 'minLatency') {
|
||||
updatedQueryParams([''], [METRICS_PAGE_QUERY_PARAM.latencyMin]);
|
||||
updateSelectedData({
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
selectedKind,
|
||||
selectedLatency: {
|
||||
min: '',
|
||||
max: selectedLatency.max,
|
||||
},
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleCloseTagElement(item: TagItem): void {
|
||||
const updatedSelectedtags = selectedTags.filter((e) => e.key !== item.key);
|
||||
|
||||
updatedQueryParams(
|
||||
[updatedSelectedtags],
|
||||
[METRICS_PAGE_QUERY_PARAM.selectedTags],
|
||||
);
|
||||
updateSelectedTags(updatedSelectedtags);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
{selectedService.length !== 0 && (
|
||||
<Tag
|
||||
closable
|
||||
onClose={(e): void => {
|
||||
e.preventDefault();
|
||||
handleCloseTag('service');
|
||||
}}
|
||||
>
|
||||
service:{selectedService}
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{selectedOperation.length !== 0 && (
|
||||
<Tag
|
||||
closable
|
||||
onClose={(e): void => {
|
||||
e.preventDefault();
|
||||
handleCloseTag('operation');
|
||||
}}
|
||||
>
|
||||
operation:{selectedOperation}
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{selectedLatency?.min.length !== 0 && (
|
||||
<Tag
|
||||
closable
|
||||
onClose={(e): void => {
|
||||
e.preventDefault();
|
||||
handleCloseTag('minLatency');
|
||||
}}
|
||||
>
|
||||
minLatency:
|
||||
{(parseInt(selectedLatency?.min || '0') / 1000000).toString()}ms
|
||||
</Tag>
|
||||
)}
|
||||
{selectedLatency?.max.length !== 0 && (
|
||||
<Tag
|
||||
closable
|
||||
onClose={(e): void => {
|
||||
e.preventDefault();
|
||||
handleCloseTag('maxLatency');
|
||||
}}
|
||||
>
|
||||
maxLatency:
|
||||
{(parseInt(selectedLatency?.max || '0') / 1000000).toString()}ms
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{selectedTags.map((item) => (
|
||||
<Tag
|
||||
closable
|
||||
key={`${item.key}-${item.operator}-${item.value}`}
|
||||
onClose={(e): void => {
|
||||
e.preventDefault();
|
||||
handleCloseTagElement(item);
|
||||
}}
|
||||
>
|
||||
{item.key} {item.operator} {item.value}
|
||||
</Tag>
|
||||
))}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
updateSelectedTags: (
|
||||
selectedTags: TraceReducer['selectedTags'],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
updateSelectedData: (props: UpdateSelectedDataProps) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateSelectedTags: bindActionCreators(UpdateSelectedTags, dispatch),
|
||||
updateSelectedData: bindActionCreators(UpdateSelectedData, dispatch),
|
||||
});
|
||||
|
||||
interface FilterProps extends DispatchProps {
|
||||
updatedQueryParams: (updatedValue: string[], key: string[]) => void;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Filter);
|
@ -1,160 +0,0 @@
|
||||
import { Col, Form, InputNumber, Modal, notification, Row } from 'antd';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import { FormInstance, RuleObject } from 'rc-field-form/lib/interface';
|
||||
import React from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { UpdateSelectedLatency } from 'store/actions/trace';
|
||||
import {
|
||||
UpdateSelectedData,
|
||||
UpdateSelectedDataProps,
|
||||
} from 'store/actions/trace/updateSelectedData';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
const LatencyForm = ({
|
||||
onCancel,
|
||||
visible,
|
||||
updateSelectedLatency,
|
||||
onLatencyButtonClick,
|
||||
updatedQueryParams,
|
||||
updateSelectedData,
|
||||
}: LatencyModalFormProps): JSX.Element => {
|
||||
const [form] = Form.useForm();
|
||||
const [notifications, Element] = notification.useNotification();
|
||||
const {
|
||||
selectedLatency,
|
||||
selectedKind,
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.trace);
|
||||
|
||||
const validateMinValue = (form: FormInstance): RuleObject => ({
|
||||
validator(_: RuleObject, value): Promise<void> {
|
||||
const { getFieldValue } = form;
|
||||
const minValue = getFieldValue('min');
|
||||
const maxValue = getFieldValue('max');
|
||||
|
||||
if (value <= maxValue && value >= minValue) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('Min value should be less than Max value'));
|
||||
},
|
||||
});
|
||||
|
||||
const validateMaxValue = (form: FormInstance): RuleObject => ({
|
||||
validator(_, value): Promise<void> {
|
||||
const { getFieldValue } = form;
|
||||
|
||||
const minValue = getFieldValue('min');
|
||||
const maxValue = getFieldValue('max');
|
||||
|
||||
if (value >= minValue && value <= maxValue) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
new Error('Max value should be greater than Min value'),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const onOkHandler = (): void => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
const maxValue = (values.max * 1000000).toString();
|
||||
const minValue = (values.min * 1000000).toString();
|
||||
|
||||
onLatencyButtonClick();
|
||||
updatedQueryParams(
|
||||
[maxValue, minValue],
|
||||
[METRICS_PAGE_QUERY_PARAM.latencyMax, METRICS_PAGE_QUERY_PARAM.latencyMin],
|
||||
);
|
||||
updateSelectedLatency({
|
||||
max: maxValue,
|
||||
min: minValue,
|
||||
});
|
||||
updateSelectedData({
|
||||
selectedKind,
|
||||
selectedLatency: {
|
||||
max: maxValue,
|
||||
min: minValue,
|
||||
},
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
});
|
||||
})
|
||||
.catch((info) => {
|
||||
notifications.error({
|
||||
message: info.toString(),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{Element}
|
||||
|
||||
<Modal
|
||||
title="Chose min and max values of Latency"
|
||||
okText="Apply"
|
||||
cancelText="Cancel"
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={onOkHandler}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{
|
||||
min: parseInt(selectedLatency.min, 10) / 1000000,
|
||||
max: parseInt(selectedLatency.max, 10) / 1000000,
|
||||
}}
|
||||
>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Form.Item name="min" label="Min (in ms)" rules={[validateMinValue]}>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="max" label="Max (in ms)" rules={[validateMaxValue]}>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
updateSelectedLatency: (
|
||||
selectedLatency: TraceReducer['selectedLatency'],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
updateSelectedData: (props: UpdateSelectedDataProps) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateSelectedLatency: bindActionCreators(UpdateSelectedLatency, dispatch),
|
||||
updateSelectedData: bindActionCreators(UpdateSelectedData, dispatch),
|
||||
});
|
||||
|
||||
interface LatencyModalFormProps extends DispatchProps {
|
||||
onCancel: () => void;
|
||||
visible: boolean;
|
||||
onLatencyButtonClick: () => void;
|
||||
updatedQueryParams: (updatedValue: string[], value: string[]) => void;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(LatencyForm);
|
@ -1,15 +0,0 @@
|
||||
interface SpanKindList {
|
||||
label: 'SERVER' | 'CLIENT';
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const spanKindList: SpanKindList[] = [
|
||||
{
|
||||
label: 'SERVER',
|
||||
value: '2',
|
||||
},
|
||||
{
|
||||
label: 'CLIENT',
|
||||
value: '3',
|
||||
},
|
||||
];
|
@ -1,390 +0,0 @@
|
||||
import { Button, Input, notification, Typography } from 'antd';
|
||||
import { SelectValue } from 'antd/lib/select';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TagItem, TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import { spanKindList } from './config';
|
||||
import Filter from './Filter';
|
||||
import LatencyForm from './LatencyForm';
|
||||
import { AutoComplete, Form, InfoWrapper, Select } from './styles';
|
||||
const { Option } = Select;
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import createQueryParams from 'lib/createQueryParams';
|
||||
import history from 'lib/history';
|
||||
import { useLocation } from 'react-router';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { UpdateSelectedTags } from 'store/actions/trace';
|
||||
import {
|
||||
UpdateSelectedData,
|
||||
UpdateSelectedDataProps,
|
||||
} from 'store/actions/trace/updateSelectedData';
|
||||
import AppActions from 'types/actions';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const TraceList = ({
|
||||
updateSelectedTags,
|
||||
updateSelectedData,
|
||||
}: TraceListProps): JSX.Element => {
|
||||
const [
|
||||
notificationInstance,
|
||||
NotificationElement,
|
||||
] = notification.useNotification();
|
||||
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
const [form] = Form.useForm();
|
||||
const [form_basefilter] = Form.useForm();
|
||||
|
||||
const { search } = useLocation();
|
||||
|
||||
const params = new URLSearchParams(search);
|
||||
|
||||
const onLatencyButtonClick = useCallback(() => {
|
||||
setVisible((visible) => !visible);
|
||||
}, []);
|
||||
|
||||
const {
|
||||
operationsList,
|
||||
serviceList,
|
||||
tagsSuggestions,
|
||||
selectedTags,
|
||||
selectedService,
|
||||
selectedOperation,
|
||||
selectedLatency,
|
||||
selectedKind,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.trace);
|
||||
|
||||
const paramsInObject = (params: URLSearchParams): { [x: string]: string } => {
|
||||
const updatedParamas: { [x: string]: string } = {};
|
||||
params.forEach((value, key) => {
|
||||
updatedParamas[key] = value;
|
||||
});
|
||||
return updatedParamas;
|
||||
};
|
||||
|
||||
const updatedQueryParams = (updatedValue: string[], key: string[]): void => {
|
||||
const updatedParams = paramsInObject(params);
|
||||
|
||||
updatedValue.forEach((_, index) => {
|
||||
updatedParams[key[index]] = updatedValue[index];
|
||||
});
|
||||
|
||||
const queryParams = createQueryParams(updatedParams);
|
||||
history.push(ROUTES.TRACE + `?${queryParams}`);
|
||||
};
|
||||
|
||||
const getUpdatedSelectedData = (props: UpdateSelectedDataProps): void => {
|
||||
const {
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
} = props;
|
||||
|
||||
updateSelectedData({
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
});
|
||||
};
|
||||
|
||||
const onTagSubmitTagHandler = (values: Item): void => {
|
||||
if (values.tag_key.length === 0 || values.tag_value.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check whether it is pre-existing in the array or not
|
||||
|
||||
const isFound = selectedTags.find((tags) => {
|
||||
return (
|
||||
tags.key === values.tag_key &&
|
||||
tags.value === values.tag_value &&
|
||||
tags.operator === values.operator
|
||||
);
|
||||
});
|
||||
|
||||
if (!isFound) {
|
||||
const preSelectedTags = [
|
||||
...selectedTags,
|
||||
{
|
||||
operator: values.operator,
|
||||
key: values.tag_key,
|
||||
value: values.tag_value,
|
||||
},
|
||||
];
|
||||
|
||||
updatedQueryParams(
|
||||
[JSON.stringify(preSelectedTags)],
|
||||
[METRICS_PAGE_QUERY_PARAM.selectedTags],
|
||||
);
|
||||
|
||||
updateSelectedTags(preSelectedTags);
|
||||
} else {
|
||||
notificationInstance.error({
|
||||
message: 'Tag Already Present',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onChangeTagKey = (data: string): void => {
|
||||
form.setFieldsValue({ tag_key: data });
|
||||
};
|
||||
|
||||
const updateSelectedServiceHandler = (value: string): void => {
|
||||
updatedQueryParams([value], [METRICS_PAGE_QUERY_PARAM.service]);
|
||||
getUpdatedSelectedData({
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
selectedOperation,
|
||||
selectedService: value,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
});
|
||||
};
|
||||
|
||||
const updateSelectedOperationHandler = (value: string): void => {
|
||||
updatedQueryParams([value], [METRICS_PAGE_QUERY_PARAM.operation]);
|
||||
getUpdatedSelectedData({
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
selectedOperation: value,
|
||||
selectedService,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
});
|
||||
};
|
||||
|
||||
const updateSelectedKindHandler = (value: string): void => {
|
||||
updatedQueryParams([value], [METRICS_PAGE_QUERY_PARAM.kind]);
|
||||
getUpdatedSelectedData({
|
||||
selectedKind: value,
|
||||
selectedLatency,
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedService.length !== 0) {
|
||||
form_basefilter.setFieldsValue({
|
||||
service: selectedService,
|
||||
});
|
||||
} else {
|
||||
form_basefilter.setFieldsValue({
|
||||
service: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedOperation.length !== 0) {
|
||||
form_basefilter.setFieldsValue({
|
||||
operation: selectedOperation,
|
||||
});
|
||||
} else {
|
||||
form_basefilter.setFieldsValue({
|
||||
operation: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedKind.length !== 0) {
|
||||
form_basefilter.setFieldsValue({
|
||||
spanKind: selectedKind,
|
||||
});
|
||||
} else {
|
||||
form_basefilter.setFieldsValue({
|
||||
spanKind: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedLatency.max.length === 0 && selectedLatency.min.length === 0) {
|
||||
form_basefilter.setFieldsValue({
|
||||
latency: 'Latency',
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedLatency.max.length !== 0 && selectedLatency.min.length === 0) {
|
||||
form_basefilter.setFieldsValue({
|
||||
latency: `Latency < Max Latency: ${
|
||||
parseInt(selectedLatency.max, 10) / 1000000
|
||||
} ms`,
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedLatency.max.length === 0 && selectedLatency.min.length !== 0) {
|
||||
form_basefilter.setFieldsValue({
|
||||
latency: `Min Latency: ${
|
||||
parseInt(selectedLatency.min, 10) / 1000000
|
||||
} ms < Latency`,
|
||||
});
|
||||
}
|
||||
|
||||
if (selectedLatency.max.length !== 0 && selectedLatency.min.length !== 0) {
|
||||
form_basefilter.setFieldsValue({
|
||||
latency: `Min Latency: ${
|
||||
parseInt(selectedLatency.min, 10) / 1000000
|
||||
} ms < Latency < Max Latency: ${
|
||||
parseInt(selectedLatency.min, 10) / 1000000
|
||||
} ms`,
|
||||
});
|
||||
}
|
||||
}, [
|
||||
selectedService,
|
||||
selectedOperation,
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
form_basefilter,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{NotificationElement}
|
||||
|
||||
<Typography>Filter Traces</Typography>
|
||||
<Form form={form_basefilter} layout="inline">
|
||||
<FormItem name="service">
|
||||
<Select
|
||||
showSearch
|
||||
onChange={(value: SelectValue): void => {
|
||||
updateSelectedServiceHandler(value?.toString() || '');
|
||||
}}
|
||||
placeholder="Select Service"
|
||||
allowClear
|
||||
>
|
||||
{serviceList.map((s) => (
|
||||
<Option key={s} value={s}>
|
||||
{s}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="operation">
|
||||
<Select
|
||||
showSearch
|
||||
onChange={(value: SelectValue): void => {
|
||||
updateSelectedOperationHandler(value?.toString() || '');
|
||||
}}
|
||||
placeholder="Select Operation"
|
||||
allowClear
|
||||
>
|
||||
{operationsList.map((item) => (
|
||||
<Option key={item} value={item}>
|
||||
{item}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="latency">
|
||||
<Input type="button" onClick={onLatencyButtonClick} />
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="spanKind">
|
||||
<Select
|
||||
showSearch
|
||||
onChange={(value: SelectValue): void => {
|
||||
updateSelectedKindHandler(value?.toString() || '');
|
||||
}}
|
||||
placeholder="Select Span Kind"
|
||||
allowClear
|
||||
>
|
||||
{spanKindList.map((spanKind) => (
|
||||
<Option value={spanKind.value} key={spanKind.value}>
|
||||
{spanKind.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
{(selectedTags.length !== 0 ||
|
||||
selectedService.length !== 0 ||
|
||||
selectedOperation.length !== 0 ||
|
||||
selectedLatency.max.length !== 0 ||
|
||||
selectedLatency.min.length !== 0) && (
|
||||
<Filter updatedQueryParams={updatedQueryParams} />
|
||||
)}
|
||||
|
||||
<InfoWrapper>Select Service to get Tag suggestions</InfoWrapper>
|
||||
<Form
|
||||
form={form}
|
||||
layout="inline"
|
||||
onFinish={onTagSubmitTagHandler}
|
||||
initialValues={{ operator: 'equals' }}
|
||||
>
|
||||
<FormItem name="tag_key">
|
||||
<AutoComplete
|
||||
options={tagsSuggestions.map((s) => {
|
||||
return { value: s.tagKeys };
|
||||
})}
|
||||
onChange={onChangeTagKey}
|
||||
filterOption={(inputValue, option): boolean =>
|
||||
option?.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
}
|
||||
placeholder="Tag Key"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="operator">
|
||||
<Select>
|
||||
<Option value="equals">EQUAL</Option>
|
||||
<Option value="contains">CONTAINS</Option>
|
||||
<Option value="regex">REGEX</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="tag_value">
|
||||
<Input placeholder="Tag Value" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button type="primary" htmlType="submit">
|
||||
Apply Tag Filter
|
||||
</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
<LatencyForm
|
||||
onCancel={(): void => {
|
||||
setVisible(false);
|
||||
}}
|
||||
updatedQueryParams={updatedQueryParams}
|
||||
visible={visible}
|
||||
onLatencyButtonClick={onLatencyButtonClick}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface Item {
|
||||
tag_key: string;
|
||||
tag_value: string;
|
||||
operator: TagItem['operator'];
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
updateSelectedTags: (
|
||||
selectedTags: TraceReducer['selectedTags'],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
updateSelectedData: (props: UpdateSelectedDataProps) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateSelectedTags: bindActionCreators(UpdateSelectedTags, dispatch),
|
||||
updateSelectedData: bindActionCreators(UpdateSelectedData, dispatch),
|
||||
});
|
||||
|
||||
type TraceListProps = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TraceList);
|
@ -1,34 +0,0 @@
|
||||
import {
|
||||
AutoComplete as AutoCompleteComponent,
|
||||
Card as CardComponent,
|
||||
Form as FormComponent,
|
||||
Select as SelectComponent,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const InfoWrapper = styled(Typography)`
|
||||
padding-top: 1rem;
|
||||
font-style: italic;
|
||||
font-size: 0.75rem;
|
||||
`;
|
||||
|
||||
export const Select = styled(SelectComponent)`
|
||||
min-width: 180px;
|
||||
`;
|
||||
|
||||
export const AutoComplete = styled(AutoCompleteComponent)`
|
||||
min-width: 180px;
|
||||
`;
|
||||
|
||||
export const Form = styled(FormComponent)`
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
export const Card = styled(CardComponent)`
|
||||
.ant-card-body {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
`;
|
@ -1,141 +0,0 @@
|
||||
import { Space, Table, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table/Table';
|
||||
import ROUTES from 'constants/routes';
|
||||
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
|
||||
import getFormattedDate from 'lib/getFormatedDate';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { pushDStree } from 'types/api/trace/getSpans';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { isOnboardingSkipped } from 'utils/app';
|
||||
|
||||
import { TitleContainer } from './styles';
|
||||
|
||||
const TraceDetails = (): JSX.Element => {
|
||||
const { spanList } = useSelector<AppState, TraceReducer>(
|
||||
(state) => state.trace,
|
||||
);
|
||||
|
||||
const spans: TableDataSourceItem[] = spanList[0]?.events?.map(
|
||||
(item: (number | string | string[] | pushDStree[])[], index) => {
|
||||
if (
|
||||
typeof item[0] === 'number' &&
|
||||
typeof item[4] === 'string' &&
|
||||
typeof item[6] === 'string' &&
|
||||
typeof item[1] === 'string' &&
|
||||
typeof item[2] === 'string' &&
|
||||
typeof item[3] === 'string'
|
||||
) {
|
||||
return {
|
||||
startTime: item[0],
|
||||
operationName: item[4],
|
||||
duration: parseInt(item[6]),
|
||||
spanid: item[1],
|
||||
traceid: item[2],
|
||||
key: index.toString(),
|
||||
service: item[3],
|
||||
};
|
||||
}
|
||||
return {
|
||||
duration: 0,
|
||||
key: '',
|
||||
operationName: '',
|
||||
service: '',
|
||||
spanid: '',
|
||||
startTime: 0,
|
||||
traceid: '',
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const columns: ColumnsType<TableDataSourceItem> = [
|
||||
{
|
||||
title: 'Start Time',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
sorter: (a, b): number => a.startTime - b.startTime,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number): string => {
|
||||
const date = new Date(value);
|
||||
const result = `${getFormattedDate(date)} ${convertDateToAmAndPm(date)}`;
|
||||
return result;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Service',
|
||||
dataIndex: 'service',
|
||||
key: 'service',
|
||||
},
|
||||
{
|
||||
title: 'Operation',
|
||||
dataIndex: 'operationName',
|
||||
key: 'operationName',
|
||||
},
|
||||
{
|
||||
title: 'Duration (in ms)',
|
||||
dataIndex: 'duration',
|
||||
key: 'duration',
|
||||
sorter: (a, b): number => a.duration - b.duration,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number): string => (value / 1000000).toFixed(2),
|
||||
},
|
||||
];
|
||||
|
||||
if (isOnboardingSkipped() && spans?.length === 0) {
|
||||
return (
|
||||
<Space style={{ width: '100%', margin: '40px 0', justifyContent: 'center' }}>
|
||||
No spans found. Please add instrumentation (follow this
|
||||
<a
|
||||
href={'https://signoz.io/docs/instrumentation/overview'}
|
||||
target={'_blank'}
|
||||
rel="noreferrer"
|
||||
>
|
||||
guide
|
||||
</a>
|
||||
)
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
if (spans?.length === 0) {
|
||||
return <Typography> No spans found for given filter!</Typography>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TitleContainer>List of filtered spans</TitleContainer>
|
||||
|
||||
<Table
|
||||
dataSource={spans}
|
||||
columns={columns}
|
||||
size="middle"
|
||||
onRow={(
|
||||
record: TableDataSourceItem,
|
||||
): React.HTMLAttributes<HTMLElement> => ({
|
||||
onClick: (): void => {
|
||||
history.push({
|
||||
pathname: ROUTES.TRACE + '/' + record.traceid,
|
||||
state: {
|
||||
spanId: record.spanid,
|
||||
},
|
||||
});
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface TableDataSourceItem {
|
||||
key: string;
|
||||
spanid: string;
|
||||
traceid: string;
|
||||
operationName: string;
|
||||
startTime: number;
|
||||
duration: number;
|
||||
service: string;
|
||||
}
|
||||
|
||||
export default TraceDetails;
|
@ -1,7 +0,0 @@
|
||||
import { Typography } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TitleContainer = styled(Typography)`
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
`;
|
26
frontend/src/hooks/useClickOutside.ts
Normal file
26
frontend/src/hooks/useClickOutside.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
const useClickOutside = (
|
||||
ref: React.RefObject<HTMLElement>,
|
||||
callback: (e: HTMLElement) => void | null,
|
||||
) => {
|
||||
const listener = (e: Event) => {
|
||||
const node = e?.target as HTMLElement;
|
||||
|
||||
if (ref.current && !ref.current.contains(node)) {
|
||||
if (callback) {
|
||||
callback(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('click', listener);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', listener);
|
||||
};
|
||||
}, [ref, callback]);
|
||||
};
|
||||
|
||||
export default useClickOutside;
|
34
frontend/src/hooks/useDebouncedFunction.ts
Normal file
34
frontend/src/hooks/useDebouncedFunction.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { useCallback } from 'react';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
|
||||
export interface DebouncedFunc<T extends (...args: any[]) => any> {
|
||||
(...args: Parameters<T>): ReturnType<T> | undefined;
|
||||
|
||||
cancel(): void;
|
||||
|
||||
flush(): ReturnType<T> | undefined;
|
||||
}
|
||||
|
||||
export type DebounceOptions = {
|
||||
leading?: boolean;
|
||||
maxWait?: number;
|
||||
trailing?: boolean;
|
||||
};
|
||||
|
||||
const defaultOptions: DebounceOptions = {
|
||||
leading: false,
|
||||
trailing: true,
|
||||
};
|
||||
|
||||
const useDebouncedFn = <T extends (...args: any) => any>(
|
||||
fn: T,
|
||||
wait: number = 100,
|
||||
options: DebounceOptions = defaultOptions,
|
||||
dependencies?: ReadonlyArray<any>,
|
||||
): DebouncedFunc<T> => {
|
||||
const debounced = debounce(fn, wait, options);
|
||||
|
||||
return useCallback(debounced, dependencies || []);
|
||||
};
|
||||
|
||||
export default useDebouncedFn;
|
15
frontend/src/lib/query/convertObjectIntoParams.ts
Normal file
15
frontend/src/lib/query/convertObjectIntoParams.ts
Normal file
@ -0,0 +1,15 @@
|
||||
const convertObjectIntoParams = (
|
||||
props: Record<any, any>,
|
||||
stringify = false,
|
||||
) => {
|
||||
return Object.keys(props)
|
||||
.map(
|
||||
(e) =>
|
||||
`${e}=${
|
||||
stringify ? encodeURIComponent(JSON.stringify(props[e])) : props[e]
|
||||
}`,
|
||||
)
|
||||
.join('&');
|
||||
};
|
||||
|
||||
export default convertObjectIntoParams;
|
157
frontend/src/pages/Trace/index.tsx
Normal file
157
frontend/src/pages/Trace/index.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import { Card } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import Filters from 'container/Trace/Filters';
|
||||
import TraceGraph from 'container/Trace/Graph';
|
||||
import Search from 'container/Trace/Search';
|
||||
import TraceGraphFilter from 'container/Trace/TraceGraphFilter';
|
||||
import TraceTable from 'container/Trace/TraceTable';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GetInitialTraceFilter } from 'store/actions/trace/getInitialFilter';
|
||||
import {
|
||||
GetSpansAggregate,
|
||||
GetSpansAggregateProps,
|
||||
} from 'store/actions/trace/getInitialSpansAggregate';
|
||||
import { GetSpans, GetSpansProps } from 'store/actions/trace/getSpans';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { RESET_TRACE_FILTER } from 'types/actions/trace';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
import {
|
||||
Container,
|
||||
LeftContainer,
|
||||
RightContainer,
|
||||
ClearAllFilter,
|
||||
} from './styles';
|
||||
|
||||
const Trace = ({
|
||||
getSpansAggregate,
|
||||
getSpans,
|
||||
getInitialFilter,
|
||||
}: Props): JSX.Element => {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const [isChanged, setIsChanged] = useState<boolean>(true);
|
||||
|
||||
const {
|
||||
selectedFilter,
|
||||
spansAggregate,
|
||||
selectedTags,
|
||||
selectedFunction,
|
||||
selectedGroupBy,
|
||||
isFilterExclude,
|
||||
} = useSelector<AppState, TraceReducer>((state) => state.traces);
|
||||
|
||||
useEffect(() => {
|
||||
getInitialFilter(minTime, maxTime);
|
||||
}, [maxTime, minTime, getInitialFilter, isChanged]);
|
||||
|
||||
useEffect(() => {
|
||||
getSpansAggregate({
|
||||
maxTime: maxTime,
|
||||
minTime: minTime,
|
||||
selectedFilter,
|
||||
current: spansAggregate.currentPage,
|
||||
pageSize: spansAggregate.pageSize,
|
||||
selectedTags,
|
||||
});
|
||||
}, [selectedTags, selectedFilter, maxTime, minTime]);
|
||||
|
||||
useEffect(() => {
|
||||
getSpans({
|
||||
end: maxTime,
|
||||
function: selectedFunction,
|
||||
groupBy: selectedGroupBy,
|
||||
selectedFilter,
|
||||
selectedTags,
|
||||
start: minTime,
|
||||
step: 60,
|
||||
isFilterExclude,
|
||||
});
|
||||
}, [
|
||||
selectedFunction,
|
||||
selectedGroupBy,
|
||||
selectedFilter,
|
||||
selectedTags,
|
||||
maxTime,
|
||||
minTime,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dispatch({
|
||||
type: RESET_TRACE_FILTER,
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onClickHandler = useCallback((e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
history.replace(ROUTES.TRACE);
|
||||
|
||||
dispatch({
|
||||
type: RESET_TRACE_FILTER,
|
||||
});
|
||||
|
||||
setIsChanged((state) => !state);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Search />
|
||||
<Container>
|
||||
<div>
|
||||
<ClearAllFilter onClick={onClickHandler} type="primary">
|
||||
Clear all filters
|
||||
</ClearAllFilter>
|
||||
<LeftContainer>
|
||||
<Filters />
|
||||
</LeftContainer>
|
||||
</div>
|
||||
|
||||
<RightContainer>
|
||||
<Card>
|
||||
<TraceGraphFilter />
|
||||
<TraceGraph />
|
||||
</Card>
|
||||
|
||||
<Card style={{ marginTop: '2rem' }}>
|
||||
<TraceTable />
|
||||
</Card>
|
||||
</RightContainer>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
getSpansAggregate: (props: GetSpansAggregateProps) => void;
|
||||
getSpans: (props: GetSpansProps) => void;
|
||||
getInitialFilter: (
|
||||
minTime: GlobalReducer['minTime'],
|
||||
maxTime: GlobalReducer['maxTime'],
|
||||
) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getInitialFilter: bindActionCreators(GetInitialTraceFilter, dispatch),
|
||||
getSpansAggregate: bindActionCreators(GetSpansAggregate, dispatch),
|
||||
getSpans: bindActionCreators(GetSpans, dispatch),
|
||||
});
|
||||
|
||||
type Props = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Trace);
|
37
frontend/src/pages/Trace/styles.ts
Normal file
37
frontend/src/pages/Trace/styles.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import styled from 'styled-components';
|
||||
import { Button, Card } from 'antd';
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 80vh;
|
||||
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
export const LeftContainer = styled(Card)`
|
||||
flex: 0.5;
|
||||
width: 95%;
|
||||
padding-right: 0.5rem;
|
||||
|
||||
.ant-card-body {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const RightContainer = styled(Card)`
|
||||
&&& {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.ant-card-body {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ClearAllFilter = styled(Button)`
|
||||
&&& {
|
||||
width: 95%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
`;
|
@ -1,76 +0,0 @@
|
||||
import { Typography } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import TraceCustomVisualisation from 'container/TraceCustomVisualization';
|
||||
import TraceFilter from 'container/TraceFilter';
|
||||
import TraceList from 'container/TraceList';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import {
|
||||
GetInitialTraceData,
|
||||
GetInitialTraceDataProps,
|
||||
ResetRaceData,
|
||||
} from 'store/actions/trace';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
const TraceDetail = ({
|
||||
getInitialTraceData,
|
||||
resetTraceData,
|
||||
}: TraceDetailProps): JSX.Element => {
|
||||
const { loading, selectedTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const { loading: TraceLoading, error, errorMessage } = useSelector<
|
||||
AppState,
|
||||
TraceReducer
|
||||
>((state) => state.trace);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
getInitialTraceData({
|
||||
selectedTime,
|
||||
});
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
resetTraceData();
|
||||
};
|
||||
}, [getInitialTraceData, loading, selectedTime, resetTraceData]);
|
||||
|
||||
if (error) {
|
||||
return <Typography>{errorMessage}</Typography>;
|
||||
}
|
||||
|
||||
if (loading || TraceLoading) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TraceFilter />
|
||||
<TraceCustomVisualisation />
|
||||
<TraceList />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
getInitialTraceData: (props: GetInitialTraceDataProps) => void;
|
||||
resetTraceData: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getInitialTraceData: bindActionCreators(GetInitialTraceData, dispatch),
|
||||
resetTraceData: bindActionCreators(ResetRaceData, dispatch),
|
||||
});
|
||||
|
||||
type TraceDetailProps = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TraceDetail);
|
@ -1,201 +0,0 @@
|
||||
import getServiceList from 'api/trace/getServiceList';
|
||||
import getServiceOperation from 'api/trace/getServiceOperation';
|
||||
import getSpan from 'api/trace/getSpan';
|
||||
import getSpansAggregate from 'api/trace/getSpanAggregate';
|
||||
import getTags from 'api/trace/getTags';
|
||||
import { AxiosError } from 'axios';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import history from 'lib/history';
|
||||
import { Dispatch } from 'redux';
|
||||
import store from 'store';
|
||||
import AppActions from 'types/actions';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps as ServiceOperationPayloadProps } from 'types/api/trace/getServiceOperation';
|
||||
import { PayloadProps as TagPayloadProps } from 'types/api/trace/getTags';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const GetInitialTraceData = ({
|
||||
selectedTime,
|
||||
}: GetInitialTraceDataProps): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
||||
try {
|
||||
const { globalTime, trace } = store.getState();
|
||||
const { minTime, maxTime, selectedTime: globalSelectedTime } = globalTime;
|
||||
const { selectedAggOption, selectedEntity } = trace;
|
||||
|
||||
// keeping the redux as source of truth
|
||||
if (selectedTime !== globalSelectedTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'UPDATE_SPANS_LOADING',
|
||||
payload: {
|
||||
loading: true,
|
||||
},
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(history.location.search.split('?')[1]);
|
||||
const operationName = urlParams.get(METRICS_PAGE_QUERY_PARAM.operation);
|
||||
const serviceName = urlParams.get(METRICS_PAGE_QUERY_PARAM.service);
|
||||
const errorTag = urlParams.get(METRICS_PAGE_QUERY_PARAM.error);
|
||||
const kindTag = urlParams.get(METRICS_PAGE_QUERY_PARAM.kind);
|
||||
const latencyMin = urlParams.get(METRICS_PAGE_QUERY_PARAM.latencyMin);
|
||||
const latencyMax = urlParams.get(METRICS_PAGE_QUERY_PARAM.latencyMax);
|
||||
const selectedTags = urlParams.get(METRICS_PAGE_QUERY_PARAM.selectedTags);
|
||||
const aggregationOption = urlParams.get(
|
||||
METRICS_PAGE_QUERY_PARAM.aggregationOption,
|
||||
);
|
||||
const selectedEntityOption = urlParams.get(METRICS_PAGE_QUERY_PARAM.entity);
|
||||
|
||||
const isCustomSelected = selectedTime === 'custom';
|
||||
|
||||
const end = isCustomSelected
|
||||
? globalTime.maxTime + 15 * 60 * 1000000000
|
||||
: maxTime;
|
||||
|
||||
const start = isCustomSelected
|
||||
? globalTime.minTime - 15 * 60 * 1000000000
|
||||
: minTime;
|
||||
|
||||
const [
|
||||
serviceListResponse,
|
||||
spanResponse,
|
||||
spanAggregateResponse,
|
||||
] = await Promise.all([
|
||||
getServiceList(),
|
||||
getSpan({
|
||||
start,
|
||||
end,
|
||||
kind: kindTag || '',
|
||||
limit: '100',
|
||||
lookback: '2d',
|
||||
maxDuration: latencyMax || '',
|
||||
minDuration: latencyMin || '',
|
||||
operation: operationName || '',
|
||||
service: serviceName || '',
|
||||
tags: selectedTags || '[]',
|
||||
}),
|
||||
getSpansAggregate({
|
||||
aggregation_option: aggregationOption || selectedAggOption,
|
||||
dimension: selectedEntityOption || selectedEntity,
|
||||
end,
|
||||
start,
|
||||
kind: kindTag || '',
|
||||
maxDuration: latencyMax || '',
|
||||
minDuration: latencyMin || '',
|
||||
operation: operationName || '',
|
||||
service: serviceName || '',
|
||||
step: '60',
|
||||
tags: selectedTags || '[]',
|
||||
}),
|
||||
]);
|
||||
|
||||
let tagResponse:
|
||||
| SuccessResponse<TagPayloadProps>
|
||||
| ErrorResponse
|
||||
| undefined;
|
||||
|
||||
let serviceOperationResponse:
|
||||
| SuccessResponse<ServiceOperationPayloadProps>
|
||||
| ErrorResponse
|
||||
| undefined;
|
||||
|
||||
if (serviceName !== null && serviceName.length !== 0) {
|
||||
[tagResponse, serviceOperationResponse] = await Promise.all([
|
||||
getTags({
|
||||
service: serviceName,
|
||||
}),
|
||||
getServiceOperation({
|
||||
service: serviceName,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
const getSelectedTags = (): TraceReducer['selectedTags'] => {
|
||||
const selectedTag = JSON.parse(selectedTags || '[]');
|
||||
|
||||
if (typeof selectedTag !== 'object' && Array.isArray(selectedTag)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (errorTag) {
|
||||
return [
|
||||
...selectedTag,
|
||||
{
|
||||
key: METRICS_PAGE_QUERY_PARAM.error,
|
||||
operator: 'equals',
|
||||
value: errorTag,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return [...selectedTag];
|
||||
};
|
||||
|
||||
const getCondition = (): boolean => {
|
||||
const basicCondition =
|
||||
serviceListResponse.statusCode === 200 &&
|
||||
spanResponse.statusCode === 200 &&
|
||||
(spanAggregateResponse.statusCode === 200 ||
|
||||
spanAggregateResponse.statusCode === 400);
|
||||
|
||||
if (serviceName === null || serviceName.length === 0) {
|
||||
return basicCondition;
|
||||
}
|
||||
|
||||
return (
|
||||
basicCondition &&
|
||||
tagResponse?.statusCode === 200 &&
|
||||
serviceOperationResponse?.statusCode === 200
|
||||
);
|
||||
};
|
||||
|
||||
const condition = getCondition();
|
||||
|
||||
if (condition) {
|
||||
dispatch({
|
||||
type: 'GET_TRACE_INITIAL_DATA_SUCCESS',
|
||||
payload: {
|
||||
serviceList: serviceListResponse.payload || [],
|
||||
operationList: serviceOperationResponse?.payload || [],
|
||||
tagsSuggestions: tagResponse?.payload || [],
|
||||
spansList: spanResponse?.payload || [],
|
||||
selectedService: serviceName || '',
|
||||
selectedOperation: operationName || '',
|
||||
selectedTags: getSelectedTags(),
|
||||
selectedKind: kindTag || '',
|
||||
selectedLatency: {
|
||||
max: latencyMax || '',
|
||||
min: latencyMin || '',
|
||||
},
|
||||
spansAggregate: spanAggregateResponse.payload || [],
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: 'GET_TRACE_LOADING_END',
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'GET_TRACE_INITIAL_DATA_ERROR',
|
||||
payload: {
|
||||
errorMessage: serviceListResponse?.error || 'Something went wrong',
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'GET_TRACE_INITIAL_DATA_ERROR',
|
||||
payload: {
|
||||
errorMessage: (error as AxiosError).toString() || 'Something went wrong',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export interface GetInitialTraceDataProps {
|
||||
selectedTime: GlobalReducer['selectedTime'];
|
||||
}
|
173
frontend/src/store/actions/trace/getInitialFilter.ts
Normal file
173
frontend/src/store/actions/trace/getInitialFilter.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import { Dispatch, Store } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import getFiltersApi from 'api/trace/getFilters';
|
||||
import {
|
||||
parseSelectedFilter,
|
||||
parseFilterToFetchData,
|
||||
parseQueryIntoCurrent,
|
||||
parseQueryIntoSelectedTags,
|
||||
isTraceFilterEnum,
|
||||
parseQueryIntoFilter,
|
||||
parseIsSkippedSelection,
|
||||
parseFilterExclude,
|
||||
} from './util';
|
||||
import {
|
||||
UPDATE_ALL_FILTERS,
|
||||
UPDATE_TRACE_FILTER_LOADING,
|
||||
} from 'types/actions/trace';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
import { notification } from 'antd';
|
||||
import xor from 'lodash-es/xor';
|
||||
|
||||
export const GetInitialTraceFilter = (
|
||||
minTime: GlobalReducer['minTime'],
|
||||
maxTime: GlobalReducer['maxTime'],
|
||||
): ((
|
||||
dispatch: Dispatch<AppActions>,
|
||||
getState: Store<AppState>['getState'],
|
||||
) => void) => {
|
||||
return async (dispatch, getState): Promise<void> => {
|
||||
try {
|
||||
const query = location.search;
|
||||
|
||||
const { traces, globalTime } = getState();
|
||||
|
||||
if (globalTime.maxTime !== maxTime && globalTime.minTime !== minTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getSelectedFilter = parseSelectedFilter(
|
||||
query,
|
||||
traces.selectedFilter,
|
||||
true,
|
||||
);
|
||||
|
||||
const getFilterToFetchData = parseFilterToFetchData(
|
||||
query,
|
||||
traces.filterToFetchData,
|
||||
);
|
||||
|
||||
const getUserSelected = parseSelectedFilter(
|
||||
query,
|
||||
traces.userSelectedFilter,
|
||||
);
|
||||
|
||||
const getIsFilterExcluded = parseFilterExclude(
|
||||
query,
|
||||
traces.isFilterExclude,
|
||||
);
|
||||
|
||||
const parsedQueryCurrent = parseQueryIntoCurrent(
|
||||
query,
|
||||
traces.spansAggregate.currentPage,
|
||||
);
|
||||
|
||||
const isSelectionSkipped = parseIsSkippedSelection(query);
|
||||
|
||||
const parsedSelectedTags = parseQueryIntoSelectedTags(
|
||||
query,
|
||||
traces.selectedTags,
|
||||
);
|
||||
|
||||
const parsedFilter = parseQueryIntoFilter(query, traces.filter);
|
||||
|
||||
// now filter are not matching we need to fetch the data and make in sync
|
||||
dispatch({
|
||||
type: UPDATE_TRACE_FILTER_LOADING,
|
||||
payload: {
|
||||
filterLoading: true,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await getFiltersApi({
|
||||
end: String(maxTime),
|
||||
getFilters: getFilterToFetchData.currentValue,
|
||||
start: String(minTime),
|
||||
other: Object.fromEntries(getSelectedFilter.currentValue),
|
||||
isFilterExclude: getIsFilterExcluded.currentValue,
|
||||
});
|
||||
|
||||
let preSelectedFilter: Map<TraceFilterEnum, string[]> = new Map(
|
||||
getSelectedFilter.currentValue,
|
||||
);
|
||||
|
||||
if (response.payload && !isSelectionSkipped.currentValue) {
|
||||
const diff =
|
||||
query.length === 0
|
||||
? traces.filterToFetchData
|
||||
: xor(traces.filterToFetchData, getFilterToFetchData.currentValue);
|
||||
|
||||
Object.keys(response.payload).map((key) => {
|
||||
const value = response.payload[key];
|
||||
Object.keys(value)
|
||||
// remove maxDuration and minDuration filter from initial selection logic
|
||||
.filter((e) => !['maxDuration', 'minDuration'].includes(e))
|
||||
.map((preKey) => {
|
||||
if (isTraceFilterEnum(key) && diff.find((v) => v === key)) {
|
||||
// const preValue = preSelectedFilter?.get(key) || [];
|
||||
const preValue = getUserSelected.currentValue?.get(key) || [];
|
||||
// preSelectedFilter?.set(key, [...new Set([...preValue, preKey])]);
|
||||
getUserSelected.currentValue.set(key, [
|
||||
...new Set([...preValue, preKey]),
|
||||
]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
const preResponseSelected: TraceReducer['filterResponseSelected'] = new Set();
|
||||
|
||||
const initialFilter = new Map<TraceFilterEnum, Record<string, string>>(
|
||||
parsedFilter.currentValue,
|
||||
);
|
||||
|
||||
Object.keys(response.payload).forEach((key) => {
|
||||
const value = response.payload[key];
|
||||
if (isTraceFilterEnum(key)) {
|
||||
Object.keys(value).forEach((e) => preResponseSelected.add(e));
|
||||
|
||||
initialFilter.set(key, {
|
||||
...initialFilter.get(key),
|
||||
...value,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ALL_FILTERS,
|
||||
payload: {
|
||||
filter: initialFilter,
|
||||
selectedFilter: preSelectedFilter,
|
||||
filterToFetchData: getFilterToFetchData.currentValue,
|
||||
current: parsedQueryCurrent.currentValue,
|
||||
selectedTags: parsedSelectedTags.currentValue,
|
||||
userSelected: getUserSelected.currentValue,
|
||||
isFilterExclude: getIsFilterExcluded.currentValue,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_TRACE_FILTER_LOADING,
|
||||
payload: {
|
||||
filterLoading: false,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
dispatch({
|
||||
type: UPDATE_TRACE_FILTER_LOADING,
|
||||
payload: {
|
||||
filterLoading: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
115
frontend/src/store/actions/trace/getInitialSpansAggregate.ts
Normal file
115
frontend/src/store/actions/trace/getInitialSpansAggregate.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import { Dispatch, Store } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_SPANS_AGGREEGATE } from 'types/actions/trace';
|
||||
import getSpansAggregate from 'api/trace/getSpansAggregate';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { notification } from 'antd';
|
||||
|
||||
export const GetSpansAggregate = (
|
||||
props: GetSpansAggregateProps,
|
||||
): ((
|
||||
dispatch: Dispatch<AppActions>,
|
||||
getState: Store<AppState>['getState'],
|
||||
) => void) => {
|
||||
return async (dispatch, getState): Promise<void> => {
|
||||
const { traces, globalTime } = getState();
|
||||
const { spansAggregate } = traces;
|
||||
|
||||
if (
|
||||
globalTime.maxTime !== props.maxTime &&
|
||||
globalTime.minTime !== props.minTime
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (traces.filterLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// triggering loading
|
||||
dispatch({
|
||||
type: UPDATE_SPANS_AGGREEGATE,
|
||||
payload: {
|
||||
spansAggregate: {
|
||||
currentPage: props.current,
|
||||
loading: true,
|
||||
data: spansAggregate.data,
|
||||
error: false,
|
||||
total: spansAggregate.total,
|
||||
pageSize: props.pageSize,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await getSpansAggregate({
|
||||
end: props.maxTime,
|
||||
start: props.minTime,
|
||||
selectedFilter: props.selectedFilter,
|
||||
limit: props.pageSize,
|
||||
offset: props.current * props.pageSize - props.pageSize,
|
||||
selectedTags: props.selectedTags,
|
||||
isFilterExclude: traces.isFilterExclude,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
dispatch({
|
||||
type: UPDATE_SPANS_AGGREEGATE,
|
||||
payload: {
|
||||
spansAggregate: {
|
||||
currentPage: props.current,
|
||||
loading: false,
|
||||
data: response.payload.spans,
|
||||
error: false,
|
||||
total: response.payload.totalSpans,
|
||||
pageSize: props.pageSize,
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_SPANS_AGGREEGATE,
|
||||
payload: {
|
||||
spansAggregate: {
|
||||
currentPage: props.current,
|
||||
loading: false,
|
||||
data: spansAggregate.data,
|
||||
error: true,
|
||||
total: spansAggregate.total,
|
||||
pageSize: props.pageSize,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: UPDATE_SPANS_AGGREEGATE,
|
||||
payload: {
|
||||
spansAggregate: {
|
||||
currentPage: props.current,
|
||||
loading: false,
|
||||
data: spansAggregate.data,
|
||||
error: true,
|
||||
total: spansAggregate.total,
|
||||
pageSize: props.pageSize,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export interface GetSpansAggregateProps {
|
||||
maxTime: GlobalReducer['maxTime'];
|
||||
minTime: GlobalReducer['minTime'];
|
||||
selectedFilter: TraceReducer['selectedFilter'];
|
||||
current: TraceReducer['spansAggregate']['currentPage'];
|
||||
pageSize: TraceReducer['spansAggregate']['pageSize'];
|
||||
selectedTags: TraceReducer['selectedTags'];
|
||||
}
|
96
frontend/src/store/actions/trace/getSpans.ts
Normal file
96
frontend/src/store/actions/trace/getSpans.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { Dispatch, Store } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
UPDATE_TRACE_GRAPH_ERROR,
|
||||
UPDATE_TRACE_GRAPH_LOADING,
|
||||
UPDATE_TRACE_GRAPH_SUCCESS,
|
||||
} from 'types/actions/trace';
|
||||
import getSpans from 'api/trace/getSpans';
|
||||
import { Props } from 'types/api/trace/getSpans';
|
||||
import { notification } from 'antd';
|
||||
|
||||
export const GetSpans = (
|
||||
props: GetSpansProps,
|
||||
): ((
|
||||
dispatch: Dispatch<AppActions>,
|
||||
getState: Store<AppState>['getState'],
|
||||
) => void) => {
|
||||
return async (dispatch, getState): Promise<void> => {
|
||||
try {
|
||||
const { traces, globalTime } = getState();
|
||||
const { spansGraph } = traces;
|
||||
|
||||
if (globalTime.maxTime !== props.end && globalTime.minTime !== props.start) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { selectedTime } = globalTime;
|
||||
|
||||
if (traces.filterLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @TODO refactor this logic when share url functionlity is updated
|
||||
const isCustomSelected = selectedTime === 'custom';
|
||||
|
||||
const end = isCustomSelected
|
||||
? globalTime.maxTime + 15 * 60 * 1000000000
|
||||
: props.end;
|
||||
|
||||
const start = isCustomSelected
|
||||
? globalTime.minTime - 15 * 60 * 1000000000
|
||||
: props.start;
|
||||
|
||||
if (!spansGraph.loading) {
|
||||
dispatch({
|
||||
type: UPDATE_TRACE_GRAPH_LOADING,
|
||||
payload: {
|
||||
loading: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const response = await getSpans({
|
||||
end: end,
|
||||
function: props.function,
|
||||
groupBy: props.groupBy,
|
||||
selectedFilter: props.selectedFilter,
|
||||
selectedTags: props.selectedTags,
|
||||
start: start,
|
||||
step: props.step,
|
||||
isFilterExclude: props.isFilterExclude,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
dispatch({
|
||||
type: UPDATE_TRACE_GRAPH_SUCCESS,
|
||||
payload: {
|
||||
data: response.payload,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message: response.error || 'Something went wrong',
|
||||
});
|
||||
dispatch({
|
||||
type: UPDATE_TRACE_GRAPH_ERROR,
|
||||
payload: {
|
||||
error: true,
|
||||
errorMessage: response.error || 'Something went wrong',
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: UPDATE_TRACE_GRAPH_ERROR,
|
||||
payload: {
|
||||
error: true,
|
||||
errorMessage: (error as Error)?.toString() || 'Something went wrong',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export type GetSpansProps = Props;
|
@ -1,92 +0,0 @@
|
||||
import getSpansAggregate from 'api/trace/getSpanAggregate';
|
||||
import { AxiosError } from 'axios';
|
||||
import { Dispatch } from 'redux';
|
||||
import store from 'store';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const GetTraceVisualAggregates = ({
|
||||
selectedEntity,
|
||||
selectedAggOption,
|
||||
}: GetTraceVisualAggregatesProps): ((
|
||||
dispatch: Dispatch<AppActions>,
|
||||
) => void) => {
|
||||
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
||||
try {
|
||||
dispatch({
|
||||
type: 'UPDATE_SPANS_LOADING',
|
||||
payload: {
|
||||
loading: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { trace, globalTime } = store.getState();
|
||||
|
||||
const {
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
selectedTags,
|
||||
} = trace;
|
||||
|
||||
const { selectedTime, maxTime, minTime } = globalTime;
|
||||
|
||||
const isCustomSelected = selectedTime === 'custom';
|
||||
|
||||
const end = isCustomSelected
|
||||
? globalTime.maxTime + 15 * 60 * 1000000000
|
||||
: maxTime;
|
||||
|
||||
const start = isCustomSelected
|
||||
? globalTime.minTime - 15 * 60 * 1000000000
|
||||
: minTime;
|
||||
|
||||
const [spanAggregateResponse] = await Promise.all([
|
||||
getSpansAggregate({
|
||||
aggregation_option: selectedAggOption,
|
||||
dimension: selectedEntity,
|
||||
end,
|
||||
start,
|
||||
kind: selectedKind || '',
|
||||
maxDuration: selectedLatency.max || '',
|
||||
minDuration: selectedLatency.min || '',
|
||||
operation: selectedOperation || '',
|
||||
service: selectedService || '',
|
||||
step: '60',
|
||||
tags: JSON.stringify(selectedTags) || '[]',
|
||||
}),
|
||||
]);
|
||||
|
||||
if (spanAggregateResponse.statusCode === 200) {
|
||||
dispatch({
|
||||
type: 'UPDATE_AGGREGATES',
|
||||
payload: {
|
||||
spansAggregate: spanAggregateResponse.payload,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'UPDATE_SPANS_LOADING',
|
||||
payload: {
|
||||
loading: false,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'GET_TRACE_INITIAL_DATA_ERROR',
|
||||
payload: {
|
||||
errorMessage: (error as AxiosError).toString() || 'Something went wrong',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export interface GetTraceVisualAggregatesProps {
|
||||
selectedAggOption: TraceReducer['selectedAggOption'];
|
||||
selectedEntity: TraceReducer['selectedEntity'];
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
export * from './getInitialData';
|
||||
export * from './resetTraceDetails';
|
||||
export * from './updateSelectedAggOption';
|
||||
export * from './updateSelectedEntity';
|
||||
export * from './updateSelectedKind';
|
||||
export * from './updateSelectedLatency';
|
||||
export * from './updateSelectedOperation';
|
||||
export * from './updateSelectedService';
|
||||
export * from './updateSelectedTags';
|
@ -1,12 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
|
||||
export const LoadingCompleted = (): ((
|
||||
dispatch: Dispatch<AppActions>,
|
||||
) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'GET_TRACE_LOADING_END',
|
||||
});
|
||||
};
|
||||
};
|
36
frontend/src/store/actions/trace/parseFilter/current.ts
Normal file
36
frontend/src/store/actions/trace/parseFilter/current.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { ParsedUrl } from '../util';
|
||||
|
||||
export const parseQueryIntoCurrent = (
|
||||
query: string,
|
||||
stateCurrent: TraceReducer['spansAggregate']['currentPage'],
|
||||
): ParsedUrl<TraceReducer['spansAggregate']['currentPage']> => {
|
||||
const url = new URLSearchParams(query);
|
||||
|
||||
let current = 1;
|
||||
|
||||
const selected = url.get('current');
|
||||
|
||||
if (selected) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(decodeURIComponent(selected));
|
||||
if (Number.isInteger(parsedValue)) {
|
||||
current = parseInt(parsedValue, 10);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error while parsing json');
|
||||
}
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
return {
|
||||
currentValue: parseInt(selected, 10),
|
||||
urlValue: current,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
currentValue: stateCurrent,
|
||||
urlValue: current,
|
||||
};
|
||||
};
|
43
frontend/src/store/actions/trace/parseFilter/filter.ts
Normal file
43
frontend/src/store/actions/trace/parseFilter/filter.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
import { isTraceFilterEnum, ParsedUrl } from '../util';
|
||||
|
||||
export const parseQueryIntoFilter = (
|
||||
query: string,
|
||||
stateFilter: TraceReducer['filter'],
|
||||
): ParsedUrl<TraceReducer['filter']> => {
|
||||
const urlFilter = new Map<TraceFilterEnum, Record<string, string>>();
|
||||
const url = new URLSearchParams(query);
|
||||
|
||||
const selected = url.get('filter');
|
||||
|
||||
if (selected) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(selected);
|
||||
|
||||
if (typeof parsedValue === 'object') {
|
||||
Object.keys(parsedValue).forEach((key) => {
|
||||
if (isTraceFilterEnum(key)) {
|
||||
const value = parsedValue[key];
|
||||
if (typeof value === 'object') {
|
||||
urlFilter.set(key, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
return {
|
||||
currentValue: urlFilter,
|
||||
urlValue: urlFilter,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
currentValue: stateFilter,
|
||||
urlValue: urlFilter,
|
||||
};
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
import { ParsedUrl } from '../util';
|
||||
|
||||
export const parseFilterToFetchData = (
|
||||
query: string,
|
||||
stateTraceFilterData: TraceReducer['filterToFetchData'],
|
||||
): ParsedUrl<TraceFilterEnum[]> => {
|
||||
const url = new URLSearchParams(query);
|
||||
|
||||
let filterToFetchData: TraceFilterEnum[] = [];
|
||||
|
||||
const selected = url.get('filterToFetchData');
|
||||
|
||||
if (selected) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(decodeURIComponent(selected));
|
||||
|
||||
if (Array.isArray(parsedValue)) {
|
||||
filterToFetchData.push(...parsedValue);
|
||||
}
|
||||
} catch (error) {
|
||||
//error while parsing json
|
||||
}
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
return {
|
||||
currentValue: filterToFetchData,
|
||||
urlValue: filterToFetchData,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
currentValue: stateTraceFilterData,
|
||||
urlValue: filterToFetchData,
|
||||
};
|
||||
};
|
8
frontend/src/store/actions/trace/parseFilter/index.ts
Normal file
8
frontend/src/store/actions/trace/parseFilter/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from './minMaxTime';
|
||||
export * from './selectedFilter';
|
||||
export * from './filterToFetchData';
|
||||
export * from './selectedTags';
|
||||
export * from './filter';
|
||||
export * from './skippedSelected';
|
||||
export * from './current';
|
||||
export * from './isFilterExclude';
|
@ -0,0 +1,44 @@
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
import { isTraceFilterEnum, ParsedUrl } from '../util';
|
||||
|
||||
export const parseFilterExclude = (
|
||||
query: string,
|
||||
stateFilterExclude: TraceReducer['isFilterExclude'],
|
||||
): ParsedUrl<TraceReducer['isFilterExclude']> => {
|
||||
const currentFilter = new Map<TraceFilterEnum, boolean>();
|
||||
|
||||
const url = new URLSearchParams(query);
|
||||
|
||||
const isPresent = url.get('isFilterExclude');
|
||||
|
||||
if (isPresent) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(isPresent);
|
||||
|
||||
if (typeof parsedValue === 'object') {
|
||||
Object.keys(parsedValue).forEach((key) => {
|
||||
if (isTraceFilterEnum(key)) {
|
||||
const keyValue = parsedValue[key];
|
||||
if (typeof keyValue === 'boolean') {
|
||||
currentFilter.set(key, keyValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// parsing the value
|
||||
}
|
||||
}
|
||||
|
||||
if (isPresent) {
|
||||
return {
|
||||
currentValue: currentFilter,
|
||||
urlValue: currentFilter,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
currentValue: stateFilterExclude,
|
||||
urlValue: currentFilter,
|
||||
};
|
||||
};
|
20
frontend/src/store/actions/trace/parseFilter/minMaxTime.ts
Normal file
20
frontend/src/store/actions/trace/parseFilter/minMaxTime.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
|
||||
export const parseMinMaxTime = (query: string): GlobalTime => {
|
||||
const url = new URLSearchParams(query);
|
||||
let maxTime = 0;
|
||||
let minTime = 0;
|
||||
|
||||
const urlMaxTime = url.get('minTime');
|
||||
const urlMinTime = url.get('maxTime');
|
||||
|
||||
if (urlMaxTime && urlMinTime) {
|
||||
maxTime = parseInt(urlMaxTime);
|
||||
minTime = parseInt(urlMinTime);
|
||||
}
|
||||
|
||||
return {
|
||||
maxTime,
|
||||
minTime,
|
||||
};
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
import { isTraceFilterEnum, ParsedUrl } from '../util';
|
||||
|
||||
export const parseSelectedFilter = (
|
||||
query: string,
|
||||
selectedFilter: TraceReducer['selectedFilter'],
|
||||
isUserSelected = false,
|
||||
): ParsedUrl<Map<TraceFilterEnum, string[]>> => {
|
||||
const url = new URLSearchParams(query);
|
||||
|
||||
const filters = new Map<TraceFilterEnum, string[]>();
|
||||
|
||||
const title = isUserSelected ? 'selected' : 'userSelectedFilter';
|
||||
|
||||
const selected = url.get(title);
|
||||
|
||||
if (selected) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(decodeURIComponent(selected));
|
||||
if (typeof parsedValue === 'object') {
|
||||
Object.keys(parsedValue).forEach((e) => {
|
||||
if (isTraceFilterEnum(e)) {
|
||||
filters.set(e, parsedValue[e]);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// if the parsing error happens
|
||||
}
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
return {
|
||||
urlValue: filters,
|
||||
currentValue: filters,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
urlValue: filters,
|
||||
currentValue: selectedFilter,
|
||||
};
|
||||
};
|
37
frontend/src/store/actions/trace/parseFilter/selectedTags.ts
Normal file
37
frontend/src/store/actions/trace/parseFilter/selectedTags.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { ParsedUrl } from '../util';
|
||||
|
||||
export const parseQueryIntoSelectedTags = (
|
||||
query: string,
|
||||
stateSelectedTags: TraceReducer['selectedTags'],
|
||||
): ParsedUrl<TraceReducer['selectedTags']> => {
|
||||
const url = new URLSearchParams(query);
|
||||
|
||||
let selectedTags: TraceReducer['selectedTags'] = [];
|
||||
|
||||
const querySelectedTags = url.get('selectedTags');
|
||||
|
||||
if (querySelectedTags) {
|
||||
try {
|
||||
const parsedQuerySelectedTags = JSON.parse(querySelectedTags);
|
||||
|
||||
if (Array.isArray(parsedQuerySelectedTags)) {
|
||||
selectedTags = parsedQuerySelectedTags;
|
||||
}
|
||||
} catch (error) {
|
||||
//error while parsing
|
||||
}
|
||||
}
|
||||
|
||||
if (querySelectedTags) {
|
||||
return {
|
||||
currentValue: selectedTags,
|
||||
urlValue: selectedTags,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
currentValue: stateSelectedTags,
|
||||
urlValue: selectedTags,
|
||||
};
|
||||
};
|
@ -0,0 +1,33 @@
|
||||
import { ParsedUrl } from '../util';
|
||||
|
||||
export const parseIsSkippedSelection = (query: string): ParsedUrl<boolean> => {
|
||||
const url = new URLSearchParams(query);
|
||||
|
||||
let current = false;
|
||||
|
||||
const isSkippedSelected = url.get('isSelectedFilterSkipped');
|
||||
|
||||
if (isSkippedSelected) {
|
||||
try {
|
||||
const parsedValue = JSON.parse(isSkippedSelected);
|
||||
|
||||
if (typeof parsedValue === 'boolean') {
|
||||
current = parsedValue;
|
||||
}
|
||||
} catch (error) {
|
||||
current = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSkippedSelected) {
|
||||
return {
|
||||
currentValue: current,
|
||||
urlValue: current,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
currentValue: current,
|
||||
urlValue: current,
|
||||
};
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
|
||||
export const ResetRaceData = (): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'RESET_TRACE_DATA',
|
||||
});
|
||||
};
|
||||
};
|
50
frontend/src/store/actions/trace/selectTraceFilter.ts
Normal file
50
frontend/src/store/actions/trace/selectTraceFilter.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { Dispatch, Store } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceFilterEnum } from 'types/reducer/trace';
|
||||
import { updateURL } from './util';
|
||||
|
||||
export const SelectedTraceFilter = (props: {
|
||||
topic: TraceFilterEnum;
|
||||
value: string;
|
||||
}): ((
|
||||
dispatch: Dispatch<AppActions>,
|
||||
getState: Store<AppState>['getState'],
|
||||
) => void) => {
|
||||
return (_, getState): void => {
|
||||
const { topic, value } = props;
|
||||
const { traces } = getState();
|
||||
|
||||
const filter = traces.selectedFilter;
|
||||
|
||||
const isTopicPresent = filter.get(topic);
|
||||
|
||||
// append the value
|
||||
if (!isTopicPresent) {
|
||||
filter.set(props.topic, [props.value]);
|
||||
} else {
|
||||
const isValuePresent =
|
||||
isTopicPresent.find((e) => e === props.value) !== undefined;
|
||||
|
||||
// check the value if present then remove the value
|
||||
if (isValuePresent) {
|
||||
filter.set(
|
||||
props.topic,
|
||||
isTopicPresent.filter((e) => e !== value),
|
||||
);
|
||||
} else {
|
||||
// if not present add into the array of string
|
||||
filter.set(props.topic, [...isTopicPresent, props.value]);
|
||||
}
|
||||
}
|
||||
|
||||
updateURL(
|
||||
filter,
|
||||
traces.filterToFetchData,
|
||||
traces.spansAggregate.currentPage,
|
||||
traces.selectedTags,
|
||||
traces.filter,
|
||||
traces.isFilterExclude,
|
||||
);
|
||||
};
|
||||
};
|
17
frontend/src/store/actions/trace/updateIsTagsError.ts
Normal file
17
frontend/src/store/actions/trace/updateIsTagsError.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { UPDATE_IS_TAG_ERROR } from 'types/actions/trace';
|
||||
|
||||
export const UpdateTagIsError = (
|
||||
isTagModalError: TraceReducer['isTagModalError'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch): void => {
|
||||
dispatch({
|
||||
type: UPDATE_IS_TAG_ERROR,
|
||||
payload: {
|
||||
isTagModalError,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateSelectedAggOption = (
|
||||
selectedAggOption: TraceReducer['selectedAggOption'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'UPDATE_SELECTED_AGG_OPTION',
|
||||
payload: {
|
||||
selectedAggOption,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
@ -1,164 +0,0 @@
|
||||
import getServiceOperation from 'api/trace/getServiceOperation';
|
||||
import getSpan from 'api/trace/getSpan';
|
||||
import getSpansAggregate from 'api/trace/getSpanAggregate';
|
||||
import getTags from 'api/trace/getTags';
|
||||
import { AxiosError } from 'axios';
|
||||
import { Dispatch } from 'redux';
|
||||
import store from 'store';
|
||||
import AppActions from 'types/actions';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps as ServiceOperationPayloadProps } from 'types/api/trace/getServiceOperation';
|
||||
import { PayloadProps as TagPayloadProps } from 'types/api/trace/getTags';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateSelectedData = ({
|
||||
selectedKind,
|
||||
selectedService,
|
||||
selectedLatency,
|
||||
selectedOperation,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
}: UpdateSelectedDataProps): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
||||
try {
|
||||
dispatch({
|
||||
type: 'UPDATE_SPANS_LOADING',
|
||||
payload: {
|
||||
loading: true,
|
||||
},
|
||||
});
|
||||
const { trace, globalTime } = store.getState();
|
||||
const { minTime, maxTime, selectedTime } = globalTime;
|
||||
|
||||
const { selectedTags } = trace;
|
||||
|
||||
const isCustomSelected = selectedTime === 'custom';
|
||||
|
||||
const end = isCustomSelected
|
||||
? globalTime.maxTime + 15 * 60 * 1000000000
|
||||
: maxTime;
|
||||
|
||||
const start = isCustomSelected
|
||||
? globalTime.minTime - 15 * 60 * 1000000000
|
||||
: minTime;
|
||||
|
||||
const [spanResponse, getSpanAggregateResponse] = await Promise.all([
|
||||
getSpan({
|
||||
start,
|
||||
end,
|
||||
kind: selectedKind || '',
|
||||
limit: '100',
|
||||
lookback: '2d',
|
||||
maxDuration: selectedLatency.max || '',
|
||||
minDuration: selectedLatency.min || '',
|
||||
operation: selectedOperation || '',
|
||||
service: selectedService || '',
|
||||
tags: JSON.stringify(selectedTags),
|
||||
}),
|
||||
getSpansAggregate({
|
||||
aggregation_option: selectedAggOption || '',
|
||||
dimension: selectedEntity || '',
|
||||
end,
|
||||
kind: selectedKind || '',
|
||||
maxDuration: selectedLatency.max || '',
|
||||
minDuration: selectedLatency.min || '',
|
||||
operation: selectedOperation || '',
|
||||
service: selectedService || '',
|
||||
start,
|
||||
step: '60',
|
||||
tags: JSON.stringify(selectedTags),
|
||||
}),
|
||||
]);
|
||||
|
||||
let tagResponse:
|
||||
| SuccessResponse<TagPayloadProps>
|
||||
| ErrorResponse
|
||||
| undefined;
|
||||
|
||||
let serviceOperationResponse:
|
||||
| SuccessResponse<ServiceOperationPayloadProps>
|
||||
| ErrorResponse
|
||||
| undefined;
|
||||
|
||||
if (selectedService !== null && selectedService.length !== 0) {
|
||||
[tagResponse, serviceOperationResponse] = await Promise.all([
|
||||
getTags({
|
||||
service: selectedService,
|
||||
}),
|
||||
getServiceOperation({
|
||||
service: selectedService,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
const spanAggregateCondition =
|
||||
getSpanAggregateResponse.statusCode === 200 ||
|
||||
getSpanAggregateResponse.statusCode === 400;
|
||||
|
||||
const getCondition = (): boolean => {
|
||||
const basicCondition =
|
||||
spanResponse.statusCode === 200 && spanAggregateCondition;
|
||||
|
||||
if (selectedService === null || selectedService.length === 0) {
|
||||
return basicCondition;
|
||||
}
|
||||
|
||||
return (
|
||||
basicCondition &&
|
||||
tagResponse?.statusCode === 200 &&
|
||||
serviceOperationResponse?.statusCode === 200
|
||||
);
|
||||
};
|
||||
|
||||
const condition = getCondition();
|
||||
|
||||
if (condition) {
|
||||
dispatch({
|
||||
type: 'UPDATE_SELECTED_TRACE_DATA',
|
||||
payload: {
|
||||
operationList: serviceOperationResponse?.payload || [],
|
||||
spansList: spanResponse.payload || [],
|
||||
tagsSuggestions: tagResponse?.payload || [],
|
||||
selectedKind,
|
||||
selectedService,
|
||||
selectedLatency,
|
||||
selectedOperation,
|
||||
spansAggregate: spanAggregateCondition
|
||||
? getSpanAggregateResponse.payload || []
|
||||
: [],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: 'GET_TRACE_INITIAL_DATA_ERROR',
|
||||
payload: {
|
||||
errorMessage: 'Something went wrong',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'UPDATE_SPANS_LOADING',
|
||||
payload: {
|
||||
loading: false,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'GET_TRACE_INITIAL_DATA_ERROR',
|
||||
payload: {
|
||||
errorMessage: (error as AxiosError).toString() || 'Something went wrong',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export interface UpdateSelectedDataProps {
|
||||
selectedKind: TraceReducer['selectedKind'];
|
||||
selectedService: TraceReducer['selectedService'];
|
||||
selectedLatency: TraceReducer['selectedLatency'];
|
||||
selectedOperation: TraceReducer['selectedOperation'];
|
||||
selectedEntity: TraceReducer['selectedEntity'];
|
||||
selectedAggOption: TraceReducer['selectedAggOption'];
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateSelectedEntity = (
|
||||
selectedEntity: TraceReducer['selectedEntity'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'UPDATE_SELECTED_ENTITY',
|
||||
payload: {
|
||||
selectedEntity,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateSelectedKind = (
|
||||
selectedKind: TraceReducer['selectedKind'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'UPDATE_TRACE_SELECTED_KIND',
|
||||
payload: {
|
||||
selectedKind,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateSelectedLatency = (
|
||||
selectedLatency: TraceReducer['selectedLatency'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'UPDATE_TRACE_SELECTED_LATENCY_VALUE',
|
||||
payload: {
|
||||
selectedLatency,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateSelectedOperation = (
|
||||
selectedOperation: TraceReducer['selectedOperation'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'UPDATE_TRACE_SELECTED_OPERATION',
|
||||
payload: {
|
||||
selectedOperation,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateSelectedService = (
|
||||
selectedService: TraceReducer['selectedService'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'UPDATE_TRACE_SELECTED_SERVICE',
|
||||
payload: {
|
||||
selectedService,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
@ -1,94 +0,0 @@
|
||||
import getSpan from 'api/trace/getSpan';
|
||||
import getSpansAggregate from 'api/trace/getSpanAggregate';
|
||||
import { AxiosError } from 'axios';
|
||||
import { Dispatch } from 'redux';
|
||||
import store from 'store';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateSelectedTags = (
|
||||
selectedTags: TraceReducer['selectedTags'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
||||
try {
|
||||
dispatch({
|
||||
type: 'UPDATE_SPANS_LOADING',
|
||||
payload: {
|
||||
loading: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { trace, globalTime } = store.getState();
|
||||
const {
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
selectedAggOption,
|
||||
selectedEntity,
|
||||
spansAggregate,
|
||||
} = trace;
|
||||
|
||||
const { maxTime, minTime } = globalTime;
|
||||
|
||||
const [spanResponse, spansAggregateResponse] = await Promise.all([
|
||||
getSpan({
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
kind: selectedKind || '',
|
||||
limit: '100',
|
||||
lookback: '2d',
|
||||
maxDuration: selectedLatency.max || '',
|
||||
minDuration: selectedLatency.min || '',
|
||||
operation: selectedOperation || '',
|
||||
service: selectedService || '',
|
||||
tags: JSON.stringify(selectedTags),
|
||||
}),
|
||||
getSpansAggregate({
|
||||
aggregation_option: selectedAggOption,
|
||||
dimension: selectedEntity,
|
||||
end: maxTime,
|
||||
kind: selectedKind || '',
|
||||
maxDuration: selectedLatency.max || '',
|
||||
minDuration: selectedLatency.min || '',
|
||||
operation: selectedOperation || '',
|
||||
service: selectedService || '',
|
||||
start: minTime,
|
||||
step: '60',
|
||||
tags: JSON.stringify(selectedTags),
|
||||
}),
|
||||
]);
|
||||
|
||||
const condition =
|
||||
spansAggregateResponse.statusCode === 200 ||
|
||||
spansAggregateResponse.statusCode === 400;
|
||||
|
||||
if (spanResponse.statusCode === 200 && condition) {
|
||||
dispatch({
|
||||
type: 'UPDATE_TRACE_SELECTED_TAGS',
|
||||
payload: {
|
||||
selectedTags,
|
||||
spansList: spanResponse.payload,
|
||||
spansAggregate:
|
||||
spansAggregateResponse.statusCode === 400
|
||||
? spansAggregate
|
||||
: spansAggregateResponse.payload || [],
|
||||
},
|
||||
});
|
||||
}
|
||||
dispatch({
|
||||
type: 'UPDATE_SPANS_LOADING',
|
||||
payload: {
|
||||
loading: false,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: 'GET_TRACE_INITIAL_DATA_ERROR',
|
||||
payload: {
|
||||
errorMessage: (error as AxiosError).toString() || 'Something went wrong',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export const UpdateSpanLoading = (
|
||||
spansLoading: TraceReducer['spansLoading'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
dispatch({
|
||||
type: 'UPDATE_SPANS_LOADING',
|
||||
payload: {
|
||||
loading: spansLoading,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
17
frontend/src/store/actions/trace/updateTagPanelVisiblity.ts
Normal file
17
frontend/src/store/actions/trace/updateTagPanelVisiblity.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { UPDATE_TAG_MODAL_VISIBLITY } from 'types/actions/trace';
|
||||
|
||||
export const UpdateTagVisiblity = (
|
||||
isTagModalOpen: TraceReducer['isTagModalOpen'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch): void => {
|
||||
dispatch({
|
||||
type: UPDATE_TAG_MODAL_VISIBLITY,
|
||||
payload: {
|
||||
isTagModalOpen: isTagModalOpen,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
17
frontend/src/store/actions/trace/updateTagsSelected.ts
Normal file
17
frontend/src/store/actions/trace/updateTagsSelected.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { UPDATE_SELECTED_TAGS } from 'types/actions/trace';
|
||||
|
||||
export const UpdateSelectedTags = (
|
||||
selectedTags: TraceReducer['selectedTags'],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch): void => {
|
||||
dispatch({
|
||||
type: UPDATE_SELECTED_TAGS,
|
||||
payload: {
|
||||
selectedTags: selectedTags,
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
79
frontend/src/store/actions/trace/util.ts
Normal file
79
frontend/src/store/actions/trace/util.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
import history from 'lib/history';
|
||||
import { AllTraceFilterEnum } from 'container/Trace/Filters';
|
||||
import { PayloadProps as GetFilterPayload } from 'types/api/trace/getFilters';
|
||||
export * from './parseFilter';
|
||||
export interface ParsedUrl<T> {
|
||||
currentValue: T;
|
||||
urlValue: T;
|
||||
}
|
||||
|
||||
export function isTraceFilterEnum(
|
||||
value: TraceFilterEnum | string,
|
||||
): value is TraceFilterEnum {
|
||||
if (AllTraceFilterEnum.find((enums) => enums === value)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const updateURL = (
|
||||
selectedFilter: TraceReducer['selectedFilter'],
|
||||
filterToFetchData: TraceReducer['filterToFetchData'],
|
||||
current: TraceReducer['spansAggregate']['total'],
|
||||
selectedTags: TraceReducer['selectedTags'],
|
||||
filter: TraceReducer['filter'],
|
||||
isFilterExclude: TraceReducer['isFilterExclude'],
|
||||
userSelectedFilter: TraceReducer['userSelectedFilter'],
|
||||
) => {
|
||||
const search = new URLSearchParams(location.search);
|
||||
const preResult: { key: string; value: string }[] = [];
|
||||
|
||||
const keyToSkip = [
|
||||
'selected',
|
||||
'filterToFetchData',
|
||||
'current',
|
||||
'selectedTags',
|
||||
'filter',
|
||||
'isFilterExclude',
|
||||
'userSelectedFilter',
|
||||
];
|
||||
|
||||
search.forEach((value, key) => {
|
||||
if (!keyToSkip.includes(key)) {
|
||||
preResult.push({
|
||||
key,
|
||||
value,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
history.replace(
|
||||
`${history.location.pathname}?selected=${JSON.stringify(
|
||||
Object.fromEntries(selectedFilter),
|
||||
)}&filterToFetchData=${JSON.stringify(
|
||||
filterToFetchData,
|
||||
)}¤t=${current}&selectedTags=${JSON.stringify(
|
||||
selectedTags,
|
||||
)}&filter=${JSON.stringify(Object.fromEntries(filter))}&${preResult
|
||||
.map((e) => `${e.key}=${e.value}`)
|
||||
.join('&')}&isFilterExclude=${JSON.stringify(
|
||||
Object.fromEntries(isFilterExclude),
|
||||
)}&userSelectedFilter=${JSON.stringify(
|
||||
Object.fromEntries(userSelectedFilter),
|
||||
)}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const getFilter = (data: GetFilterPayload): TraceReducer['filter'] => {
|
||||
const filter = new Map<TraceFilterEnum, Record<string, string>>();
|
||||
|
||||
Object.keys(data).forEach((key) => {
|
||||
const value = data[key];
|
||||
if (isTraceFilterEnum(key)) {
|
||||
filter.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
return filter;
|
||||
};
|
@ -5,16 +5,15 @@ import dashboardReducer from './dashboard';
|
||||
import globalTimeReducer from './global';
|
||||
import metricsReducers from './metric';
|
||||
import { ServiceMapReducer } from './serviceMap';
|
||||
import { traceReducer } from './trace';
|
||||
import traceReducer from './trace';
|
||||
import TraceFilterReducer from './traceFilters';
|
||||
import { traceItemReducer, tracesReducer } from './traces';
|
||||
import { traceItemReducer } from './traces';
|
||||
import { usageDataReducer } from './usage';
|
||||
|
||||
const reducers = combineReducers({
|
||||
traceFilters: TraceFilterReducer,
|
||||
traces: tracesReducer,
|
||||
traces: traceReducer,
|
||||
traceItem: traceItemReducer,
|
||||
trace: traceReducer,
|
||||
usageDate: usageDataReducer,
|
||||
globalTime: globalTimeReducer,
|
||||
serviceMap: ServiceMapReducer,
|
||||
|
@ -1,200 +1,199 @@
|
||||
import {
|
||||
GET_TRACE_INITIAL_DATA_ERROR,
|
||||
GET_TRACE_INITIAL_DATA_SUCCESS,
|
||||
GET_TRACE_LOADING_END,
|
||||
GET_TRACE_LOADING_START,
|
||||
RESET_TRACE_DATA,
|
||||
SELECT_TRACE_FILTER,
|
||||
TraceActions,
|
||||
UPDATE_AGGREGATES,
|
||||
UPDATE_SELECTED_AGG_OPTION,
|
||||
UPDATE_SELECTED_ENTITY,
|
||||
UPDATE_SELECTED_TRACE_DATA,
|
||||
UPDATE_SPANS_LOADING,
|
||||
UPDATE_TRACE_SELECTED_KIND,
|
||||
UPDATE_TRACE_SELECTED_LATENCY_VALUE,
|
||||
UPDATE_TRACE_SELECTED_OPERATION,
|
||||
UPDATE_TRACE_SELECTED_SERVICE,
|
||||
UPDATE_TRACE_SELECTED_TAGS,
|
||||
UPDATE_TRACE_FILTER,
|
||||
UPDATE_TRACE_FILTER_LOADING,
|
||||
UPDATE_ALL_FILTERS,
|
||||
UPDATE_SELECTED_TAGS,
|
||||
UPDATE_SPANS_AGGREEGATE,
|
||||
UPDATE_TAG_MODAL_VISIBLITY,
|
||||
UPDATE_IS_TAG_ERROR,
|
||||
UPDATE_SELECTED_FUNCTION,
|
||||
UPDATE_SELECTED_GROUP_BY,
|
||||
UPDATE_TRACE_GRAPH_LOADING,
|
||||
UPDATE_TRACE_GRAPH_ERROR,
|
||||
UPDATE_TRACE_GRAPH_SUCCESS,
|
||||
RESET_TRACE_FILTER,
|
||||
UPDATE_FILTER_RESPONSE_SELECTED,
|
||||
UPDATE_FILTER_EXCLUDE,
|
||||
} from 'types/actions/trace';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
const intitalState: TraceReducer = {
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
operationsList: [],
|
||||
selectedKind: '',
|
||||
selectedLatency: {
|
||||
max: '',
|
||||
min: '',
|
||||
},
|
||||
selectedOperation: '',
|
||||
selectedService: '',
|
||||
const initialValue: TraceReducer = {
|
||||
filter: new Map(),
|
||||
filterToFetchData: ['duration', 'status', 'serviceName'],
|
||||
filterLoading: true,
|
||||
filterResponseSelected: new Set(),
|
||||
selectedFilter: new Map(),
|
||||
selectedTags: [],
|
||||
serviceList: [],
|
||||
spanList: [],
|
||||
tagsSuggestions: [],
|
||||
selectedAggOption: 'count',
|
||||
selectedEntity: 'calls',
|
||||
spansAggregate: [],
|
||||
spansLoading: false,
|
||||
isTagModalOpen: false,
|
||||
isTagModalError: false,
|
||||
isFilterExclude: new Map<TraceFilterEnum, boolean>([]),
|
||||
userSelectedFilter: new Map(),
|
||||
spansAggregate: {
|
||||
currentPage: 1,
|
||||
loading: false,
|
||||
data: [],
|
||||
error: false,
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
},
|
||||
selectedGroupBy: '',
|
||||
selectedFunction: 'count',
|
||||
spansGraph: {
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: true,
|
||||
payload: { items: {} },
|
||||
},
|
||||
};
|
||||
|
||||
export const traceReducer = (
|
||||
state = intitalState,
|
||||
const traceReducer = (
|
||||
state = initialValue,
|
||||
action: TraceActions,
|
||||
): TraceReducer => {
|
||||
switch (action.type) {
|
||||
case GET_TRACE_INITIAL_DATA_ERROR: {
|
||||
case UPDATE_TRACE_FILTER: {
|
||||
return {
|
||||
...state,
|
||||
errorMessage: action.payload.errorMessage,
|
||||
loading: false,
|
||||
error: true,
|
||||
filter: action.payload.filter,
|
||||
};
|
||||
}
|
||||
|
||||
case GET_TRACE_LOADING_START: {
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
spansLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
case GET_TRACE_INITIAL_DATA_SUCCESS: {
|
||||
case UPDATE_ALL_FILTERS: {
|
||||
const { payload } = action;
|
||||
const {
|
||||
serviceList,
|
||||
operationList,
|
||||
tagsSuggestions,
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
filter,
|
||||
filterToFetchData,
|
||||
selectedFilter,
|
||||
current,
|
||||
selectedTags,
|
||||
spansList,
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
spansAggregate,
|
||||
} = action.payload;
|
||||
userSelected,
|
||||
isFilterExclude,
|
||||
} = payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
serviceList: serviceList,
|
||||
tagsSuggestions,
|
||||
selectedOperation,
|
||||
selectedService,
|
||||
filter,
|
||||
filterToFetchData,
|
||||
selectedFilter,
|
||||
selectedTags,
|
||||
spanList: spansList,
|
||||
operationsList: operationList,
|
||||
error: false,
|
||||
selectedKind,
|
||||
selectedLatency,
|
||||
spansAggregate,
|
||||
spansLoading: false,
|
||||
userSelectedFilter: userSelected,
|
||||
isFilterExclude,
|
||||
spansAggregate: {
|
||||
...state.spansAggregate,
|
||||
currentPage: current,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TRACE_SELECTED_KIND: {
|
||||
case UPDATE_TRACE_FILTER_LOADING: {
|
||||
return {
|
||||
...state,
|
||||
selectedKind: action.payload.selectedKind,
|
||||
filterLoading: action.payload.filterLoading,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TRACE_SELECTED_LATENCY_VALUE: {
|
||||
case SELECT_TRACE_FILTER: {
|
||||
return {
|
||||
...state,
|
||||
selectedLatency: action.payload.selectedLatency,
|
||||
selectedFilter: action.payload.selectedFilter,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TRACE_SELECTED_OPERATION: {
|
||||
case RESET_TRACE_FILTER: {
|
||||
return {
|
||||
...state,
|
||||
selectedOperation: action.payload.selectedOperation,
|
||||
...initialValue,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TRACE_SELECTED_SERVICE: {
|
||||
return {
|
||||
...state,
|
||||
selectedService: action.payload.selectedService,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TRACE_SELECTED_TAGS: {
|
||||
case UPDATE_SELECTED_TAGS: {
|
||||
return {
|
||||
...state,
|
||||
selectedTags: action.payload.selectedTags,
|
||||
spanList: action.payload.spansList,
|
||||
spansAggregate: action.payload.spansAggregate,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SELECTED_TRACE_DATA: {
|
||||
const {
|
||||
spansList,
|
||||
tagsSuggestions,
|
||||
operationList,
|
||||
selectedOperation,
|
||||
selectedLatency,
|
||||
selectedService,
|
||||
selectedKind,
|
||||
spansAggregate,
|
||||
} = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
spanList: spansList,
|
||||
tagsSuggestions,
|
||||
operationsList: operationList,
|
||||
selectedOperation,
|
||||
selectedLatency,
|
||||
selectedService,
|
||||
selectedKind,
|
||||
spansAggregate,
|
||||
};
|
||||
}
|
||||
|
||||
case GET_TRACE_LOADING_END: {
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SELECTED_AGG_OPTION: {
|
||||
return {
|
||||
...state,
|
||||
selectedAggOption: action.payload.selectedAggOption,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SELECTED_ENTITY: {
|
||||
return {
|
||||
...state,
|
||||
selectedEntity: action.payload.selectedEntity,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SPANS_LOADING: {
|
||||
return {
|
||||
...state,
|
||||
spansLoading: action.payload.loading,
|
||||
};
|
||||
}
|
||||
|
||||
case RESET_TRACE_DATA: {
|
||||
return {
|
||||
...intitalState,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_AGGREGATES: {
|
||||
case UPDATE_SPANS_AGGREEGATE: {
|
||||
return {
|
||||
...state,
|
||||
spansAggregate: action.payload.spansAggregate,
|
||||
selectedAggOption: action.payload.selectedAggOption,
|
||||
selectedEntity: action.payload.selectedEntity,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TAG_MODAL_VISIBLITY: {
|
||||
return {
|
||||
...state,
|
||||
isTagModalOpen: action.payload.isTagModalOpen,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_IS_TAG_ERROR: {
|
||||
return {
|
||||
...state,
|
||||
isTagModalError: action.payload.isTagModalError,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SELECTED_FUNCTION: {
|
||||
return {
|
||||
...state,
|
||||
selectedFunction: action.payload.selectedFunction,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_SELECTED_GROUP_BY: {
|
||||
return {
|
||||
...state,
|
||||
selectedGroupBy: action.payload.selectedGroupBy,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TRACE_GRAPH_LOADING: {
|
||||
return {
|
||||
...state,
|
||||
spansGraph: {
|
||||
...state.spansGraph,
|
||||
loading: action.payload.loading,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TRACE_GRAPH_ERROR: {
|
||||
return {
|
||||
...state,
|
||||
spansGraph: {
|
||||
...state.spansGraph,
|
||||
error: action.payload.error,
|
||||
errorMessage: action.payload.errorMessage,
|
||||
loading: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_TRACE_GRAPH_SUCCESS: {
|
||||
return {
|
||||
...state,
|
||||
spansGraph: {
|
||||
...state.spansGraph,
|
||||
payload: action.payload.data,
|
||||
loading: false,
|
||||
error: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_FILTER_RESPONSE_SELECTED: {
|
||||
return {
|
||||
...state,
|
||||
filterResponseSelected: action.payload.filterResponseSelected,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_FILTER_EXCLUDE: {
|
||||
return {
|
||||
...state,
|
||||
isFilterExclude: action.payload.isFilterExclude,
|
||||
};
|
||||
}
|
||||
|
||||
@ -202,3 +201,5 @@ export const traceReducer = (
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default traceReducer;
|
||||
|
@ -1,151 +1,172 @@
|
||||
export const GET_TRACE_INITIAL_DATA_SUCCESS = 'GET_TRACE_INITIAL_DATA_SUCCESS';
|
||||
export const GET_TRACE_INITIAL_DATA_ERROR = 'GET_TRACE_INITIAL_DATA_ERROR';
|
||||
export const GET_TRACE_LOADING_START = 'GET_TRACE_LOADING_START';
|
||||
export const GET_TRACE_LOADING_END = 'GET_TRACE_LOADING_END';
|
||||
|
||||
export const UPDATE_TRACE_SELECTED_SERVICE = 'UPDATE_TRACE_SELECTED_SERVICE';
|
||||
export const UPDATE_TRACE_SELECTED_OPERATION =
|
||||
'UPDATE_TRACE_SELECTED_OPERATION';
|
||||
export const UPDATE_TRACE_SELECTED_LATENCY_VALUE =
|
||||
'UPDATE_TRACE_SELECTED_LATENCY_VALUE';
|
||||
export const UPDATE_TRACE_SELECTED_KIND = 'UPDATE_TRACE_SELECTED_KIND';
|
||||
export const UPDATE_TRACE_SELECTED_TAGS = 'UPDATE_TRACE_SELECTED_TAGS';
|
||||
|
||||
export const UPDATE_SELECTED_AGG_OPTION = 'UPDATE_SELECTED_AGG_OPTION';
|
||||
export const UPDATE_SELECTED_ENTITY = 'UPDATE_SELECTED_ENTITY';
|
||||
export const UPDATE_SPANS_LOADING = 'UPDATE_SPANS_LOADING';
|
||||
|
||||
export const UPDATE_SELECTED_TRACE_DATA = 'UPDATE_SELECTED_TRACE_DATA';
|
||||
export const UPDATE_AGGREGATES = 'UPDATE_AGGREGATES';
|
||||
|
||||
export const RESET_TRACE_DATA = 'RESET_TRACE_DATA';
|
||||
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
interface GetTraceLoading {
|
||||
type: typeof GET_TRACE_LOADING_START | typeof GET_TRACE_LOADING_END;
|
||||
}
|
||||
export const UPDATE_TRACE_FILTER = 'UPDATE_TRACE_FILTER';
|
||||
export const GET_TRACE_FILTER = 'GET_TRACE_FILTER';
|
||||
export const UPDATE_TRACE_FILTER_LOADING = 'UPDATE_TRACE_FILTER_LOADING';
|
||||
|
||||
interface UpdateSpansLoading {
|
||||
type: typeof UPDATE_SPANS_LOADING;
|
||||
export const SELECT_TRACE_FILTER = 'SELECT_TRACE_FILTER';
|
||||
export const UPDATE_ALL_FILTERS = 'UPDATE_ALL_FILTERS';
|
||||
export const UPDATE_SELECTED_TAGS = 'UPDATE_SELECTED_TAGS';
|
||||
export const UPDATE_TAG_MODAL_VISIBLITY = 'UPDATE_TAG_MODAL_VISIBLITY';
|
||||
|
||||
export const UPDATE_SPANS_AGGREEGATE = 'UPDATE_SPANS_AGGREEGATE';
|
||||
|
||||
export const UPDATE_IS_TAG_ERROR = 'UPDATE_IS_TAG_ERROR';
|
||||
|
||||
export const UPDATE_SELECTED_FUNCTION = 'UPDATE_SELECTED_FUNCTION';
|
||||
export const UPDATE_SELECTED_GROUP_BY = 'UPDATE_SELECTED_GROUP_BY';
|
||||
|
||||
export const UPDATE_TRACE_GRAPH_LOADING = 'UPDATE_TRACE_GRAPH_LOADING';
|
||||
export const UPDATE_TRACE_GRAPH_ERROR = 'UPDATE_TRACE_GRAPH_ERROR';
|
||||
export const UPDATE_TRACE_GRAPH_SUCCESS = 'UPDATE_TRACE_GRAPH_SUCCESS';
|
||||
|
||||
export const RESET_TRACE_FILTER = 'RESET_TRACE_FILTER';
|
||||
export const UPDATE_FILTER_RESPONSE_SELECTED =
|
||||
'UPDATE_FILTER_RESPONSE_SELECTED';
|
||||
export const UPDATE_FILTER_EXCLUDE = 'UPDATE_FILTER_EXCLUDE';
|
||||
|
||||
export interface UpdateFilter {
|
||||
type: typeof UPDATE_TRACE_FILTER;
|
||||
payload: {
|
||||
loading: boolean;
|
||||
filter: TraceReducer['filter'];
|
||||
};
|
||||
}
|
||||
|
||||
interface GetTraceInitialData {
|
||||
type: typeof GET_TRACE_INITIAL_DATA_SUCCESS;
|
||||
export interface UpdateSpansAggregate {
|
||||
type: typeof UPDATE_SPANS_AGGREEGATE;
|
||||
payload: {
|
||||
serviceList: TraceReducer['serviceList'];
|
||||
selectedTags: TraceReducer['selectedTags'];
|
||||
operationList: TraceReducer['operationsList'];
|
||||
tagsSuggestions: TraceReducer['tagsSuggestions'];
|
||||
spansList: TraceReducer['spanList'];
|
||||
selectedService: TraceReducer['selectedService'];
|
||||
selectedOperation: TraceReducer['selectedOperation'];
|
||||
selectedLatency: TraceReducer['selectedLatency'];
|
||||
selectedKind: TraceReducer['selectedKind'];
|
||||
spansAggregate: TraceReducer['spansAggregate'];
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateSelectedDate {
|
||||
type: typeof UPDATE_SELECTED_TRACE_DATA;
|
||||
export interface UpdateTagVisiblity {
|
||||
type: typeof UPDATE_TAG_MODAL_VISIBLITY;
|
||||
payload: {
|
||||
operationList: TraceReducer['operationsList'];
|
||||
tagsSuggestions: TraceReducer['tagsSuggestions'];
|
||||
spansList: TraceReducer['spanList'];
|
||||
selectedKind: TraceReducer['selectedKind'];
|
||||
selectedService: TraceReducer['selectedService'];
|
||||
selectedLatency: TraceReducer['selectedLatency'];
|
||||
selectedOperation: TraceReducer['selectedOperation'];
|
||||
spansAggregate: TraceReducer['spansAggregate'];
|
||||
isTagModalOpen: TraceReducer['isTagModalOpen'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface GetTraceInitialDataError {
|
||||
type: typeof GET_TRACE_INITIAL_DATA_ERROR;
|
||||
payload: {
|
||||
errorMessage: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateTraceSelectedService {
|
||||
type: typeof UPDATE_TRACE_SELECTED_SERVICE;
|
||||
payload: {
|
||||
selectedService: TraceReducer['selectedService'];
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateTraceSelectedOperation {
|
||||
type: typeof UPDATE_TRACE_SELECTED_OPERATION;
|
||||
payload: {
|
||||
selectedOperation: TraceReducer['selectedOperation'];
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateTraceSelectedKind {
|
||||
type: typeof UPDATE_TRACE_SELECTED_KIND;
|
||||
payload: {
|
||||
selectedKind: TraceReducer['selectedKind'];
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateTraceSelectedLatencyValue {
|
||||
type: typeof UPDATE_TRACE_SELECTED_LATENCY_VALUE;
|
||||
payload: {
|
||||
selectedLatency: TraceReducer['selectedLatency'];
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateTraceSelectedTags {
|
||||
type: typeof UPDATE_TRACE_SELECTED_TAGS;
|
||||
export interface UpdateSelectedTags {
|
||||
type: typeof UPDATE_SELECTED_TAGS;
|
||||
payload: {
|
||||
selectedTags: TraceReducer['selectedTags'];
|
||||
spansList: TraceReducer['spanList'];
|
||||
spansAggregate: TraceReducer['spansAggregate'];
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateSelectedAggOption {
|
||||
type: typeof UPDATE_SELECTED_AGG_OPTION;
|
||||
export interface UpdateSelected {
|
||||
type: typeof UPDATE_FILTER_RESPONSE_SELECTED;
|
||||
payload: {
|
||||
selectedAggOption: TraceReducer['selectedAggOption'];
|
||||
filterResponseSelected: TraceReducer['filterResponseSelected'];
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateSelectedEntity {
|
||||
type: typeof UPDATE_SELECTED_ENTITY;
|
||||
export interface UpdateAllFilters {
|
||||
type: typeof UPDATE_ALL_FILTERS;
|
||||
payload: {
|
||||
selectedEntity: TraceReducer['selectedEntity'];
|
||||
filter: TraceReducer['filter'];
|
||||
selectedFilter: TraceReducer['selectedFilter'];
|
||||
filterToFetchData: TraceReducer['filterToFetchData'];
|
||||
current: TraceReducer['spansAggregate']['currentPage'];
|
||||
selectedTags: TraceReducer['selectedTags'];
|
||||
userSelected: TraceReducer['userSelectedFilter'];
|
||||
isFilterExclude: TraceReducer['isFilterExclude'];
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateAggregates {
|
||||
type: typeof UPDATE_AGGREGATES;
|
||||
export interface UpdateFilterLoading {
|
||||
type: typeof UPDATE_TRACE_FILTER_LOADING;
|
||||
payload: {
|
||||
spansAggregate: TraceReducer['spansAggregate'];
|
||||
selectedEntity: TraceReducer['selectedEntity'];
|
||||
selectedAggOption: TraceReducer['selectedAggOption'];
|
||||
filterLoading: TraceReducer['filterLoading'];
|
||||
};
|
||||
}
|
||||
|
||||
interface ResetTraceData {
|
||||
type: typeof RESET_TRACE_DATA;
|
||||
export interface SelectTraceFilter {
|
||||
type: typeof SELECT_TRACE_FILTER;
|
||||
payload: {
|
||||
selectedFilter: TraceReducer['selectedFilter'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResetTraceFilter {
|
||||
type: typeof RESET_TRACE_FILTER;
|
||||
}
|
||||
|
||||
export interface GetTraceFilter {
|
||||
type: typeof GET_TRACE_FILTER;
|
||||
payload: {
|
||||
filter: TraceReducer['filter'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateIsTagError {
|
||||
type: typeof UPDATE_IS_TAG_ERROR;
|
||||
payload: {
|
||||
isTagModalError: TraceReducer['isTagModalError'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateSelectedGroupBy {
|
||||
type: typeof UPDATE_SELECTED_GROUP_BY;
|
||||
payload: {
|
||||
selectedGroupBy: TraceReducer['selectedGroupBy'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateSelectedFunction {
|
||||
type: typeof UPDATE_SELECTED_FUNCTION;
|
||||
payload: {
|
||||
selectedFunction: TraceReducer['selectedFunction'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateSpanLoading {
|
||||
type: typeof UPDATE_TRACE_GRAPH_LOADING;
|
||||
payload: {
|
||||
loading: TraceReducer['spansGraph']['loading'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateSpansError {
|
||||
type: typeof UPDATE_TRACE_GRAPH_ERROR;
|
||||
payload: {
|
||||
error: TraceReducer['spansGraph']['error'];
|
||||
errorMessage: TraceReducer['spansGraph']['errorMessage'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateFilterExclude {
|
||||
type: typeof UPDATE_FILTER_EXCLUDE;
|
||||
payload: {
|
||||
isFilterExclude: TraceReducer['isFilterExclude'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateSpans {
|
||||
type: typeof UPDATE_TRACE_GRAPH_SUCCESS;
|
||||
payload: {
|
||||
data: TraceReducer['spansGraph']['payload'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResetTraceFilter {
|
||||
type: typeof RESET_TRACE_FILTER;
|
||||
}
|
||||
|
||||
export type TraceActions =
|
||||
| GetTraceLoading
|
||||
| GetTraceInitialData
|
||||
| GetTraceInitialDataError
|
||||
| UpdateTraceSelectedService
|
||||
| UpdateTraceSelectedLatencyValue
|
||||
| UpdateTraceSelectedKind
|
||||
| UpdateTraceSelectedOperation
|
||||
| UpdateTraceSelectedTags
|
||||
| UpdateSelectedDate
|
||||
| UpdateSelectedAggOption
|
||||
| UpdateSelectedEntity
|
||||
| UpdateSpansLoading
|
||||
| ResetTraceData
|
||||
| UpdateAggregates;
|
||||
| UpdateFilter
|
||||
| GetTraceFilter
|
||||
| UpdateFilterLoading
|
||||
| SelectTraceFilter
|
||||
| UpdateAllFilters
|
||||
| UpdateSelectedTags
|
||||
| UpdateTagVisiblity
|
||||
| UpdateSpansAggregate
|
||||
| UpdateIsTagError
|
||||
| UpdateSelectedGroupBy
|
||||
| UpdateSelectedFunction
|
||||
| UpdateSpanLoading
|
||||
| UpdateSpansError
|
||||
| UpdateSpans
|
||||
| ResetTraceFilter
|
||||
| UpdateSelected
|
||||
| UpdateFilterExclude;
|
||||
|
15
frontend/src/types/api/trace/getFilters.ts
Normal file
15
frontend/src/types/api/trace/getFilters.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export interface Props {
|
||||
start: string;
|
||||
end: string;
|
||||
getFilters: string[];
|
||||
other: {
|
||||
[k: string]: string[];
|
||||
};
|
||||
isFilterExclude: TraceReducer['isFilterExclude'];
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
[key: string]: Record<string, string>;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export type PayloadProps = string[];
|
@ -1,5 +0,0 @@
|
||||
export type PayloadProps = string[];
|
||||
|
||||
export interface Props {
|
||||
service: string;
|
||||
}
|
@ -1,20 +1,16 @@
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
|
||||
export interface Props {
|
||||
start: number;
|
||||
end: number;
|
||||
service: string;
|
||||
operation: string;
|
||||
maxDuration: string;
|
||||
minDuration: string;
|
||||
kind: string;
|
||||
tags: string;
|
||||
dimension: string;
|
||||
aggregation_option: string;
|
||||
step: string;
|
||||
selectedFilter: TraceReducer['selectedFilter'];
|
||||
limit: number;
|
||||
offset: number;
|
||||
selectedTags: TraceReducer['selectedTags'];
|
||||
isFilterExclude: TraceReducer['isFilterExclude'];
|
||||
}
|
||||
|
||||
interface Timestamp {
|
||||
timestamp: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export type PayloadProps = Timestamp[];
|
||||
export type PayloadProps = {
|
||||
spans: TraceReducer['spansAggregate']['data'];
|
||||
totalSpans: number;
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user