mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 17:45:55 +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": "^6.2.0",
|
||||||
"d3-flame-graph": "^3.1.1",
|
"d3-flame-graph": "^3.1.1",
|
||||||
"d3-tip": "^0.9.1",
|
"d3-tip": "^0.9.1",
|
||||||
|
"dayjs": "^1.10.7",
|
||||||
"dotenv": "8.2.0",
|
"dotenv": "8.2.0",
|
||||||
"file-loader": "6.1.1",
|
"file-loader": "6.1.1",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"html-webpack-plugin": "5.1.0",
|
"html-webpack-plugin": "5.1.0",
|
||||||
"jest": "26.6.0",
|
"jest": "26.6.0",
|
||||||
|
"less": "^4.1.2",
|
||||||
|
"less-loader": "^10.2.0",
|
||||||
"mini-css-extract-plugin": "2.4.5",
|
"mini-css-extract-plugin": "2.4.5",
|
||||||
"monaco-editor": "^0.30.0",
|
"monaco-editor": "^0.30.0",
|
||||||
"react": "17.0.0",
|
"react": "17.0.0",
|
||||||
|
@ -19,7 +19,7 @@ export const ServiceMapPage = Loadable(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const TraceDetailPages = Loadable(
|
export const TraceDetailPages = Loadable(
|
||||||
() => import(/* webpackChunkName: "TraceDetailPage" */ 'pages/TraceDetails'),
|
() => import(/* webpackChunkName: "TraceDetailPage" */ 'pages/Trace'),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const TraceGraphPage = Loadable(
|
export const TraceGraphPage = Loadable(
|
||||||
|
@ -3,14 +3,13 @@ import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
import { PayloadProps, Props } from 'types/api/alerts/getGroups';
|
import { PayloadProps, Props } from 'types/api/alerts/getGroups';
|
||||||
|
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||||
|
|
||||||
const getGroups = async (
|
const getGroups = async (
|
||||||
props: Props,
|
props: Props,
|
||||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
const queryParams = Object.keys(props)
|
const queryParams = convertObjectIntoParams(props);
|
||||||
.map((e) => `${e}=${props[e]}`)
|
|
||||||
.join('&');
|
|
||||||
|
|
||||||
const response = await AxiosAlertManagerInstance.get(
|
const response = await AxiosAlertManagerInstance.get(
|
||||||
`/alerts/groups?${queryParams}`,
|
`/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(() => {
|
useEffect(() => {
|
||||||
buildChart();
|
buildChart();
|
||||||
}, [buildChart]);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '85%' }}>
|
<div style={{ height: '85%' }}>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { DatePicker, Modal } from 'antd';
|
import { Modal } from 'antd';
|
||||||
import { Moment } from 'moment';
|
|
||||||
import moment from 'moment';
|
|
||||||
import React, { useState } from 'react';
|
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;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
@ -20,8 +20,8 @@ const CustomDateTimeModal = ({
|
|||||||
setCustomDateTimeRange(date_time);
|
setCustomDateTimeRange(date_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disabledDate(current: Moment): boolean {
|
function disabledDate(current: Dayjs): boolean {
|
||||||
if (current > moment()) {
|
if (current > dayjs()) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -8,7 +8,7 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
|
|||||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||||
import { LOCAL_STORAGE } from 'constants/localStorage';
|
import { LOCAL_STORAGE } from 'constants/localStorage';
|
||||||
import getTimeString from 'lib/getTimeString';
|
import getTimeString from 'lib/getTimeString';
|
||||||
import moment from 'moment';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { connect, useSelector } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
import { RouteComponentProps, withRouter } from 'react-router';
|
import { RouteComponentProps, withRouter } from 'react-router';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
@ -37,26 +37,18 @@ const DateTimeSelection = ({
|
|||||||
|
|
||||||
const getTime = useCallback((): [number, number] | undefined => {
|
const getTime = useCallback((): [number, number] | undefined => {
|
||||||
if (searchEndTime && searchStartTime) {
|
if (searchEndTime && searchStartTime) {
|
||||||
const startMoment = moment(
|
const startDate = dayjs(
|
||||||
new Date(parseInt(getTimeString(searchStartTime), 10)),
|
new Date(parseInt(getTimeString(searchStartTime), 10)),
|
||||||
);
|
);
|
||||||
const endMoment = moment(
|
const endDate = dayjs(new Date(parseInt(getTimeString(searchEndTime), 10)));
|
||||||
new Date(parseInt(getTimeString(searchEndTime), 10)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return [
|
return [startDate.toDate().getTime() || 0, endDate.toDate().getTime() || 0];
|
||||||
startMoment.toDate().getTime() || 0,
|
|
||||||
endMoment.toDate().getTime() || 0,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (localstorageStartTime && localstorageEndTime) {
|
if (localstorageStartTime && localstorageEndTime) {
|
||||||
const startMoment = moment(localstorageStartTime);
|
const startDate = dayjs(localstorageStartTime);
|
||||||
const endMoment = moment(localstorageEndTime);
|
const endDate = dayjs(localstorageEndTime);
|
||||||
|
|
||||||
return [
|
return [startDate.toDate().getTime() || 0, endDate.toDate().getTime() || 0];
|
||||||
startMoment.toDate().getTime() || 0,
|
|
||||||
endMoment.toDate().getTime() || 0,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [
|
}, [
|
||||||
@ -66,8 +58,8 @@ const DateTimeSelection = ({
|
|||||||
searchStartTime,
|
searchStartTime,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [startTime, setStartTime] = useState<moment.Moment>();
|
const [startTime, setStartTime] = useState<Dayjs>();
|
||||||
const [endTime, setEndTime] = useState<moment.Moment>();
|
const [endTime, setEndTime] = useState<Dayjs>();
|
||||||
|
|
||||||
const [options, setOptions] = useState(getOptions(location.pathname));
|
const [options, setOptions] = useState(getOptions(location.pathname));
|
||||||
const [refreshButtonHidden, setRefreshButtonHidden] = useState<boolean>(false);
|
const [refreshButtonHidden, setRefreshButtonHidden] = useState<boolean>(false);
|
||||||
@ -136,8 +128,8 @@ const DateTimeSelection = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getInputLabel = (
|
const getInputLabel = (
|
||||||
startTime?: moment.Moment,
|
startTime?: Dayjs,
|
||||||
endTime?: moment.Moment,
|
endTime?: Dayjs,
|
||||||
timeInterval: Time = '15min',
|
timeInterval: Time = '15min',
|
||||||
): string | Time => {
|
): string | Time => {
|
||||||
if (startTime && endTime && timeInterval === 'custom') {
|
if (startTime && endTime && timeInterval === 'custom') {
|
||||||
@ -153,18 +145,18 @@ const DateTimeSelection = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onLastRefreshHandler = useCallback(() => {
|
const onLastRefreshHandler = useCallback(() => {
|
||||||
const currentTime = moment();
|
const currentTime = dayjs();
|
||||||
|
|
||||||
const lastRefresh = moment(
|
const lastRefresh = dayjs(
|
||||||
selectedTimeInterval === 'custom' ? minTime / 1000000 : maxTime / 1000000,
|
selectedTimeInterval === 'custom' ? minTime / 1000000 : maxTime / 1000000,
|
||||||
);
|
);
|
||||||
const duration = moment.duration(currentTime.diff(lastRefresh));
|
|
||||||
|
|
||||||
const secondsDiff = Math.floor(duration.asSeconds());
|
const secondsDiff = currentTime.diff(lastRefresh, 'seconds');
|
||||||
const minutedDiff = Math.floor(duration.asMinutes());
|
|
||||||
const hoursDiff = Math.floor(duration.asHours());
|
const minutedDiff = currentTime.diff(lastRefresh, 'minutes');
|
||||||
const daysDiff = Math.floor(duration.asDays());
|
const hoursDiff = currentTime.diff(lastRefresh, 'hours');
|
||||||
const monthsDiff = Math.floor(duration.asMonths());
|
const daysDiff = currentTime.diff(lastRefresh, 'days');
|
||||||
|
const monthsDiff = currentTime.diff(lastRefresh, 'months');
|
||||||
|
|
||||||
if (monthsDiff > 0) {
|
if (monthsDiff > 0) {
|
||||||
return `Last refresh -${monthsDiff} months ago`;
|
return `Last refresh -${monthsDiff} months ago`;
|
||||||
@ -242,8 +234,8 @@ const DateTimeSelection = ({
|
|||||||
|
|
||||||
const [preStartTime = 0, preEndTime = 0] = getTime() || [];
|
const [preStartTime = 0, preEndTime = 0] = getTime() || [];
|
||||||
|
|
||||||
setStartTime(moment(preStartTime));
|
setStartTime(dayjs(preStartTime));
|
||||||
setEndTime(moment(preEndTime));
|
setEndTime(dayjs(preEndTime));
|
||||||
|
|
||||||
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
||||||
}, [
|
}, [
|
||||||
@ -318,8 +310,3 @@ const mapDispatchToProps = (
|
|||||||
type Props = DispatchProps & RouteComponentProps;
|
type Props = DispatchProps & RouteComponentProps;
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(withRouter(DateTimeSelection));
|
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();
|
const urlParams = new URLSearchParams();
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.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 (
|
const onClickhandler = async (
|
||||||
@ -85,12 +86,12 @@ const Application = ({ getWidget }: DashboardProps): JSX.Element => {
|
|||||||
const urlParams = new URLSearchParams();
|
const urlParams = new URLSearchParams();
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.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 (
|
return (
|
||||||
|
@ -28,12 +28,12 @@ const TopEndpointsTable = (props: TopEndpointsTableProps): JSX.Element => {
|
|||||||
METRICS_PAGE_QUERY_PARAM.endTime,
|
METRICS_PAGE_QUERY_PARAM.endTime,
|
||||||
(maxTime / 1000000).toString(),
|
(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> = [
|
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 globalTimeReducer from './global';
|
||||||
import metricsReducers from './metric';
|
import metricsReducers from './metric';
|
||||||
import { ServiceMapReducer } from './serviceMap';
|
import { ServiceMapReducer } from './serviceMap';
|
||||||
import { traceReducer } from './trace';
|
import traceReducer from './trace';
|
||||||
import TraceFilterReducer from './traceFilters';
|
import TraceFilterReducer from './traceFilters';
|
||||||
import { traceItemReducer, tracesReducer } from './traces';
|
import { traceItemReducer } from './traces';
|
||||||
import { usageDataReducer } from './usage';
|
import { usageDataReducer } from './usage';
|
||||||
|
|
||||||
const reducers = combineReducers({
|
const reducers = combineReducers({
|
||||||
traceFilters: TraceFilterReducer,
|
traceFilters: TraceFilterReducer,
|
||||||
traces: tracesReducer,
|
traces: traceReducer,
|
||||||
traceItem: traceItemReducer,
|
traceItem: traceItemReducer,
|
||||||
trace: traceReducer,
|
|
||||||
usageDate: usageDataReducer,
|
usageDate: usageDataReducer,
|
||||||
globalTime: globalTimeReducer,
|
globalTime: globalTimeReducer,
|
||||||
serviceMap: ServiceMapReducer,
|
serviceMap: ServiceMapReducer,
|
||||||
|
@ -1,200 +1,199 @@
|
|||||||
import {
|
import {
|
||||||
GET_TRACE_INITIAL_DATA_ERROR,
|
SELECT_TRACE_FILTER,
|
||||||
GET_TRACE_INITIAL_DATA_SUCCESS,
|
|
||||||
GET_TRACE_LOADING_END,
|
|
||||||
GET_TRACE_LOADING_START,
|
|
||||||
RESET_TRACE_DATA,
|
|
||||||
TraceActions,
|
TraceActions,
|
||||||
UPDATE_AGGREGATES,
|
UPDATE_TRACE_FILTER,
|
||||||
UPDATE_SELECTED_AGG_OPTION,
|
UPDATE_TRACE_FILTER_LOADING,
|
||||||
UPDATE_SELECTED_ENTITY,
|
UPDATE_ALL_FILTERS,
|
||||||
UPDATE_SELECTED_TRACE_DATA,
|
UPDATE_SELECTED_TAGS,
|
||||||
UPDATE_SPANS_LOADING,
|
UPDATE_SPANS_AGGREEGATE,
|
||||||
UPDATE_TRACE_SELECTED_KIND,
|
UPDATE_TAG_MODAL_VISIBLITY,
|
||||||
UPDATE_TRACE_SELECTED_LATENCY_VALUE,
|
UPDATE_IS_TAG_ERROR,
|
||||||
UPDATE_TRACE_SELECTED_OPERATION,
|
UPDATE_SELECTED_FUNCTION,
|
||||||
UPDATE_TRACE_SELECTED_SERVICE,
|
UPDATE_SELECTED_GROUP_BY,
|
||||||
UPDATE_TRACE_SELECTED_TAGS,
|
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';
|
} from 'types/actions/trace';
|
||||||
import { TraceReducer } from 'types/reducer/trace';
|
import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace';
|
||||||
|
|
||||||
const intitalState: TraceReducer = {
|
const initialValue: TraceReducer = {
|
||||||
|
filter: new Map(),
|
||||||
|
filterToFetchData: ['duration', 'status', 'serviceName'],
|
||||||
|
filterLoading: true,
|
||||||
|
filterResponseSelected: new Set(),
|
||||||
|
selectedFilter: new Map(),
|
||||||
|
selectedTags: [],
|
||||||
|
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,
|
error: false,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
loading: true,
|
loading: true,
|
||||||
operationsList: [],
|
payload: { items: {} },
|
||||||
selectedKind: '',
|
|
||||||
selectedLatency: {
|
|
||||||
max: '',
|
|
||||||
min: '',
|
|
||||||
},
|
},
|
||||||
selectedOperation: '',
|
|
||||||
selectedService: '',
|
|
||||||
selectedTags: [],
|
|
||||||
serviceList: [],
|
|
||||||
spanList: [],
|
|
||||||
tagsSuggestions: [],
|
|
||||||
selectedAggOption: 'count',
|
|
||||||
selectedEntity: 'calls',
|
|
||||||
spansAggregate: [],
|
|
||||||
spansLoading: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const traceReducer = (
|
const traceReducer = (
|
||||||
state = intitalState,
|
state = initialValue,
|
||||||
action: TraceActions,
|
action: TraceActions,
|
||||||
): TraceReducer => {
|
): TraceReducer => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case GET_TRACE_INITIAL_DATA_ERROR: {
|
case UPDATE_TRACE_FILTER: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
errorMessage: action.payload.errorMessage,
|
filter: action.payload.filter,
|
||||||
loading: false,
|
|
||||||
error: true,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case GET_TRACE_LOADING_START: {
|
case UPDATE_ALL_FILTERS: {
|
||||||
return {
|
const { payload } = action;
|
||||||
...state,
|
|
||||||
loading: true,
|
|
||||||
spansLoading: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case GET_TRACE_INITIAL_DATA_SUCCESS: {
|
|
||||||
const {
|
const {
|
||||||
serviceList,
|
filter,
|
||||||
operationList,
|
filterToFetchData,
|
||||||
tagsSuggestions,
|
selectedFilter,
|
||||||
selectedOperation,
|
current,
|
||||||
selectedService,
|
|
||||||
selectedTags,
|
selectedTags,
|
||||||
spansList,
|
userSelected,
|
||||||
selectedKind,
|
isFilterExclude,
|
||||||
selectedLatency,
|
} = payload;
|
||||||
spansAggregate,
|
|
||||||
} = action.payload;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
serviceList: serviceList,
|
filter,
|
||||||
tagsSuggestions,
|
filterToFetchData,
|
||||||
selectedOperation,
|
selectedFilter,
|
||||||
selectedService,
|
|
||||||
selectedTags,
|
selectedTags,
|
||||||
spanList: spansList,
|
userSelectedFilter: userSelected,
|
||||||
operationsList: operationList,
|
isFilterExclude,
|
||||||
error: false,
|
spansAggregate: {
|
||||||
selectedKind,
|
...state.spansAggregate,
|
||||||
selectedLatency,
|
currentPage: current,
|
||||||
spansAggregate,
|
},
|
||||||
spansLoading: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPDATE_TRACE_SELECTED_KIND: {
|
case UPDATE_TRACE_FILTER_LOADING: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selectedKind: action.payload.selectedKind,
|
filterLoading: action.payload.filterLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPDATE_TRACE_SELECTED_LATENCY_VALUE: {
|
case SELECT_TRACE_FILTER: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selectedLatency: action.payload.selectedLatency,
|
selectedFilter: action.payload.selectedFilter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPDATE_TRACE_SELECTED_OPERATION: {
|
case RESET_TRACE_FILTER: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...initialValue,
|
||||||
selectedOperation: action.payload.selectedOperation,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPDATE_TRACE_SELECTED_SERVICE: {
|
case UPDATE_SELECTED_TAGS: {
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
selectedService: action.payload.selectedService,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case UPDATE_TRACE_SELECTED_TAGS: {
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selectedTags: action.payload.selectedTags,
|
selectedTags: action.payload.selectedTags,
|
||||||
spanList: action.payload.spansList,
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_SPANS_AGGREEGATE: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
spansAggregate: action.payload.spansAggregate,
|
spansAggregate: action.payload.spansAggregate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPDATE_SELECTED_TRACE_DATA: {
|
case UPDATE_TAG_MODAL_VISIBLITY: {
|
||||||
const {
|
|
||||||
spansList,
|
|
||||||
tagsSuggestions,
|
|
||||||
operationList,
|
|
||||||
selectedOperation,
|
|
||||||
selectedLatency,
|
|
||||||
selectedService,
|
|
||||||
selectedKind,
|
|
||||||
spansAggregate,
|
|
||||||
} = action.payload;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
spanList: spansList,
|
isTagModalOpen: action.payload.isTagModalOpen,
|
||||||
tagsSuggestions,
|
|
||||||
operationsList: operationList,
|
|
||||||
selectedOperation,
|
|
||||||
selectedLatency,
|
|
||||||
selectedService,
|
|
||||||
selectedKind,
|
|
||||||
spansAggregate,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case GET_TRACE_LOADING_END: {
|
case UPDATE_IS_TAG_ERROR: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...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,
|
loading: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPDATE_SELECTED_AGG_OPTION: {
|
case UPDATE_TRACE_GRAPH_SUCCESS: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selectedAggOption: action.payload.selectedAggOption,
|
spansGraph: {
|
||||||
|
...state.spansGraph,
|
||||||
|
payload: action.payload.data,
|
||||||
|
loading: false,
|
||||||
|
error: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPDATE_SELECTED_ENTITY: {
|
case UPDATE_FILTER_RESPONSE_SELECTED: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
selectedEntity: action.payload.selectedEntity,
|
filterResponseSelected: action.payload.filterResponseSelected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
case UPDATE_SPANS_LOADING: {
|
case UPDATE_FILTER_EXCLUDE: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
spansLoading: action.payload.loading,
|
isFilterExclude: action.payload.isFilterExclude,
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case RESET_TRACE_DATA: {
|
|
||||||
return {
|
|
||||||
...intitalState,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
case UPDATE_AGGREGATES: {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
spansAggregate: action.payload.spansAggregate,
|
|
||||||
selectedAggOption: action.payload.selectedAggOption,
|
|
||||||
selectedEntity: action.payload.selectedEntity,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,3 +201,5 @@ export const traceReducer = (
|
|||||||
return state;
|
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';
|
import { TraceReducer } from 'types/reducer/trace';
|
||||||
|
|
||||||
interface GetTraceLoading {
|
export const UPDATE_TRACE_FILTER = 'UPDATE_TRACE_FILTER';
|
||||||
type: typeof GET_TRACE_LOADING_START | typeof GET_TRACE_LOADING_END;
|
export const GET_TRACE_FILTER = 'GET_TRACE_FILTER';
|
||||||
}
|
export const UPDATE_TRACE_FILTER_LOADING = 'UPDATE_TRACE_FILTER_LOADING';
|
||||||
|
|
||||||
interface UpdateSpansLoading {
|
export const SELECT_TRACE_FILTER = 'SELECT_TRACE_FILTER';
|
||||||
type: typeof UPDATE_SPANS_LOADING;
|
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: {
|
payload: {
|
||||||
loading: boolean;
|
filter: TraceReducer['filter'];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetTraceInitialData {
|
export interface UpdateSpansAggregate {
|
||||||
type: typeof GET_TRACE_INITIAL_DATA_SUCCESS;
|
type: typeof UPDATE_SPANS_AGGREEGATE;
|
||||||
payload: {
|
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'];
|
spansAggregate: TraceReducer['spansAggregate'];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSelectedDate {
|
export interface UpdateTagVisiblity {
|
||||||
type: typeof UPDATE_SELECTED_TRACE_DATA;
|
type: typeof UPDATE_TAG_MODAL_VISIBLITY;
|
||||||
payload: {
|
payload: {
|
||||||
operationList: TraceReducer['operationsList'];
|
isTagModalOpen: TraceReducer['isTagModalOpen'];
|
||||||
tagsSuggestions: TraceReducer['tagsSuggestions'];
|
|
||||||
spansList: TraceReducer['spanList'];
|
|
||||||
selectedKind: TraceReducer['selectedKind'];
|
|
||||||
selectedService: TraceReducer['selectedService'];
|
|
||||||
selectedLatency: TraceReducer['selectedLatency'];
|
|
||||||
selectedOperation: TraceReducer['selectedOperation'];
|
|
||||||
spansAggregate: TraceReducer['spansAggregate'];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetTraceInitialDataError {
|
export interface UpdateSelectedTags {
|
||||||
type: typeof GET_TRACE_INITIAL_DATA_ERROR;
|
type: typeof UPDATE_SELECTED_TAGS;
|
||||||
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;
|
|
||||||
payload: {
|
payload: {
|
||||||
selectedTags: TraceReducer['selectedTags'];
|
selectedTags: TraceReducer['selectedTags'];
|
||||||
spansList: TraceReducer['spanList'];
|
|
||||||
spansAggregate: TraceReducer['spansAggregate'];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSelectedAggOption {
|
export interface UpdateSelected {
|
||||||
type: typeof UPDATE_SELECTED_AGG_OPTION;
|
type: typeof UPDATE_FILTER_RESPONSE_SELECTED;
|
||||||
payload: {
|
payload: {
|
||||||
selectedAggOption: TraceReducer['selectedAggOption'];
|
filterResponseSelected: TraceReducer['filterResponseSelected'];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSelectedEntity {
|
export interface UpdateAllFilters {
|
||||||
type: typeof UPDATE_SELECTED_ENTITY;
|
type: typeof UPDATE_ALL_FILTERS;
|
||||||
payload: {
|
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 {
|
export interface UpdateFilterLoading {
|
||||||
type: typeof UPDATE_AGGREGATES;
|
type: typeof UPDATE_TRACE_FILTER_LOADING;
|
||||||
payload: {
|
payload: {
|
||||||
spansAggregate: TraceReducer['spansAggregate'];
|
filterLoading: TraceReducer['filterLoading'];
|
||||||
selectedEntity: TraceReducer['selectedEntity'];
|
|
||||||
selectedAggOption: TraceReducer['selectedAggOption'];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResetTraceData {
|
export interface SelectTraceFilter {
|
||||||
type: typeof RESET_TRACE_DATA;
|
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 =
|
export type TraceActions =
|
||||||
| GetTraceLoading
|
| UpdateFilter
|
||||||
| GetTraceInitialData
|
| GetTraceFilter
|
||||||
| GetTraceInitialDataError
|
| UpdateFilterLoading
|
||||||
| UpdateTraceSelectedService
|
| SelectTraceFilter
|
||||||
| UpdateTraceSelectedLatencyValue
|
| UpdateAllFilters
|
||||||
| UpdateTraceSelectedKind
|
| UpdateSelectedTags
|
||||||
| UpdateTraceSelectedOperation
|
| UpdateTagVisiblity
|
||||||
| UpdateTraceSelectedTags
|
| UpdateSpansAggregate
|
||||||
| UpdateSelectedDate
|
| UpdateIsTagError
|
||||||
| UpdateSelectedAggOption
|
| UpdateSelectedGroupBy
|
||||||
| UpdateSelectedEntity
|
| UpdateSelectedFunction
|
||||||
| UpdateSpansLoading
|
| UpdateSpanLoading
|
||||||
| ResetTraceData
|
| UpdateSpansError
|
||||||
| UpdateAggregates;
|
| 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 {
|
export interface Props {
|
||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
service: string;
|
selectedFilter: TraceReducer['selectedFilter'];
|
||||||
operation: string;
|
limit: number;
|
||||||
maxDuration: string;
|
offset: number;
|
||||||
minDuration: string;
|
selectedTags: TraceReducer['selectedTags'];
|
||||||
kind: string;
|
isFilterExclude: TraceReducer['isFilterExclude'];
|
||||||
tags: string;
|
|
||||||
dimension: string;
|
|
||||||
aggregation_option: string;
|
|
||||||
step: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Timestamp {
|
export type PayloadProps = {
|
||||||
timestamp: number;
|
spans: TraceReducer['spansAggregate']['data'];
|
||||||
value: number;
|
totalSpans: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type PayloadProps = Timestamp[];
|
|
||||||
|
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