mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 21:26:02 +08:00
Fix(FE):trace page (#356)
* chore: Router provider is removed * update: localstorage set get is added * update: AppLayout is updated * fix: adapter type is fixed * fix: Metric and metric application is now fixed * fix: Metrics page application is updated * fix: Tracepage is made fix * fix: app layout is updated * fix: global Time reducer is updated * refactor: getService api is added * update: metrics reducer is added * update: service list is fixed * fix: Metrics page is updated * fix: api for the metrics application are done * fix: metrics reducer is updated * fix: metrics application is updated * fix: content layout shift is removed * fix: Metric application is updated * fix: metrics application is updated * fix: Metrics application is updated * fix: Application tab is updated * chore: graph is updated * chore: Metrics application is updated * fix: chart x-axis is label is now fixed * fix: application tab is updated * fix: Top end points is added and re-redering in stopped * fix: fixed the edge case when user changes the global time then updated data is fetched * fix: Settings page is updated * chore: AppLayout is updated * chore: AppLayout is updated * chore: applayout is updated * chore: changed default loading is true in the global time reducer * chore: Global Time option is fixed * chore: Signup and Applayout is updated * chore: Button text is updated * chore: Button in the metrics application is updated * chore: dashboard menu item position in the side nav is updated * fix: Logo is now redirecting to the Application page * fix: Application page is updated * fix: AppLayout is updated * fix: starting and ending time is fixed * fix: Metrics Application is updated to the previous chart data * update: getDateArrayFromStartAndEnd function is added * update: Empty graph data is added * fix: External Call and DB Call Tabs graph are updated when there is no data a empty data is rendered * fix: onboarding modal condition is fixed and new calling api every 50000 ms to fetch the data * fix: onBoarding condition modal is updated * fix: onBoarding condition modal is updated * fix: onBoarding condition modal is updated * fix: Application chart re rendering issue is fixed * fix: Application page is changed when we change the global time * chore: step size is increased from 30 to 60 * chore: build is now fixed * chore: metrics application page is updated * fix: empty graph is now fixed * fix: application metrics graph is now fixed * update: seperate api for trace page are made * fix: /trace page is updated * chore: Filter of the Trace page is updated * chore: initial trace page is updated * fix: changing the filters,fetches the updated values from the backend * chore: Trace page is updated * update: trace page is updated * fix: trace page is updated * Refresh Text is updated * update: Trace page is updated * update:header is updated * update: Trace page is updated * update: Trace page is updated * update: Trace page is updated * update: Trace page is updated * update: why did you re render is added * update: trace page is updated * update: trace page is updated * update: Loading is updated * update: start and end time is updated * fix: metrics and metrics page redudant calls is reduced * fix: Metrics Application page reducer is reset on the unmount * fix: Trace page reducer is reset when the page is unmounted * fix: Custom Visualizations is now fetching only one api to get the details * fix: Trace page is updated * fix: composeEnhancers is updated * fix: metrics application is updated * chore: webpack eslint fixes are updated * chore: some of the type definition is added * fix(UI): Trace page bug is resolved * chore(UI): if length of the selected tags is zero updated the value over the form * chore(UI): check for the no spans filter is updated
This commit is contained in:
parent
510815655f
commit
28c8df5e63
@ -145,6 +145,7 @@
|
|||||||
"@types/webpack-dev-server": "^4.3.0",
|
"@types/webpack-dev-server": "^4.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||||
"@typescript-eslint/parser": "^4.28.2",
|
"@typescript-eslint/parser": "^4.28.2",
|
||||||
|
"@welldone-software/why-did-you-render": "^6.2.1",
|
||||||
"autoprefixer": "^9.0.0",
|
"autoprefixer": "^9.0.0",
|
||||||
"babel-plugin-styled-components": "^1.12.0",
|
"babel-plugin-styled-components": "^1.12.0",
|
||||||
"compression-webpack-plugin": "^9.0.0",
|
"compression-webpack-plugin": "^9.0.0",
|
||||||
|
@ -25,6 +25,10 @@ export const TraceDetailPage = Loadable(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const TraceDetailPages = Loadable(
|
||||||
|
() => import(/* webpackChunkName: "TraceDetailPage" */ 'pages/TraceDetails'),
|
||||||
|
);
|
||||||
|
|
||||||
export const TraceGraphPage = Loadable(
|
export const TraceGraphPage = Loadable(
|
||||||
() =>
|
() =>
|
||||||
import(
|
import(
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
SettingsPage,
|
SettingsPage,
|
||||||
SignupPage,
|
SignupPage,
|
||||||
TraceDetailPage,
|
TraceDetailPage,
|
||||||
|
TraceDetailPages,
|
||||||
TraceGraphPage,
|
TraceGraphPage,
|
||||||
UsageExplorerPage,
|
UsageExplorerPage,
|
||||||
} from './pageComponents';
|
} from './pageComponents';
|
||||||
@ -77,6 +78,11 @@ const routes: AppRoutes[] = [
|
|||||||
exact: true,
|
exact: true,
|
||||||
component: DashboardWidget,
|
component: DashboardWidget,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.TRACE,
|
||||||
|
exact: true,
|
||||||
|
component: TraceDetailPages,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
interface AppRoutes {
|
interface AppRoutes {
|
||||||
|
24
frontend/src/api/trace/getServiceList.ts
Normal file
24
frontend/src/api/trace/getServiceList.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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;
|
24
frontend/src/api/trace/getServiceOperation.ts
Normal file
24
frontend/src/api/trace/getServiceOperation.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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;
|
26
frontend/src/api/trace/getSpan.ts
Normal file
26
frontend/src/api/trace/getSpan.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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;
|
26
frontend/src/api/trace/getSpanAggregate.ts
Normal file
26
frontend/src/api/trace/getSpanAggregate.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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;
|
24
frontend/src/api/trace/getTags.ts
Normal file
24
frontend/src/api/trace/getTags.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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;
|
@ -6,4 +6,9 @@ export enum METRICS_PAGE_QUERY_PARAM {
|
|||||||
error = 'error',
|
error = 'error',
|
||||||
operation = 'operation',
|
operation = 'operation',
|
||||||
kind = 'kind',
|
kind = 'kind',
|
||||||
|
latencyMax = 'latencyMax',
|
||||||
|
latencyMin = 'latencyMin',
|
||||||
|
selectedTags = 'selectedTags',
|
||||||
|
aggregationOption = 'aggregationOption',
|
||||||
|
entity = 'entity',
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ const ROUTES = {
|
|||||||
SERVICE_METRICS: '/application/:servicename',
|
SERVICE_METRICS: '/application/:servicename',
|
||||||
SERVICE_MAP: '/service-map',
|
SERVICE_MAP: '/service-map',
|
||||||
TRACES: '/traces',
|
TRACES: '/traces',
|
||||||
|
TRACE: '/trace',
|
||||||
TRACE_GRAPH: '/traces/:id',
|
TRACE_GRAPH: '/traces/:id',
|
||||||
SETTINGS: '/settings',
|
SETTINGS: '/settings',
|
||||||
INSTRUMENTATION: '/add-instrumentation',
|
INSTRUMENTATION: '/add-instrumentation',
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
import getChartData from 'lib/getChartData';
|
import getChartData from 'lib/getChartData';
|
||||||
import GetMaxMinTime from 'lib/getMaxMinTime';
|
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||||
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
||||||
import React, { memo, useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { GlobalTime } from 'types/actions/globalTime';
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
@ -219,15 +219,4 @@ interface FullViewProps {
|
|||||||
noDataGraph?: boolean;
|
noDataGraph?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(FullView, (prev, next) => {
|
export default FullView;
|
||||||
if (
|
|
||||||
next.widget.query.length !== prev.widget.query.length &&
|
|
||||||
next.widget.query.every((value, index) => {
|
|
||||||
return value === prev.widget.query[index];
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
34
frontend/src/container/Header/DateTimeSelection/Refresh.tsx
Normal file
34
frontend/src/container/Header/DateTimeSelection/Refresh.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { RefreshTextContainer, Typography } from './styles';
|
||||||
|
|
||||||
|
const RefreshText = ({
|
||||||
|
onLastRefreshHandler,
|
||||||
|
}: RefreshTextProps): JSX.Element => {
|
||||||
|
const [refreshText, setRefreshText] = useState<string>('');
|
||||||
|
|
||||||
|
// this is to update the refresh text
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const text = onLastRefreshHandler();
|
||||||
|
if (refreshText !== text) {
|
||||||
|
setRefreshText(text);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
return (): void => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [onLastRefreshHandler, refreshText]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RefreshTextContainer>
|
||||||
|
<Typography>{refreshText}</Typography>
|
||||||
|
</RefreshTextContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface RefreshTextProps {
|
||||||
|
onLastRefreshHandler: () => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RefreshText;
|
@ -2,13 +2,7 @@ import { Button, Select as DefaultSelect } from 'antd';
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { getDefaultOption, getOptions, Time } from './config';
|
import { getDefaultOption, getOptions, Time } from './config';
|
||||||
import {
|
import { Container, Form, FormItem } from './styles';
|
||||||
Container,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
RefreshTextContainer,
|
|
||||||
Typography,
|
|
||||||
} from './styles';
|
|
||||||
const { Option } = DefaultSelect;
|
const { Option } = DefaultSelect;
|
||||||
import get from 'api/browser/localstorage/get';
|
import get from 'api/browser/localstorage/get';
|
||||||
import set from 'api/browser/localstorage/set';
|
import set from 'api/browser/localstorage/set';
|
||||||
@ -19,31 +13,72 @@ 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';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { UpdateTimeInterval } from 'store/actions';
|
import { GlobalTimeLoading, UpdateTimeInterval } from 'store/actions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal';
|
import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal';
|
||||||
|
import RefreshText from './Refresh';
|
||||||
|
|
||||||
const DateTimeSelection = ({
|
const DateTimeSelection = ({
|
||||||
location,
|
location,
|
||||||
updateTimeInterval,
|
updateTimeInterval,
|
||||||
|
globalTimeLoading,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const [form_dtselector] = Form.useForm();
|
const [form_dtselector] = Form.useForm();
|
||||||
|
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const searchStartTime = params.get('startTime');
|
||||||
|
const searchEndTime = params.get('endTime');
|
||||||
|
|
||||||
|
const localstorageStartTime = get('startTime');
|
||||||
|
const localstorageEndTime = get('endTime');
|
||||||
|
|
||||||
|
const getTime = useCallback((): [number, number] | undefined => {
|
||||||
|
if (searchEndTime && searchStartTime) {
|
||||||
|
const startMoment = moment(
|
||||||
|
new Date(parseInt(getTimeString(searchStartTime), 10)),
|
||||||
|
);
|
||||||
|
const endMoment = moment(
|
||||||
|
new Date(parseInt(getTimeString(searchEndTime), 10)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
startMoment.toDate().getTime() || 0,
|
||||||
|
endMoment.toDate().getTime() || 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (localstorageStartTime && localstorageEndTime) {
|
||||||
|
const startMoment = moment(localstorageStartTime);
|
||||||
|
const endMoment = moment(localstorageEndTime);
|
||||||
|
|
||||||
|
return [
|
||||||
|
startMoment.toDate().getTime() || 0,
|
||||||
|
endMoment.toDate().getTime() || 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, [
|
||||||
|
localstorageEndTime,
|
||||||
|
localstorageStartTime,
|
||||||
|
searchEndTime,
|
||||||
|
searchStartTime,
|
||||||
|
]);
|
||||||
|
|
||||||
const [startTime, setStartTime] = useState<moment.Moment>();
|
const [startTime, setStartTime] = useState<moment.Moment>();
|
||||||
const [endTime, setEndTime] = useState<moment.Moment>();
|
const [endTime, setEndTime] = useState<moment.Moment>();
|
||||||
|
|
||||||
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);
|
||||||
const [refreshText, setRefreshText] = useState<string>('');
|
|
||||||
const [customDateTimeVisible, setCustomDTPickerVisible] = useState<boolean>(
|
const [customDateTimeVisible, setCustomDTPickerVisible] = useState<boolean>(
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
const isOnSelectHandler = useRef<boolean>(false);
|
|
||||||
|
|
||||||
const { maxTime, loading, minTime } = useSelector<AppState, GlobalReducer>(
|
const { maxTime, minTime, selectedTime, loading } = useSelector<
|
||||||
(state) => state.globalTime,
|
AppState,
|
||||||
);
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
const getDefaultTime = (pathName: string): Time => {
|
const getDefaultTime = (pathName: string): Time => {
|
||||||
const defaultSelectedOption = getDefaultOption(pathName);
|
const defaultSelectedOption = getDefaultOption(pathName);
|
||||||
@ -81,8 +116,6 @@ const DateTimeSelection = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSelectHandler = (value: Time): void => {
|
const onSelectHandler = (value: Time): void => {
|
||||||
isOnSelectHandler.current = true;
|
|
||||||
|
|
||||||
if (value !== 'custom') {
|
if (value !== 'custom') {
|
||||||
updateTimeInterval(value);
|
updateTimeInterval(value);
|
||||||
const selectedLabel = getInputLabel(undefined, undefined, value);
|
const selectedLabel = getInputLabel(undefined, undefined, value);
|
||||||
@ -168,17 +201,6 @@ const DateTimeSelection = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// this is to update the refresh text
|
|
||||||
useEffect(() => {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
const text = onLastRefreshHandler();
|
|
||||||
setRefreshText(text);
|
|
||||||
}, 2000);
|
|
||||||
return (): void => {
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
}, [onLastRefreshHandler]);
|
|
||||||
|
|
||||||
// this is triggred when we change the routes and based on that we are changing the default options
|
// this is triggred when we change the routes and based on that we are changing the default options
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const metricsTimeDuration = get(LOCAL_STORAGE.METRICS_TIME_IN_DURATION);
|
const metricsTimeDuration = get(LOCAL_STORAGE.METRICS_TIME_IN_DURATION);
|
||||||
@ -187,21 +209,13 @@ const DateTimeSelection = ({
|
|||||||
set(LOCAL_STORAGE.METRICS_TIME_IN_DURATION, JSON.stringify({}));
|
set(LOCAL_STORAGE.METRICS_TIME_IN_DURATION, JSON.stringify({}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOnSelectHandler.current === false) {
|
|
||||||
const currentRoute = location.pathname;
|
const currentRoute = location.pathname;
|
||||||
const params = new URLSearchParams(location.search);
|
|
||||||
const time = getDefaultTime(currentRoute);
|
const time = getDefaultTime(currentRoute);
|
||||||
|
|
||||||
const currentOptions = getOptions(currentRoute);
|
const currentOptions = getOptions(currentRoute);
|
||||||
setOptions(currentOptions);
|
setOptions(currentOptions);
|
||||||
|
|
||||||
const searchStartTime = params.get('startTime');
|
const getCustomOrIntervalTime = (time: Time): Time => {
|
||||||
const searchEndTime = params.get('endTime');
|
|
||||||
|
|
||||||
const localstorageStartTime = get('startTime');
|
|
||||||
const localstorageEndTime = get('endTime');
|
|
||||||
|
|
||||||
const getUpdatedTime = (time: Time): Time => {
|
|
||||||
if (searchEndTime !== null && searchStartTime !== null) {
|
if (searchEndTime !== null && searchStartTime !== null) {
|
||||||
return 'custom';
|
return 'custom';
|
||||||
}
|
}
|
||||||
@ -210,62 +224,29 @@ const DateTimeSelection = ({
|
|||||||
(localstorageEndTime === null || localstorageStartTime === null) &&
|
(localstorageEndTime === null || localstorageStartTime === null) &&
|
||||||
time === 'custom'
|
time === 'custom'
|
||||||
) {
|
) {
|
||||||
return getDefaultOption(location.pathname);
|
return getDefaultOption(currentRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
return time;
|
return time;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatedTime = getUpdatedTime(time);
|
const updatedTime = getCustomOrIntervalTime(time);
|
||||||
|
|
||||||
setSelectedTimeInterval(updatedTime);
|
const [preStartTime = 0, preEndTime = 0] = getTime() || [];
|
||||||
|
|
||||||
const getTime = (): [number, number] | undefined => {
|
setStartTime(moment(preStartTime));
|
||||||
if (searchEndTime && searchStartTime) {
|
setEndTime(moment(preEndTime));
|
||||||
const startMoment = moment(
|
|
||||||
new Date(parseInt(getTimeString(searchStartTime), 10)),
|
|
||||||
);
|
|
||||||
const endMoment = moment(
|
|
||||||
new Date(parseInt(getTimeString(searchEndTime), 10)),
|
|
||||||
);
|
|
||||||
|
|
||||||
setStartTime(startMoment);
|
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
||||||
setEndTime(endMoment);
|
|
||||||
|
|
||||||
return [
|
|
||||||
startMoment.toDate().getTime() || 0,
|
|
||||||
endMoment.toDate().getTime() || 0,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (localstorageStartTime && localstorageEndTime) {
|
|
||||||
const startMoment = moment(localstorageStartTime);
|
|
||||||
const endMoment = moment(localstorageEndTime);
|
|
||||||
|
|
||||||
setStartTime(startMoment);
|
|
||||||
setEndTime(endMoment);
|
|
||||||
|
|
||||||
return [
|
|
||||||
startMoment.toDate().getTime() || 0,
|
|
||||||
endMoment.toDate().getTime() || 0,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading === true) {
|
|
||||||
updateTimeInterval(updatedTime, getTime());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
isOnSelectHandler.current = false;
|
|
||||||
}
|
|
||||||
}, [
|
}, [
|
||||||
location.pathname,
|
location.pathname,
|
||||||
location.search,
|
getTime,
|
||||||
startTime,
|
localstorageEndTime,
|
||||||
endTime,
|
localstorageStartTime,
|
||||||
|
searchEndTime,
|
||||||
|
searchStartTime,
|
||||||
updateTimeInterval,
|
updateTimeInterval,
|
||||||
selectedTimeInterval,
|
globalTimeLoading,
|
||||||
loading,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -273,11 +254,11 @@ const DateTimeSelection = ({
|
|||||||
<Form
|
<Form
|
||||||
form={form_dtselector}
|
form={form_dtselector}
|
||||||
layout="inline"
|
layout="inline"
|
||||||
initialValues={{ interval: selectedTimeInterval }}
|
initialValues={{ interval: selectedTime }}
|
||||||
>
|
>
|
||||||
<DefaultSelect
|
<DefaultSelect
|
||||||
onSelect={(value): void => onSelectHandler(value as Time)}
|
onSelect={(value): void => onSelectHandler(value as Time)}
|
||||||
value={getInputLabel(startTime, endTime, selectedTimeInterval)}
|
value={getInputLabel(startTime, endTime, selectedTime)}
|
||||||
data-testid="dropDown"
|
data-testid="dropDown"
|
||||||
>
|
>
|
||||||
{options.map(({ value, label }) => (
|
{options.map(({ value, label }) => (
|
||||||
@ -294,9 +275,11 @@ const DateTimeSelection = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<RefreshTextContainer>
|
<RefreshText
|
||||||
<Typography>{refreshText}</Typography>
|
{...{
|
||||||
</RefreshTextContainer>
|
onLastRefreshHandler,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<CustomDateTimeModal
|
<CustomDateTimeModal
|
||||||
visible={customDateTimeVisible}
|
visible={customDateTimeVisible}
|
||||||
@ -314,16 +297,21 @@ interface DispatchProps {
|
|||||||
interval: Time,
|
interval: Time,
|
||||||
dateTimeRange?: [number, number],
|
dateTimeRange?: [number, number],
|
||||||
) => (dispatch: Dispatch<AppActions>) => void;
|
) => (dispatch: Dispatch<AppActions>) => void;
|
||||||
// globalTimeLoading: () => void;
|
globalTimeLoading: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
const mapDispatchToProps = (
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
): DispatchProps => ({
|
): DispatchProps => ({
|
||||||
updateTimeInterval: bindActionCreators(UpdateTimeInterval, dispatch),
|
updateTimeInterval: bindActionCreators(UpdateTimeInterval, dispatch),
|
||||||
// globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
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',
|
||||||
|
// };
|
||||||
|
@ -6,13 +6,9 @@ import FullView from 'container/GridGraphLayout/Graph/FullView';
|
|||||||
import { colors } from 'lib/getRandomColor';
|
import { colors } from 'lib/getRandomColor';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { connect, useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { GlobalTimeLoading } from 'store/actions';
|
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
import MetricReducer from 'types/reducer/metrics';
|
import MetricReducer from 'types/reducer/metrics';
|
||||||
|
|
||||||
@ -20,10 +16,7 @@ import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
|
|||||||
import TopEndpointsTable from '../TopEndpointsTable';
|
import TopEndpointsTable from '../TopEndpointsTable';
|
||||||
import { Button } from './styles';
|
import { Button } from './styles';
|
||||||
|
|
||||||
const Application = ({
|
const Application = ({ getWidget }: DashboardProps): JSX.Element => {
|
||||||
globalLoading,
|
|
||||||
getWidget,
|
|
||||||
}: DashboardProps): JSX.Element => {
|
|
||||||
const { servicename } = useParams<{ servicename?: string }>();
|
const { servicename } = useParams<{ servicename?: string }>();
|
||||||
const selectedTimeStamp = useRef(0);
|
const selectedTimeStamp = useRef(0);
|
||||||
|
|
||||||
@ -42,8 +35,7 @@ const Application = ({
|
|||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
||||||
}
|
}
|
||||||
|
|
||||||
globalLoading();
|
history.push(`${ROUTES.TRACE}?${urlParams.toString()}`);
|
||||||
history.push(`${ROUTES.TRACES}?${urlParams.toString()}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickhandler = async (
|
const onClickhandler = async (
|
||||||
@ -74,7 +66,7 @@ const Application = ({
|
|||||||
buttonElement.style.display = 'block';
|
buttonElement.style.display = 'block';
|
||||||
buttonElement.style.left = `${firstPoint.element.x}px`;
|
buttonElement.style.left = `${firstPoint.element.x}px`;
|
||||||
buttonElement.style.top = `${firstPoint.element.y}px`;
|
buttonElement.style.top = `${firstPoint.element.y}px`;
|
||||||
selectedTimeStamp.current = new Date(time).getTime();
|
selectedTimeStamp.current = time.getTime();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -97,8 +89,7 @@ const Application = ({
|
|||||||
}
|
}
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.error, 'true');
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.error, 'true');
|
||||||
|
|
||||||
globalLoading();
|
history.push(`${ROUTES.TRACE}?${urlParams.toString()}`);
|
||||||
history.push(`${ROUTES.TRACES}?${urlParams.toString()}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -238,18 +229,8 @@ const Application = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DashboardProps {
|
||||||
globalLoading: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
globalLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface DashboardProps extends DispatchProps {
|
|
||||||
getWidget: (query: Widgets['query']) => Widgets;
|
getWidget: (query: Widgets['query']) => Widgets;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(Application);
|
export default Application;
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import { Button, Table, Tooltip } from 'antd';
|
import { Button, Table, Tooltip } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect, useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { GlobalTimeLoading } from 'store/actions';
|
|
||||||
import { topEndpointListItem } from 'store/actions/MetricsActions';
|
import { topEndpointListItem } from 'store/actions/MetricsActions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
const TopEndpointsTable = (props: TopEndpointsTableProps): JSX.Element => {
|
const TopEndpointsTable = (props: TopEndpointsTableProps): JSX.Element => {
|
||||||
@ -25,19 +22,18 @@ const TopEndpointsTable = (props: TopEndpointsTableProps): JSX.Element => {
|
|||||||
const { servicename } = params;
|
const { servicename } = params;
|
||||||
urlParams.set(
|
urlParams.set(
|
||||||
METRICS_PAGE_QUERY_PARAM.startTime,
|
METRICS_PAGE_QUERY_PARAM.startTime,
|
||||||
String(Number(minTime) / 1000000),
|
(minTime / 1000000).toString(),
|
||||||
);
|
);
|
||||||
urlParams.set(
|
urlParams.set(
|
||||||
METRICS_PAGE_QUERY_PARAM.endTime,
|
METRICS_PAGE_QUERY_PARAM.endTime,
|
||||||
String(Number(maxTime) / 1000000),
|
(maxTime / 1000000).toString(),
|
||||||
);
|
);
|
||||||
if (servicename) {
|
if (servicename) {
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
||||||
}
|
}
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.operation, operation);
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.operation, operation);
|
||||||
|
|
||||||
props.globalTimeLoading();
|
history.push(`${ROUTES.TRACE}?${urlParams.toString()}`);
|
||||||
history.push(`/traces?${urlParams.toString()}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns: ColumnsType<DataProps> = [
|
const columns: ColumnsType<DataProps> = [
|
||||||
@ -105,18 +101,8 @@ const TopEndpointsTable = (props: TopEndpointsTableProps): JSX.Element => {
|
|||||||
|
|
||||||
type DataProps = topEndpointListItem;
|
type DataProps = topEndpointListItem;
|
||||||
|
|
||||||
interface DispatchProps {
|
interface TopEndpointsTableProps {
|
||||||
globalTimeLoading: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
interface TopEndpointsTableProps extends DispatchProps {
|
|
||||||
data: topEndpointListItem[];
|
data: topEndpointListItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(TopEndpointsTable);
|
export default TopEndpointsTable;
|
||||||
|
@ -3,18 +3,15 @@ import { SKIP_ONBOARDING } from 'constants/onboarding';
|
|||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { connect, useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { servicesListItem } from 'store/actions/MetricsActions/metricsInterfaces';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { GlobalTimeLoading, servicesListItem } from 'store/actions';
|
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import MetricReducer from 'types/reducer/metrics';
|
import MetricReducer from 'types/reducer/metrics';
|
||||||
|
|
||||||
import SkipBoardModal from './SkipOnBoardModal';
|
import SkipBoardModal from './SkipOnBoardModal';
|
||||||
import { Container, Name } from './styles';
|
import { Container, Name } from './styles';
|
||||||
|
|
||||||
const Metrics = ({ globalTimeLoading }: MetricsProps): JSX.Element => {
|
const Metrics = (): JSX.Element => {
|
||||||
const [skipOnboarding, setSkipOnboarding] = useState(
|
const [skipOnboarding, setSkipOnboarding] = useState(
|
||||||
localStorage.getItem(SKIP_ONBOARDING) === 'true',
|
localStorage.getItem(SKIP_ONBOARDING) === 'true',
|
||||||
);
|
);
|
||||||
@ -30,7 +27,6 @@ const Metrics = ({ globalTimeLoading }: MetricsProps): JSX.Element => {
|
|||||||
|
|
||||||
const onClickHandler = (to: string): void => {
|
const onClickHandler = (to: string): void => {
|
||||||
history.push(to);
|
history.push(to);
|
||||||
globalTimeLoading();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -90,16 +86,4 @@ const Metrics = ({ globalTimeLoading }: MetricsProps): JSX.Element => {
|
|||||||
|
|
||||||
type DataProps = servicesListItem;
|
type DataProps = servicesListItem;
|
||||||
|
|
||||||
interface DispatchProps {
|
export default Metrics;
|
||||||
globalTimeLoading: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
type MetricsProps = DispatchProps;
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(Metrics);
|
|
||||||
|
@ -7,7 +7,7 @@ import { NavLink } from 'react-router-dom';
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { GlobalTimeLoading, ToggleDarkMode } from 'store/actions';
|
import { ToggleDarkMode } from 'store/actions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
@ -15,7 +15,7 @@ import AppReducer from 'types/reducer/app';
|
|||||||
import menus from './menuItems';
|
import menus from './menuItems';
|
||||||
import { Logo, Sider, ThemeSwitcherWrapper } from './styles';
|
import { Logo, Sider, ThemeSwitcherWrapper } from './styles';
|
||||||
|
|
||||||
const SideNav = ({ toggleDarkMode, globalTimeLoading }: Props): JSX.Element => {
|
const SideNav = ({ toggleDarkMode }: Props): JSX.Element => {
|
||||||
const [collapsed, setCollapsed] = useState<boolean>(false);
|
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
@ -49,10 +49,9 @@ const SideNav = ({ toggleDarkMode, globalTimeLoading }: Props): JSX.Element => {
|
|||||||
(to: string) => {
|
(to: string) => {
|
||||||
if (pathname !== to) {
|
if (pathname !== to) {
|
||||||
history.push(to);
|
history.push(to);
|
||||||
globalTimeLoading();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[pathname, globalTimeLoading],
|
[pathname],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -86,14 +85,12 @@ type mode = 'darkMode' | 'lightMode';
|
|||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
toggleDarkMode: () => void;
|
toggleDarkMode: () => void;
|
||||||
globalTimeLoading: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
const mapDispatchToProps = (
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
): DispatchProps => ({
|
): DispatchProps => ({
|
||||||
toggleDarkMode: bindActionCreators(ToggleDarkMode, dispatch),
|
toggleDarkMode: bindActionCreators(ToggleDarkMode, dispatch),
|
||||||
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type Props = DispatchProps;
|
type Props = DispatchProps;
|
||||||
|
@ -17,7 +17,7 @@ const menus: SidebarMenu[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: AlignLeftOutlined,
|
Icon: AlignLeftOutlined,
|
||||||
to: ROUTES.TRACES,
|
to: ROUTES.TRACE,
|
||||||
name: 'Traces',
|
name: 'Traces',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
import Graph from 'components/Graph';
|
||||||
|
import { colors } from 'lib/getRandomColor';
|
||||||
|
import React, { memo } from 'react';
|
||||||
|
import { TraceReducer } from 'types/reducer/trace';
|
||||||
|
|
||||||
|
import { CustomGraphContainer } from './styles';
|
||||||
|
|
||||||
|
const TraceCustomGraph = ({
|
||||||
|
spansAggregate,
|
||||||
|
}: TraceCustomGraphProps): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<CustomGraphContainer>
|
||||||
|
<Graph
|
||||||
|
type="line"
|
||||||
|
data={{
|
||||||
|
labels: spansAggregate.map((s) => new Date(s.timestamp / 1000000)),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: spansAggregate.map((e) => e.value),
|
||||||
|
borderColor: colors[0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</CustomGraphContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TraceCustomGraphProps {
|
||||||
|
spansAggregate: TraceReducer['spansAggregate'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(TraceCustomGraph);
|
56
frontend/src/container/TraceCustomVisualization/config.ts
Normal file
56
frontend/src/container/TraceCustomVisualization/config.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
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' }],
|
||||||
|
},
|
||||||
|
];
|
127
frontend/src/container/TraceCustomVisualization/index.tsx
Normal file
127
frontend/src/container/TraceCustomVisualization/index.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
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 { bindActionCreators } from 'redux';
|
||||||
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
|
|
||||||
|
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';
|
||||||
|
import {
|
||||||
|
GetTraceVisualAggregates,
|
||||||
|
GetTraceVisualAggregatesProps,
|
||||||
|
} from 'store/actions/trace/getTraceVisualAgrregates';
|
||||||
|
|
||||||
|
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: any): 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);
|
34
frontend/src/container/TraceCustomVisualization/styles.ts
Normal file
34
frontend/src/container/TraceCustomVisualization/styles.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
Card as CardComponent,
|
||||||
|
Form,
|
||||||
|
Space as SpaceComponent,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const CustomGraphContainer = styled.div`
|
||||||
|
min-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;
|
||||||
|
}
|
||||||
|
`;
|
187
frontend/src/container/TraceFilter/Filter.tsx
Normal file
187
frontend/src/container/TraceFilter/Filter.tsx
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
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 {
|
||||||
|
UpdateSelectedLatency,
|
||||||
|
UpdateSelectedOperation,
|
||||||
|
UpdateSelectedService,
|
||||||
|
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);
|
160
frontend/src/container/TraceFilter/LatencyForm.tsx
Normal file
160
frontend/src/container/TraceFilter/LatencyForm.tsx
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
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);
|
15
frontend/src/container/TraceFilter/config.ts
Normal file
15
frontend/src/container/TraceFilter/config.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
interface SpanKindList {
|
||||||
|
label: 'SERVER' | 'CLIENT';
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const spanKindList: SpanKindList[] = [
|
||||||
|
{
|
||||||
|
label: 'SERVER',
|
||||||
|
value: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CLIENT',
|
||||||
|
value: '3',
|
||||||
|
},
|
||||||
|
];
|
384
frontend/src/container/TraceFilter/index.tsx
Normal file
384
frontend/src/container/TraceFilter/index.tsx
Normal file
@ -0,0 +1,384 @@
|
|||||||
|
import { Button, Input, Typography, notification } 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]);
|
||||||
|
|
||||||
|
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);
|
34
frontend/src/container/TraceFilter/styles.ts
Normal file
34
frontend/src/container/TraceFilter/styles.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
141
frontend/src/container/TraceList/index.tsx
Normal file
141
frontend/src/container/TraceList/index.tsx
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
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.TRACES + '/' + 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;
|
7
frontend/src/container/TraceList/styles.ts
Normal file
7
frontend/src/container/TraceList/styles.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Typography } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const TitleContainer = styled(Typography)`
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
`;
|
@ -1,3 +1,4 @@
|
|||||||
|
import './wdyr';
|
||||||
import 'assets/index.css';
|
import 'assets/index.css';
|
||||||
|
|
||||||
import AppRoutes from 'AppRoutes';
|
import AppRoutes from 'AppRoutes';
|
||||||
|
@ -2,6 +2,7 @@ const convertDateToAmAndPm = (date: Date): string => {
|
|||||||
return date.toLocaleString('en-US', {
|
return date.toLocaleString('en-US', {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: 'numeric',
|
minute: 'numeric',
|
||||||
|
second: 'numeric',
|
||||||
hour12: true,
|
hour12: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
6
frontend/src/lib/createQueryParams.ts
Normal file
6
frontend/src/lib/createQueryParams.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const createQueryParams = (params: { [x: string]: string }): string =>
|
||||||
|
Object.keys(params)
|
||||||
|
.map((k) => `${k}=${encodeURI(params[k])}`)
|
||||||
|
.join('&');
|
||||||
|
|
||||||
|
export default createQueryParams;
|
57
frontend/src/lib/getMinMax.ts
Normal file
57
frontend/src/lib/getMinMax.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { Time } from 'container/Header/DateTimeSelection/config';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
import getMinAgo from './getStartAndEndTime/getMinAgo';
|
||||||
|
|
||||||
|
const GetMinMax = (
|
||||||
|
interval: Time,
|
||||||
|
dateTimeRange?: [number, number],
|
||||||
|
): GetMinMaxPayload => {
|
||||||
|
let maxTime = new Date().getTime();
|
||||||
|
let minTime = 0;
|
||||||
|
|
||||||
|
if (interval === '1min') {
|
||||||
|
const minTimeAgo = getMinAgo({ minutes: 1 }).getTime();
|
||||||
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === '15min') {
|
||||||
|
const minTimeAgo = getMinAgo({ minutes: 15 }).getTime();
|
||||||
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === '1hr') {
|
||||||
|
const minTimeAgo = getMinAgo({ minutes: 60 }).getTime();
|
||||||
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === '30min') {
|
||||||
|
const minTimeAgo = getMinAgo({ minutes: 30 }).getTime();
|
||||||
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === '5min') {
|
||||||
|
const minTimeAgo = getMinAgo({ minutes: 5 }).getTime();
|
||||||
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === '1day') {
|
||||||
|
// one day = 24*60(min)
|
||||||
|
const minTimeAgo = getMinAgo({ minutes: 26 * 60 }).getTime();
|
||||||
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === '1week') {
|
||||||
|
// one week = one day * 7
|
||||||
|
const minTimeAgo = getMinAgo({ minutes: 26 * 60 * 7 }).getTime();
|
||||||
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === '6hr') {
|
||||||
|
const minTimeAgo = getMinAgo({ minutes: 6 * 60 }).getTime();
|
||||||
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === 'custom') {
|
||||||
|
maxTime = (dateTimeRange || [])[1] || 0;
|
||||||
|
minTime = (dateTimeRange || [])[0] || 0;
|
||||||
|
} else {
|
||||||
|
throw new Error('invalid time type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
minTime: minTime * 1000000,
|
||||||
|
maxTime: maxTime * 1000000,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GetMinMaxPayload {
|
||||||
|
minTime: GlobalReducer['minTime'];
|
||||||
|
maxTime: GlobalReducer['maxTime'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GetMinMax;
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { servicesItem } from "Src/store/actions";
|
import { servicesItem } from "store/actions";
|
||||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||||
import { Select } from "antd";
|
import { Select } from "antd";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
@ -69,7 +69,6 @@ const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
|
|||||||
initialValues={latencyFilterValues}
|
initialValues={latencyFilterValues}
|
||||||
>
|
>
|
||||||
<Row>
|
<Row>
|
||||||
{/* <Input.Group compact> */}
|
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="min"
|
name="min"
|
||||||
|
@ -247,7 +247,6 @@ const _TraceCustomVisualizations = (
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
xAxisType="timeseries"
|
|
||||||
/>
|
/>
|
||||||
</CustomGraphContainer>
|
</CustomGraphContainer>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -1,21 +1,10 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
|
||||||
import { GlobalTimeLoading } from 'store/actions';
|
|
||||||
import AppActions from 'types/actions';
|
|
||||||
|
|
||||||
import { TraceCustomVisualizations } from './TraceCustomVisualizations';
|
import { TraceCustomVisualizations } from './TraceCustomVisualizations';
|
||||||
import { TraceFilter } from './TraceFilter';
|
import { TraceFilter } from './TraceFilter';
|
||||||
import { TraceList } from './TraceList';
|
import { TraceList } from './TraceList';
|
||||||
|
|
||||||
const TraceDetail = ({ globalTimeLoading }: Props): JSX.Element => {
|
const TraceDetail = (): JSX.Element => {
|
||||||
useEffect(() => {
|
|
||||||
return (): void => {
|
|
||||||
globalTimeLoading();
|
|
||||||
};
|
|
||||||
}, [globalTimeLoading]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TraceFilter />
|
<TraceFilter />
|
||||||
@ -25,16 +14,4 @@ const TraceDetail = ({ globalTimeLoading }: Props): JSX.Element => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DispatchProps {
|
export default TraceDetail;
|
||||||
globalTimeLoading: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
|
||||||
): DispatchProps => ({
|
|
||||||
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = DispatchProps;
|
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(TraceDetail);
|
|
||||||
|
@ -120,15 +120,6 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
|
|
||||||
const handleApplyFilterForm = useCallback(
|
const handleApplyFilterForm = useCallback(
|
||||||
(values: any): void => {
|
(values: any): void => {
|
||||||
// setTagKeyValueApplied((tagKeyValueApplied) => [
|
|
||||||
// ...tagKeyValueApplied,
|
|
||||||
// 'service eq' + values.service,
|
|
||||||
// 'operation eq ' + values.operation,
|
|
||||||
// 'maxduration eq ' +
|
|
||||||
// (parseInt(latencyFilterValues.max) / 1000000).toString(),
|
|
||||||
// 'minduration eq ' +
|
|
||||||
// (parseInt(latencyFilterValues.min) / 1000000).toString(),
|
|
||||||
// ]);
|
|
||||||
updateTraceFilters({
|
updateTraceFilters({
|
||||||
service: values.service,
|
service: values.service,
|
||||||
operation: values.operation,
|
operation: values.operation,
|
||||||
@ -272,10 +263,6 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
'&tags=' +
|
'&tags=' +
|
||||||
encodeURIComponent(JSON.stringify(traceFilters.tags));
|
encodeURIComponent(JSON.stringify(traceFilters.tags));
|
||||||
|
|
||||||
/*
|
|
||||||
Call the apis only when the route is loaded.
|
|
||||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
|
||||||
*/
|
|
||||||
if (loading === false) {
|
if (loading === false) {
|
||||||
fetchTraces(globalTime, request_string);
|
fetchTraces(globalTime, request_string);
|
||||||
}
|
}
|
||||||
@ -305,19 +292,10 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
'ms';
|
'ms';
|
||||||
|
|
||||||
form_basefilter.setFieldsValue({ latency: latencyButtonText });
|
form_basefilter.setFieldsValue({ latency: latencyButtonText });
|
||||||
}, [traceFilters.latency, form_basefilter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form_basefilter.setFieldsValue({ service: traceFilters.service });
|
form_basefilter.setFieldsValue({ service: traceFilters.service });
|
||||||
}, [traceFilters.service, form_basefilter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form_basefilter.setFieldsValue({ operation: traceFilters.operation });
|
form_basefilter.setFieldsValue({ operation: traceFilters.operation });
|
||||||
}, [traceFilters.operation, form_basefilter]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
form_basefilter.setFieldsValue({ kind: traceFilters.kind });
|
form_basefilter.setFieldsValue({ kind: traceFilters.kind });
|
||||||
}, [traceFilters.kind, form_basefilter]);
|
}, [traceFilters, form_basefilter]);
|
||||||
|
|
||||||
const onLatencyButtonClick = (): void => {
|
const onLatencyButtonClick = (): void => {
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
@ -438,8 +416,6 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
|
|
||||||
<FilterStateDisplay />
|
<FilterStateDisplay />
|
||||||
|
|
||||||
{/* // What will be the empty state of card when there is no Tag , it should show something */}
|
|
||||||
|
|
||||||
<InfoWrapper>Select Service to get Tag suggestions </InfoWrapper>
|
<InfoWrapper>Select Service to get Tag suggestions </InfoWrapper>
|
||||||
|
|
||||||
<Form
|
<Form
|
||||||
|
@ -1,67 +1,62 @@
|
|||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import MetricsApplicationContainer from 'container/MetricsApplication';
|
import MetricsApplicationContainer from 'container/MetricsApplication';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import {
|
import {
|
||||||
GetInitialData,
|
GetInitialData,
|
||||||
GetInitialDataProps,
|
GetInitialDataProps,
|
||||||
} from 'store/actions/metrics/getInitialData';
|
} from 'store/actions/metrics/getInitialData';
|
||||||
|
import { ResetInitialData } from 'store/actions/metrics/resetInitialData';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import MetricReducer from 'types/reducer/metrics';
|
import MetricReducer from 'types/reducer/metrics';
|
||||||
|
|
||||||
const MetricsApplication = ({ getInitialData }: MetricsProps): JSX.Element => {
|
const MetricsApplication = ({
|
||||||
const { loading, maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
getInitialData,
|
||||||
|
resetInitialData,
|
||||||
|
}: MetricsProps): JSX.Element => {
|
||||||
|
const { selectedTime } = useSelector<AppState, GlobalReducer>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
const { error, errorMessage } = useSelector<AppState, MetricReducer>(
|
const { error, errorMessage, metricsApplicationLoading } = useSelector<
|
||||||
(state) => state.metrics,
|
AppState,
|
||||||
);
|
MetricReducer
|
||||||
|
>((state) => state.metrics);
|
||||||
|
|
||||||
const { servicename } = useParams<ServiceProps>();
|
const { servicename } = useParams<ServiceProps>();
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (servicename !== undefined && loading == false) {
|
if (servicename !== undefined) {
|
||||||
getInitialData({
|
getInitialData({
|
||||||
end: maxTime,
|
selectedTimeInterval: selectedTime,
|
||||||
service: servicename,
|
serviceName: servicename,
|
||||||
start: minTime,
|
|
||||||
step: 60,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (): void => {
|
return () => {
|
||||||
// setting the data to it's initial this will avoid the re-rendering the graph
|
resetInitialData();
|
||||||
dispatch({
|
|
||||||
type: 'GET_INTIAL_APPLICATION_DATA',
|
|
||||||
payload: {
|
|
||||||
serviceOverview: [],
|
|
||||||
topEndPoints: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}, [servicename, maxTime, minTime, getInitialData, loading, dispatch]);
|
}, [servicename, getInitialData, selectedTime]);
|
||||||
|
|
||||||
|
if (metricsApplicationLoading) {
|
||||||
|
return <Spinner tip="Loading..." />;
|
||||||
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <Typography>{errorMessage}</Typography>;
|
return <Typography>{errorMessage}</Typography>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <Spinner tip="Loading..." />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <MetricsApplicationContainer />;
|
return <MetricsApplicationContainer />;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
getInitialData: (props: GetInitialDataProps) => void;
|
getInitialData: (props: GetInitialDataProps) => void;
|
||||||
|
resetInitialData: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ServiceProps {
|
interface ServiceProps {
|
||||||
@ -72,6 +67,7 @@ const mapDispatchToProps = (
|
|||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
): DispatchProps => ({
|
): DispatchProps => ({
|
||||||
getInitialData: bindActionCreators(GetInitialData, dispatch),
|
getInitialData: bindActionCreators(GetInitialData, dispatch),
|
||||||
|
resetInitialData: bindActionCreators(ResetInitialData, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
type MetricsProps = DispatchProps;
|
type MetricsProps = DispatchProps;
|
||||||
|
@ -5,16 +5,17 @@ import React, { useEffect } from 'react';
|
|||||||
import { connect, useSelector } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { GetService, GetServiceProps } from 'store/actions';
|
import { GetService, GetServiceProps } from 'store/actions/metrics';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import MetricReducer from 'types/reducer/metrics';
|
import MetricReducer from 'types/reducer/metrics';
|
||||||
|
|
||||||
const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
||||||
const { minTime, maxTime, loading } = useSelector<AppState, GlobalReducer>(
|
const { minTime, maxTime, loading, selectedTime } = useSelector<
|
||||||
(state) => state.globalTime,
|
AppState,
|
||||||
);
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
const { services } = useSelector<AppState, MetricReducer>(
|
const { services } = useSelector<AppState, MetricReducer>(
|
||||||
(state) => state.metrics,
|
(state) => state.metrics,
|
||||||
);
|
);
|
||||||
@ -24,11 +25,10 @@ const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading === false) {
|
if (loading === false) {
|
||||||
getService({
|
getService({
|
||||||
start: minTime,
|
selectedTimeInterval: selectedTime,
|
||||||
end: maxTime,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [getService, maxTime, minTime, loading]);
|
}, [getService, loading, selectedTime]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timeInterval: NodeJS.Timeout;
|
let timeInterval: NodeJS.Timeout;
|
||||||
@ -36,8 +36,7 @@ const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
|||||||
if (loading === false && !isSkipped && services.length === 0) {
|
if (loading === false && !isSkipped && services.length === 0) {
|
||||||
timeInterval = setInterval(() => {
|
timeInterval = setInterval(() => {
|
||||||
getService({
|
getService({
|
||||||
start: minTime,
|
selectedTimeInterval: selectedTime,
|
||||||
end: maxTime,
|
|
||||||
});
|
});
|
||||||
}, 50000);
|
}, 50000);
|
||||||
}
|
}
|
||||||
@ -45,7 +44,7 @@ const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
|||||||
return (): void => {
|
return (): void => {
|
||||||
clearInterval(timeInterval);
|
clearInterval(timeInterval);
|
||||||
};
|
};
|
||||||
}, [getService, isSkipped, loading, maxTime, minTime, services]);
|
}, [getService, isSkipped, loading, maxTime, minTime, services, selectedTime]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Spinner tip="Loading..." />;
|
return <Spinner tip="Loading..." />;
|
||||||
@ -55,10 +54,9 @@ const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
getService: ({
|
getService: (
|
||||||
end,
|
props: GetServiceProps,
|
||||||
start,
|
) => (dispatch: Dispatch<AppActions>, getState: () => AppState) => void;
|
||||||
}: GetServiceProps) => (dispatch: Dispatch<AppActions>) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
const mapDispatchToProps = (
|
||||||
|
@ -6,7 +6,7 @@ import React, { useState } from 'react';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { GlobalTimeLoading, UserLoggedIn } from 'store/actions';
|
import { UserLoggedIn } from 'store/actions';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -17,7 +17,7 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
const Signup = ({ globalLoading, loggedIn }: SignupProps): JSX.Element => {
|
const Signup = ({ loggedIn }: SignupProps): JSX.Element => {
|
||||||
const [state, setState] = useState({ submitted: false });
|
const [state, setState] = useState({ submitted: false });
|
||||||
const [formState, setFormState] = useState({
|
const [formState, setFormState] = useState({
|
||||||
firstName: { value: '' },
|
firstName: { value: '' },
|
||||||
@ -59,7 +59,6 @@ const Signup = ({ globalLoading, loggedIn }: SignupProps): JSX.Element => {
|
|||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
loggedIn();
|
loggedIn();
|
||||||
globalLoading();
|
|
||||||
history.push(ROUTES.APPLICATION);
|
history.push(ROUTES.APPLICATION);
|
||||||
} else {
|
} else {
|
||||||
// @TODO throw a error notification here
|
// @TODO throw a error notification here
|
||||||
@ -125,14 +124,12 @@ const Signup = ({ globalLoading, loggedIn }: SignupProps): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
globalLoading: () => void;
|
|
||||||
loggedIn: () => void;
|
loggedIn: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
const mapDispatchToProps = (
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
): DispatchProps => ({
|
): DispatchProps => ({
|
||||||
globalLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
|
||||||
loggedIn: bindActionCreators(UserLoggedIn, dispatch),
|
loggedIn: bindActionCreators(UserLoggedIn, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
76
frontend/src/pages/TraceDetails/index.tsx
Normal file
76
frontend/src/pages/TraceDetails/index.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
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,
|
||||||
|
ResetRaceData,
|
||||||
|
GetInitialTraceDataProps,
|
||||||
|
} 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]);
|
||||||
|
|
||||||
|
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,5 +1,5 @@
|
|||||||
import { Time } from 'container/Header/DateTimeSelection/config';
|
import { Time } from 'container/Header/DateTimeSelection/config';
|
||||||
import getMinAgo from 'lib/getStartAndEndTime/getMinAgo';
|
import GetMinMax from 'lib/getMinMax';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
|
|
||||||
@ -8,45 +8,14 @@ export const UpdateTimeInterval = (
|
|||||||
dateTimeRange: [number, number] = [0, 0],
|
dateTimeRange: [number, number] = [0, 0],
|
||||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||||
return (dispatch: Dispatch<AppActions>): void => {
|
return (dispatch: Dispatch<AppActions>): void => {
|
||||||
let maxTime = new Date().getTime();
|
const { maxTime, minTime } = GetMinMax(interval, dateTimeRange);
|
||||||
let minTime = 0;
|
|
||||||
|
|
||||||
if (interval === '1min') {
|
|
||||||
const minTimeAgo = getMinAgo({ minutes: 1 }).getTime();
|
|
||||||
minTime = minTimeAgo;
|
|
||||||
} else if (interval === '15min') {
|
|
||||||
const minTimeAgo = getMinAgo({ minutes: 15 }).getTime();
|
|
||||||
minTime = minTimeAgo;
|
|
||||||
} else if (interval === '1hr') {
|
|
||||||
const minTimeAgo = getMinAgo({ minutes: 60 }).getTime();
|
|
||||||
minTime = minTimeAgo;
|
|
||||||
} else if (interval === '30min') {
|
|
||||||
const minTimeAgo = getMinAgo({ minutes: 30 }).getTime();
|
|
||||||
minTime = minTimeAgo;
|
|
||||||
} else if (interval === '5min') {
|
|
||||||
const minTimeAgo = getMinAgo({ minutes: 5 }).getTime();
|
|
||||||
minTime = minTimeAgo;
|
|
||||||
} else if (interval === '1day') {
|
|
||||||
// one day = 24*60(min)
|
|
||||||
const minTimeAgo = getMinAgo({ minutes: 26 * 60 }).getTime();
|
|
||||||
minTime = minTimeAgo;
|
|
||||||
} else if (interval === '1week') {
|
|
||||||
// one week = one day * 7
|
|
||||||
const minTimeAgo = getMinAgo({ minutes: 26 * 60 * 7 }).getTime();
|
|
||||||
minTime = minTimeAgo;
|
|
||||||
} else if (interval === '6hr') {
|
|
||||||
const minTimeAgo = getMinAgo({ minutes: 6 * 60 }).getTime();
|
|
||||||
minTime = minTimeAgo;
|
|
||||||
} else if (interval === 'custom') {
|
|
||||||
maxTime = dateTimeRange[1];
|
|
||||||
minTime = dateTimeRange[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_TIME_INTERVAL',
|
type: 'UPDATE_TIME_INTERVAL',
|
||||||
payload: {
|
payload: {
|
||||||
maxTime: maxTime * 1000000, // in nano sec,
|
maxTime: maxTime,
|
||||||
minTime: minTime * 1000000,
|
minTime: minTime,
|
||||||
|
selectedTime: interval,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -5,19 +5,38 @@
|
|||||||
import getServiceOverview from 'api/metrics/getServiceOverview';
|
import getServiceOverview from 'api/metrics/getServiceOverview';
|
||||||
import getTopEndPoints from 'api/metrics/getTopEndPoints';
|
import getTopEndPoints from 'api/metrics/getTopEndPoints';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import GetMinMax from 'lib/getMinMax';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { Props } from 'types/api/metrics/getDBOverview';
|
import { Props } from 'types/api/metrics/getDBOverview';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
export const GetInitialData = (
|
export const GetInitialData = (
|
||||||
props: GetInitialDataProps,
|
props: GetInitialDataProps,
|
||||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
): ((dispatch: Dispatch<AppActions>, getState: () => AppState) => void) => {
|
||||||
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
return async (dispatch, getState): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
const { globalTime } = getState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description This is because we keeping the store as source of truth
|
||||||
|
*/
|
||||||
|
if (props.selectedTimeInterval !== globalTime.selectedTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'GET_INITIAL_APPLICATION_LOADING',
|
type: 'GET_INITIAL_APPLICATION_LOADING',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { maxTime, minTime } = GetMinMax(props.selectedTimeInterval, [
|
||||||
|
globalTime.minTime / 1000000,
|
||||||
|
globalTime.maxTime / 1000000,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const step = 60;
|
||||||
|
|
||||||
const [
|
const [
|
||||||
// getDBOverViewResponse,
|
// getDBOverViewResponse,
|
||||||
// getExternalAverageDurationResponse,
|
// getExternalAverageDurationResponse,
|
||||||
@ -39,10 +58,15 @@ export const GetInitialData = (
|
|||||||
// ...props,
|
// ...props,
|
||||||
// }),
|
// }),
|
||||||
getServiceOverview({
|
getServiceOverview({
|
||||||
...props,
|
end: maxTime,
|
||||||
|
service: props.serviceName,
|
||||||
|
start: minTime,
|
||||||
|
step,
|
||||||
}),
|
}),
|
||||||
getTopEndPoints({
|
getTopEndPoints({
|
||||||
...props,
|
end: maxTime,
|
||||||
|
service: props.serviceName,
|
||||||
|
start: minTime,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -91,4 +115,7 @@ export const GetInitialData = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetInitialDataProps = Props;
|
export interface GetInitialDataProps {
|
||||||
|
serviceName: Props['service'];
|
||||||
|
selectedTimeInterval: GlobalReducer['selectedTime'];
|
||||||
|
}
|
||||||
|
@ -1,20 +1,35 @@
|
|||||||
import getService from 'api/metrics/getService';
|
import getService from 'api/metrics/getService';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import GetMinMax from 'lib/getMinMax';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { Props } from 'types/api/metrics/getService';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
export const GetService = ({
|
export const GetService = (
|
||||||
end,
|
props: GetServiceProps,
|
||||||
start,
|
): ((dispatch: Dispatch<AppActions>, getState: () => AppState) => void) => {
|
||||||
}: GetServiceProps): ((dispatch: Dispatch<AppActions>) => void) => {
|
return async (dispatch, getState): Promise<void> => {
|
||||||
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
|
||||||
try {
|
try {
|
||||||
|
const { globalTime } = getState();
|
||||||
|
|
||||||
|
if (props.selectedTimeInterval !== globalTime.selectedTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { maxTime, minTime } = GetMinMax(props.selectedTimeInterval, [
|
||||||
|
globalTime.minTime / 1000000,
|
||||||
|
globalTime.maxTime / 1000000,
|
||||||
|
]);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'GET_SERVICE_LIST_LOADING_START',
|
type: 'GET_SERVICE_LIST_LOADING_START',
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await getService({ end, start });
|
const response = await getService({
|
||||||
|
end: maxTime,
|
||||||
|
start: minTime,
|
||||||
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -40,4 +55,6 @@ export const GetService = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetServiceProps = Props;
|
export type GetServiceProps = {
|
||||||
|
selectedTimeInterval: GlobalReducer['selectedTime'];
|
||||||
|
};
|
||||||
|
14
frontend/src/store/actions/metrics/resetInitialData.ts
Normal file
14
frontend/src/store/actions/metrics/resetInitialData.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
|
||||||
|
export const ResetInitialData = (): ((
|
||||||
|
dispatch: Dispatch<AppActions>,
|
||||||
|
getState: () => AppState,
|
||||||
|
) => void) => {
|
||||||
|
return (dispatch, getState): void => {
|
||||||
|
dispatch({
|
||||||
|
type: 'RESET_INITIAL_APPLICATION_DATA',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
201
frontend/src/store/actions/trace/getInitialData.ts
Normal file
201
frontend/src/store/actions/trace/getInitialData.ts
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
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'];
|
||||||
|
}
|
92
frontend/src/store/actions/trace/getTraceVisualAgrregates.ts
Normal file
92
frontend/src/store/actions/trace/getTraceVisualAgrregates.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
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'];
|
||||||
|
}
|
9
frontend/src/store/actions/trace/index.ts
Normal file
9
frontend/src/store/actions/trace/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export * from './getInitialData';
|
||||||
|
export * from './updateSelectedAggOption';
|
||||||
|
export * from './updateSelectedEntity';
|
||||||
|
export * from './updateSelectedKind';
|
||||||
|
export * from './updateSelectedLatency';
|
||||||
|
export * from './updateSelectedOperation';
|
||||||
|
export * from './updateSelectedService';
|
||||||
|
export * from './updateSelectedTags';
|
||||||
|
export * from './resetTraceDetails';
|
12
frontend/src/store/actions/trace/loadingCompleted.ts
Normal file
12
frontend/src/store/actions/trace/loadingCompleted.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
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',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
10
frontend/src/store/actions/trace/resetTraceDetails.ts
Normal file
10
frontend/src/store/actions/trace/resetTraceDetails.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
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',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
16
frontend/src/store/actions/trace/updateSelectedAggOption.ts
Normal file
16
frontend/src/store/actions/trace/updateSelectedAggOption.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
164
frontend/src/store/actions/trace/updateSelectedData.ts
Normal file
164
frontend/src/store/actions/trace/updateSelectedData.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
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'];
|
||||||
|
}
|
16
frontend/src/store/actions/trace/updateSelectedEntity.ts
Normal file
16
frontend/src/store/actions/trace/updateSelectedEntity.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
16
frontend/src/store/actions/trace/updateSelectedKind.ts
Normal file
16
frontend/src/store/actions/trace/updateSelectedKind.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
16
frontend/src/store/actions/trace/updateSelectedLatency.ts
Normal file
16
frontend/src/store/actions/trace/updateSelectedLatency.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
16
frontend/src/store/actions/trace/updateSelectedOperation.ts
Normal file
16
frontend/src/store/actions/trace/updateSelectedOperation.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
16
frontend/src/store/actions/trace/updateSelectedService.ts
Normal file
16
frontend/src/store/actions/trace/updateSelectedService.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
94
frontend/src/store/actions/trace/updateSelectedTags.ts
Normal file
94
frontend/src/store/actions/trace/updateSelectedTags.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
16
frontend/src/store/actions/trace/updateSpanLoading.ts
Normal file
16
frontend/src/store/actions/trace/updateSpanLoading.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
@ -1,3 +1,4 @@
|
|||||||
|
import { getDefaultOption } from 'container/Header/DateTimeSelection/config';
|
||||||
import {
|
import {
|
||||||
GLOBAL_TIME_LOADING_START,
|
GLOBAL_TIME_LOADING_START,
|
||||||
GlobalTimeAction,
|
GlobalTimeAction,
|
||||||
@ -9,6 +10,7 @@ const intitalState: GlobalReducer = {
|
|||||||
maxTime: Date.now() * 1000000,
|
maxTime: Date.now() * 1000000,
|
||||||
minTime: (Date.now() - 15 * 60 * 1000) * 1000000,
|
minTime: (Date.now() - 15 * 60 * 1000) * 1000000,
|
||||||
loading: true,
|
loading: true,
|
||||||
|
selectedTime: getDefaultOption(location.pathname),
|
||||||
};
|
};
|
||||||
|
|
||||||
const globalTimeReducer = (
|
const globalTimeReducer = (
|
||||||
|
@ -6,6 +6,7 @@ import globalTimeReducer from './global';
|
|||||||
import metricsReducers from './metric';
|
import metricsReducers from './metric';
|
||||||
import { metricsReducer } from './metrics';
|
import { metricsReducer } from './metrics';
|
||||||
import { ServiceMapReducer } from './serviceMap';
|
import { ServiceMapReducer } from './serviceMap';
|
||||||
|
import { traceReducer } from './trace';
|
||||||
import TraceFilterReducer from './traceFilters';
|
import TraceFilterReducer from './traceFilters';
|
||||||
import { traceItemReducer, tracesReducer } from './traces';
|
import { traceItemReducer, tracesReducer } from './traces';
|
||||||
import { usageDataReducer } from './usage';
|
import { usageDataReducer } from './usage';
|
||||||
@ -14,6 +15,7 @@ const reducers = combineReducers({
|
|||||||
traceFilters: TraceFilterReducer,
|
traceFilters: TraceFilterReducer,
|
||||||
traces: tracesReducer,
|
traces: tracesReducer,
|
||||||
traceItem: traceItemReducer,
|
traceItem: traceItemReducer,
|
||||||
|
trace: traceReducer,
|
||||||
usageDate: usageDataReducer,
|
usageDate: usageDataReducer,
|
||||||
globalTime: globalTimeReducer,
|
globalTime: globalTimeReducer,
|
||||||
metricsData: metricsReducer,
|
metricsData: metricsReducer,
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
GET_SERVICE_LIST_ERROR,
|
GET_SERVICE_LIST_ERROR,
|
||||||
GET_SERVICE_LIST_LOADING_START,
|
GET_SERVICE_LIST_LOADING_START,
|
||||||
GET_SERVICE_LIST_SUCCESS,
|
GET_SERVICE_LIST_SUCCESS,
|
||||||
|
RESET_INITIAL_APPLICATION_DATA,
|
||||||
MetricsActions,
|
MetricsActions,
|
||||||
} from 'types/actions/metrics';
|
} from 'types/actions/metrics';
|
||||||
import InitialValueTypes from 'types/reducer/metrics';
|
import InitialValueTypes from 'types/reducer/metrics';
|
||||||
@ -12,7 +13,8 @@ import InitialValueTypes from 'types/reducer/metrics';
|
|||||||
const InitialValue: InitialValueTypes = {
|
const InitialValue: InitialValueTypes = {
|
||||||
error: false,
|
error: false,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
loading: false,
|
loading: true,
|
||||||
|
metricsApplicationLoading: true,
|
||||||
services: [],
|
services: [],
|
||||||
dbOverView: [],
|
dbOverView: [],
|
||||||
externalService: [],
|
externalService: [],
|
||||||
@ -56,18 +58,24 @@ const metrics = (
|
|||||||
case GET_INITIAL_APPLICATION_LOADING: {
|
case GET_INITIAL_APPLICATION_LOADING: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: true,
|
metricsApplicationLoading: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case GET_INITIAL_APPLICATION_ERROR: {
|
case GET_INITIAL_APPLICATION_ERROR: {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: false,
|
metricsApplicationLoading: false,
|
||||||
errorMessage: action.payload.errorMessage,
|
errorMessage: action.payload.errorMessage,
|
||||||
error: true,
|
error: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case RESET_INITIAL_APPLICATION_DATA: {
|
||||||
|
return {
|
||||||
|
...InitialValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case GET_INTIAL_APPLICATION_DATA: {
|
case GET_INTIAL_APPLICATION_DATA: {
|
||||||
const {
|
const {
|
||||||
// dbOverView,
|
// dbOverView,
|
||||||
@ -80,13 +88,13 @@ const metrics = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
loading: false,
|
|
||||||
// dbOverView,
|
// dbOverView,
|
||||||
topEndPoints,
|
topEndPoints,
|
||||||
serviceOverview,
|
serviceOverview,
|
||||||
// externalService,
|
// externalService,
|
||||||
// externalAverageDuration,
|
// externalAverageDuration,
|
||||||
// externalError,
|
// externalError,
|
||||||
|
metricsApplicationLoading: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
204
frontend/src/store/reducers/trace.ts
Normal file
204
frontend/src/store/reducers/trace.ts
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import {
|
||||||
|
GET_TRACE_INITIAL_DATA_ERROR,
|
||||||
|
GET_TRACE_INITIAL_DATA_SUCCESS,
|
||||||
|
GET_TRACE_LOADING_END,
|
||||||
|
GET_TRACE_LOADING_START,
|
||||||
|
TraceActions,
|
||||||
|
UPDATE_SELECTED_AGG_OPTION,
|
||||||
|
UPDATE_SELECTED_ENTITY,
|
||||||
|
UPDATE_SELECTED_TRACE_DATA,
|
||||||
|
UPDATE_SPANS_LOADING,
|
||||||
|
UPDATE_TRACE_SELECTED_KIND,
|
||||||
|
UPDATE_TRACE_SELECTED_LATENCY_VALUE,
|
||||||
|
UPDATE_TRACE_SELECTED_OPERATION,
|
||||||
|
UPDATE_TRACE_SELECTED_SERVICE,
|
||||||
|
UPDATE_TRACE_SELECTED_TAGS,
|
||||||
|
RESET_TRACE_DATA,
|
||||||
|
UPDATE_AGGREGATES,
|
||||||
|
} from 'types/actions/trace';
|
||||||
|
import { TraceReducer } from 'types/reducer/trace';
|
||||||
|
|
||||||
|
const intitalState: TraceReducer = {
|
||||||
|
error: false,
|
||||||
|
errorMessage: '',
|
||||||
|
loading: true,
|
||||||
|
operationsList: [],
|
||||||
|
selectedKind: '',
|
||||||
|
selectedLatency: {
|
||||||
|
max: '',
|
||||||
|
min: '',
|
||||||
|
},
|
||||||
|
selectedOperation: '',
|
||||||
|
selectedService: '',
|
||||||
|
selectedTags: [],
|
||||||
|
serviceList: [],
|
||||||
|
spanList: [],
|
||||||
|
tagsSuggestions: [],
|
||||||
|
selectedAggOption: 'count',
|
||||||
|
selectedEntity: 'calls',
|
||||||
|
spansAggregate: [],
|
||||||
|
spansLoading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const traceReducer = (
|
||||||
|
state = intitalState,
|
||||||
|
action: TraceActions,
|
||||||
|
): TraceReducer => {
|
||||||
|
switch (action.type) {
|
||||||
|
case GET_TRACE_INITIAL_DATA_ERROR: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
errorMessage: action.payload.errorMessage,
|
||||||
|
loading: false,
|
||||||
|
error: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case GET_TRACE_LOADING_START: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
spansLoading: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case GET_TRACE_INITIAL_DATA_SUCCESS: {
|
||||||
|
const {
|
||||||
|
serviceList,
|
||||||
|
operationList,
|
||||||
|
tagsSuggestions,
|
||||||
|
selectedOperation,
|
||||||
|
selectedService,
|
||||||
|
selectedTags,
|
||||||
|
spansList,
|
||||||
|
selectedKind,
|
||||||
|
selectedLatency,
|
||||||
|
spansAggregate,
|
||||||
|
} = action.payload;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
serviceList: serviceList,
|
||||||
|
tagsSuggestions,
|
||||||
|
selectedOperation,
|
||||||
|
selectedService,
|
||||||
|
selectedTags,
|
||||||
|
spanList: spansList,
|
||||||
|
operationsList: operationList,
|
||||||
|
error: false,
|
||||||
|
selectedKind,
|
||||||
|
selectedLatency,
|
||||||
|
spansAggregate,
|
||||||
|
spansLoading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_TRACE_SELECTED_KIND: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedKind: action.payload.selectedKind,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_TRACE_SELECTED_LATENCY_VALUE: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedLatency: action.payload.selectedLatency,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_TRACE_SELECTED_OPERATION: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedOperation: action.payload.selectedOperation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_TRACE_SELECTED_SERVICE: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedService: action.payload.selectedService,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_TRACE_SELECTED_TAGS: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedTags: action.payload.selectedTags,
|
||||||
|
spanList: action.payload.spansList,
|
||||||
|
spansAggregate: action.payload.spansAggregate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_SELECTED_TRACE_DATA: {
|
||||||
|
const {
|
||||||
|
spansList,
|
||||||
|
tagsSuggestions,
|
||||||
|
operationList,
|
||||||
|
selectedOperation,
|
||||||
|
selectedLatency,
|
||||||
|
selectedService,
|
||||||
|
selectedKind,
|
||||||
|
spansAggregate,
|
||||||
|
} = action.payload;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
spanList: spansList,
|
||||||
|
tagsSuggestions,
|
||||||
|
operationsList: operationList,
|
||||||
|
selectedOperation,
|
||||||
|
selectedLatency,
|
||||||
|
selectedService,
|
||||||
|
selectedKind,
|
||||||
|
spansAggregate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case GET_TRACE_LOADING_END: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_SELECTED_AGG_OPTION: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedAggOption: action.payload.selectedAggOption,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_SELECTED_ENTITY: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selectedEntity: action.payload.selectedEntity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_SPANS_LOADING: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
spansLoading: action.payload.loading,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case RESET_TRACE_DATA: {
|
||||||
|
return {
|
||||||
|
...intitalState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case UPDATE_AGGREGATES: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
spansAggregate: action.payload.spansAggregate,
|
||||||
|
selectedAggOption: action.payload.selectedAggOption,
|
||||||
|
selectedEntity: action.payload.selectedEntity,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
@ -1,9 +1,11 @@
|
|||||||
import { ActionTypes, TraceFilters } from 'store/actions';
|
import { TraceFilters } from 'store/actions/traceFilters';
|
||||||
|
import { ActionTypes } from 'store/actions/types';
|
||||||
|
|
||||||
type ACTION = {
|
type ACTION = {
|
||||||
type: ActionTypes;
|
type: ActionTypes;
|
||||||
payload: TraceFilters;
|
payload: TraceFilters;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: TraceFilters = {
|
const initialState: TraceFilters = {
|
||||||
service: '',
|
service: '',
|
||||||
tags: [],
|
tags: [],
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Time } from 'container/Header/DateTimeSelection/config';
|
||||||
|
|
||||||
export const UPDATE_TIME_INTERVAL = 'UPDATE_TIME_INTERVAL';
|
export const UPDATE_TIME_INTERVAL = 'UPDATE_TIME_INTERVAL';
|
||||||
export const GLOBAL_TIME_LOADING_START = 'GLOBAL_TIME_LOADING_START';
|
export const GLOBAL_TIME_LOADING_START = 'GLOBAL_TIME_LOADING_START';
|
||||||
|
|
||||||
@ -6,9 +8,13 @@ export type GlobalTime = {
|
|||||||
minTime: number;
|
minTime: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface UpdateTime extends GlobalTime {
|
||||||
|
selectedTime: Time;
|
||||||
|
}
|
||||||
|
|
||||||
interface UpdateTimeInterval {
|
interface UpdateTimeInterval {
|
||||||
type: typeof UPDATE_TIME_INTERVAL;
|
type: typeof UPDATE_TIME_INTERVAL;
|
||||||
payload: GlobalTime;
|
payload: UpdateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GlobalTimeLoading {
|
interface GlobalTimeLoading {
|
||||||
|
@ -2,11 +2,13 @@ import { AppAction } from './app';
|
|||||||
import { DashboardActions } from './dashboard';
|
import { DashboardActions } from './dashboard';
|
||||||
import { GlobalTimeAction } from './globalTime';
|
import { GlobalTimeAction } from './globalTime';
|
||||||
import { MetricsActions } from './metrics';
|
import { MetricsActions } from './metrics';
|
||||||
|
import { TraceActions } from './trace';
|
||||||
|
|
||||||
type AppActions =
|
type AppActions =
|
||||||
| DashboardActions
|
| DashboardActions
|
||||||
| AppAction
|
| AppAction
|
||||||
| GlobalTimeAction
|
| GlobalTimeAction
|
||||||
| MetricsActions;
|
| MetricsActions
|
||||||
|
| TraceActions;
|
||||||
|
|
||||||
export default AppActions;
|
export default AppActions;
|
||||||
|
@ -13,7 +13,7 @@ export const GET_INITIAL_APPLICATION_LOADING =
|
|||||||
'GET_INITIAL_APPLICATION_LOADING';
|
'GET_INITIAL_APPLICATION_LOADING';
|
||||||
export const GET_INITIAL_APPLICATION_ERROR = 'GET_INITIAL_APPLICATION_ERROR';
|
export const GET_INITIAL_APPLICATION_ERROR = 'GET_INITIAL_APPLICATION_ERROR';
|
||||||
export const GET_INTIAL_APPLICATION_DATA = 'GET_INTIAL_APPLICATION_DATA';
|
export const GET_INTIAL_APPLICATION_DATA = 'GET_INTIAL_APPLICATION_DATA';
|
||||||
|
export const RESET_INITIAL_APPLICATION_DATA = 'RESET_INITIAL_APPLICATION_DATA';
|
||||||
export interface GetServiceList {
|
export interface GetServiceList {
|
||||||
type: typeof GET_SERVICE_LIST_SUCCESS;
|
type: typeof GET_SERVICE_LIST_SUCCESS;
|
||||||
payload: ServicesList[];
|
payload: ServicesList[];
|
||||||
@ -44,8 +44,13 @@ export interface GetInitialApplicationData {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResetInitialApplicationData {
|
||||||
|
type: typeof RESET_INITIAL_APPLICATION_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
export type MetricsActions =
|
export type MetricsActions =
|
||||||
| GetServiceListError
|
| GetServiceListError
|
||||||
| GetServiceListLoading
|
| GetServiceListLoading
|
||||||
| GetServiceList
|
| GetServiceList
|
||||||
| GetInitialApplicationData;
|
| GetInitialApplicationData
|
||||||
|
| ResetInitialApplicationData;
|
||||||
|
151
frontend/src/types/actions/trace.ts
Normal file
151
frontend/src/types/actions/trace.ts
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
export const GET_TRACE_INITIAL_DATA_SUCCESS = 'GET_TRACE_INITIAL_DATA_SUCCESS';
|
||||||
|
export const GET_TRACE_INITIAL_DATA_ERROR = 'GET_TRACE_INITIAL_DATA_ERROR';
|
||||||
|
export const GET_TRACE_LOADING_START = 'GET_TRACE_LOADING_START';
|
||||||
|
export const GET_TRACE_LOADING_END = 'GET_TRACE_LOADING_END';
|
||||||
|
|
||||||
|
export const UPDATE_TRACE_SELECTED_SERVICE = 'UPDATE_TRACE_SELECTED_SERVICE';
|
||||||
|
export const UPDATE_TRACE_SELECTED_OPERATION =
|
||||||
|
'UPDATE_TRACE_SELECTED_OPERATION';
|
||||||
|
export const UPDATE_TRACE_SELECTED_LATENCY_VALUE =
|
||||||
|
'UPDATE_TRACE_SELECTED_LATENCY_VALUE';
|
||||||
|
export const UPDATE_TRACE_SELECTED_KIND = 'UPDATE_TRACE_SELECTED_KIND';
|
||||||
|
export const UPDATE_TRACE_SELECTED_TAGS = 'UPDATE_TRACE_SELECTED_TAGS';
|
||||||
|
|
||||||
|
export const UPDATE_SELECTED_AGG_OPTION = 'UPDATE_SELECTED_AGG_OPTION';
|
||||||
|
export const UPDATE_SELECTED_ENTITY = 'UPDATE_SELECTED_ENTITY';
|
||||||
|
export const UPDATE_SPANS_LOADING = 'UPDATE_SPANS_LOADING';
|
||||||
|
|
||||||
|
export const UPDATE_SELECTED_TRACE_DATA = 'UPDATE_SELECTED_TRACE_DATA';
|
||||||
|
export const UPDATE_AGGREGATES = 'UPDATE_AGGREGATES';
|
||||||
|
|
||||||
|
export const RESET_TRACE_DATA = 'RESET_TRACE_DATA';
|
||||||
|
|
||||||
|
import { TraceReducer } from 'types/reducer/trace';
|
||||||
|
|
||||||
|
interface GetTraceLoading {
|
||||||
|
type: typeof GET_TRACE_LOADING_START | typeof GET_TRACE_LOADING_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSpansLoading {
|
||||||
|
type: typeof UPDATE_SPANS_LOADING;
|
||||||
|
payload: {
|
||||||
|
loading: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetTraceInitialData {
|
||||||
|
type: typeof GET_TRACE_INITIAL_DATA_SUCCESS;
|
||||||
|
payload: {
|
||||||
|
serviceList: TraceReducer['serviceList'];
|
||||||
|
selectedTags: TraceReducer['selectedTags'];
|
||||||
|
operationList: TraceReducer['operationsList'];
|
||||||
|
tagsSuggestions: TraceReducer['tagsSuggestions'];
|
||||||
|
spansList: TraceReducer['spanList'];
|
||||||
|
selectedService: TraceReducer['selectedService'];
|
||||||
|
selectedOperation: TraceReducer['selectedOperation'];
|
||||||
|
selectedLatency: TraceReducer['selectedLatency'];
|
||||||
|
selectedKind: TraceReducer['selectedKind'];
|
||||||
|
spansAggregate: TraceReducer['spansAggregate'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSelectedDate {
|
||||||
|
type: typeof UPDATE_SELECTED_TRACE_DATA;
|
||||||
|
payload: {
|
||||||
|
operationList: TraceReducer['operationsList'];
|
||||||
|
tagsSuggestions: TraceReducer['tagsSuggestions'];
|
||||||
|
spansList: TraceReducer['spanList'];
|
||||||
|
selectedKind: TraceReducer['selectedKind'];
|
||||||
|
selectedService: TraceReducer['selectedService'];
|
||||||
|
selectedLatency: TraceReducer['selectedLatency'];
|
||||||
|
selectedOperation: TraceReducer['selectedOperation'];
|
||||||
|
spansAggregate: TraceReducer['spansAggregate'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetTraceInitialDataError {
|
||||||
|
type: typeof GET_TRACE_INITIAL_DATA_ERROR;
|
||||||
|
payload: {
|
||||||
|
errorMessage: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateTraceSelectedService {
|
||||||
|
type: typeof UPDATE_TRACE_SELECTED_SERVICE;
|
||||||
|
payload: {
|
||||||
|
selectedService: TraceReducer['selectedService'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateTraceSelectedOperation {
|
||||||
|
type: typeof UPDATE_TRACE_SELECTED_OPERATION;
|
||||||
|
payload: {
|
||||||
|
selectedOperation: TraceReducer['selectedOperation'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateTraceSelectedKind {
|
||||||
|
type: typeof UPDATE_TRACE_SELECTED_KIND;
|
||||||
|
payload: {
|
||||||
|
selectedKind: TraceReducer['selectedKind'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateTraceSelectedLatencyValue {
|
||||||
|
type: typeof UPDATE_TRACE_SELECTED_LATENCY_VALUE;
|
||||||
|
payload: {
|
||||||
|
selectedLatency: TraceReducer['selectedLatency'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateTraceSelectedTags {
|
||||||
|
type: typeof UPDATE_TRACE_SELECTED_TAGS;
|
||||||
|
payload: {
|
||||||
|
selectedTags: TraceReducer['selectedTags'];
|
||||||
|
spansList: TraceReducer['spanList'];
|
||||||
|
spansAggregate: TraceReducer['spansAggregate'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSelectedAggOption {
|
||||||
|
type: typeof UPDATE_SELECTED_AGG_OPTION;
|
||||||
|
payload: {
|
||||||
|
selectedAggOption: TraceReducer['selectedAggOption'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSelectedEntity {
|
||||||
|
type: typeof UPDATE_SELECTED_ENTITY;
|
||||||
|
payload: {
|
||||||
|
selectedEntity: TraceReducer['selectedEntity'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateAggregates {
|
||||||
|
type: typeof UPDATE_AGGREGATES;
|
||||||
|
payload: {
|
||||||
|
spansAggregate: TraceReducer['spansAggregate'];
|
||||||
|
selectedEntity: TraceReducer['selectedEntity'];
|
||||||
|
selectedAggOption: TraceReducer['selectedAggOption'];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResetTraceData {
|
||||||
|
type: typeof RESET_TRACE_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TraceActions =
|
||||||
|
| GetTraceLoading
|
||||||
|
| GetTraceInitialData
|
||||||
|
| GetTraceInitialDataError
|
||||||
|
| UpdateTraceSelectedService
|
||||||
|
| UpdateTraceSelectedLatencyValue
|
||||||
|
| UpdateTraceSelectedKind
|
||||||
|
| UpdateTraceSelectedOperation
|
||||||
|
| UpdateTraceSelectedTags
|
||||||
|
| UpdateSelectedDate
|
||||||
|
| UpdateSelectedAggOption
|
||||||
|
| UpdateSelectedEntity
|
||||||
|
| UpdateSpansLoading
|
||||||
|
| ResetTraceData
|
||||||
|
| UpdateAggregates;
|
1
frontend/src/types/api/trace/getServiceList.ts
Normal file
1
frontend/src/types/api/trace/getServiceList.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type PayloadProps = string[];
|
5
frontend/src/types/api/trace/getServiceOperation.ts
Normal file
5
frontend/src/types/api/trace/getServiceOperation.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type PayloadProps = string[];
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
service: string;
|
||||||
|
}
|
20
frontend/src/types/api/trace/getSpanAggregate.ts
Normal file
20
frontend/src/types/api/trace/getSpanAggregate.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export interface Props {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
service: string;
|
||||||
|
operation: string;
|
||||||
|
maxDuration: string;
|
||||||
|
minDuration: string;
|
||||||
|
kind: string;
|
||||||
|
tags: string;
|
||||||
|
dimension: string;
|
||||||
|
aggregation_option: string;
|
||||||
|
step: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Timestamp {
|
||||||
|
timestamp: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = Timestamp[];
|
51
frontend/src/types/api/trace/getSpans.ts
Normal file
51
frontend/src/types/api/trace/getSpans.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
|
|
||||||
|
export interface TraceTagItem {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface pushDStree {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
time: number;
|
||||||
|
startTime: number;
|
||||||
|
tags: TraceTagItem[];
|
||||||
|
children: pushDStree[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type span = [
|
||||||
|
number,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
string,
|
||||||
|
string | string[],
|
||||||
|
string | string[],
|
||||||
|
string | string[],
|
||||||
|
pushDStree[],
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface SpanList {
|
||||||
|
events: span[];
|
||||||
|
segmentID: string;
|
||||||
|
columns: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = SpanList[];
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
start: GlobalTime['minTime'];
|
||||||
|
end: GlobalTime['maxTime'];
|
||||||
|
lookback: string;
|
||||||
|
service: string;
|
||||||
|
operation: string;
|
||||||
|
maxDuration: string;
|
||||||
|
minDuration: string;
|
||||||
|
kind: string;
|
||||||
|
limit: string;
|
||||||
|
tags: string;
|
||||||
|
}
|
10
frontend/src/types/api/trace/getTags.ts
Normal file
10
frontend/src/types/api/trace/getTags.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Props as Prop } from './getServiceOperation';
|
||||||
|
|
||||||
|
interface TagKeys {
|
||||||
|
tagCount: number;
|
||||||
|
tagKeys: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = TagKeys[];
|
||||||
|
|
||||||
|
export type Props = Prop;
|
@ -4,6 +4,8 @@ export type Success = 200;
|
|||||||
|
|
||||||
export type Forbidden = 403;
|
export type Forbidden = 403;
|
||||||
|
|
||||||
|
export type BadRequest = 400;
|
||||||
|
|
||||||
export type Unauthorized = 401;
|
export type Unauthorized = 401;
|
||||||
|
|
||||||
export type NotFound = 404;
|
export type NotFound = 404;
|
||||||
@ -17,6 +19,7 @@ export type ErrorStatusCode =
|
|||||||
| Forbidden
|
| Forbidden
|
||||||
| Unauthorized
|
| Unauthorized
|
||||||
| NotFound
|
| NotFound
|
||||||
| ServerError;
|
| ServerError
|
||||||
|
| BadRequest;
|
||||||
|
|
||||||
export type StatusCode = SuccessStatusCode | ErrorStatusCode;
|
export type StatusCode = SuccessStatusCode | ErrorStatusCode;
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import { Time } from 'container/Header/DateTimeSelection/config';
|
||||||
import { GlobalTime } from 'types/actions/globalTime';
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
|
|
||||||
export interface GlobalReducer {
|
export interface GlobalReducer {
|
||||||
maxTime: GlobalTime['maxTime'];
|
maxTime: GlobalTime['maxTime'];
|
||||||
minTime: GlobalTime['minTime'];
|
minTime: GlobalTime['minTime'];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
selectedTime: Time;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import { TopEndPoints } from 'types/api/metrics/getTopEndPoints';
|
|||||||
interface MetricReducer {
|
interface MetricReducer {
|
||||||
services: ServicesList[];
|
services: ServicesList[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
metricsApplicationLoading: boolean;
|
||||||
error: boolean;
|
error: boolean;
|
||||||
errorMessage: string;
|
errorMessage: string;
|
||||||
dbOverView: DBOverView[];
|
dbOverView: DBOverView[];
|
||||||
|
37
frontend/src/types/reducer/trace.ts
Normal file
37
frontend/src/types/reducer/trace.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { PayloadProps as ServicePayload } from 'types/api/trace/getServiceList';
|
||||||
|
import { PayloadProps as OperationsPayload } from 'types/api/trace/getServiceOperation';
|
||||||
|
import { PayloadProps as GetSpansAggregatePayload } from 'types/api/trace/getSpanAggregate';
|
||||||
|
import { PayloadProps as GetSpansPayloadProps } from 'types/api/trace/getSpans';
|
||||||
|
import { PayloadProps as TagsPayload } from 'types/api/trace/getTags';
|
||||||
|
|
||||||
|
type TagItemOperator = 'equals' | 'contains' | 'regex';
|
||||||
|
export interface TagItem {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
operator: TagItemOperator;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LatencyValue {
|
||||||
|
min: string;
|
||||||
|
max: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TraceReducer {
|
||||||
|
selectedService: string;
|
||||||
|
selectedLatency: LatencyValue;
|
||||||
|
selectedOperation: string;
|
||||||
|
selectedKind: '' | '2' | '3' | string;
|
||||||
|
selectedTags: TagItem[];
|
||||||
|
tagsSuggestions: TagsPayload;
|
||||||
|
errorMessage: string;
|
||||||
|
serviceList: ServicePayload;
|
||||||
|
spanList: GetSpansPayloadProps;
|
||||||
|
operationsList: OperationsPayload;
|
||||||
|
error: boolean;
|
||||||
|
loading: boolean;
|
||||||
|
|
||||||
|
selectedAggOption: string;
|
||||||
|
selectedEntity: string;
|
||||||
|
spansAggregate: GetSpansAggregatePayload;
|
||||||
|
spansLoading: boolean;
|
||||||
|
}
|
2
frontend/src/typings/react-graph-vis.d.ts
vendored
2
frontend/src/typings/react-graph-vis.d.ts
vendored
@ -6,7 +6,7 @@ declare module 'react-graph-vis' {
|
|||||||
export { Network, NetworkEvents, Options, Node, Edge, DataSet } from 'vis';
|
export { Network, NetworkEvents, Options, Node, Edge, DataSet } from 'vis';
|
||||||
|
|
||||||
export interface graphEvents {
|
export interface graphEvents {
|
||||||
[event: NetworkEvents]: (params?: any) => void;
|
[event: NetworkEvents]: (params) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Doesn't appear that this module supports passing in a vis.DataSet directly. Once it does graph can just use the Data object from vis.
|
//Doesn't appear that this module supports passing in a vis.DataSet directly. Once it does graph can just use the Data object from vis.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
||||||
|
|
||||||
export const isOnboardingSkipped = () => {
|
export const isOnboardingSkipped = (): boolean => {
|
||||||
return localStorage.getItem(SKIP_ONBOARDING) === 'true';
|
return localStorage.getItem(SKIP_ONBOARDING) === 'true';
|
||||||
};
|
};
|
||||||
|
15
frontend/src/wdyr.ts
Normal file
15
frontend/src/wdyr.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/// <reference types="@welldone-software/why-did-you-render" />
|
||||||
|
// ^ https://github.com/welldone-software/why-did-you-render/issues/161
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
const whyDidYouRender = require('@welldone-software/why-did-you-render');
|
||||||
|
whyDidYouRender(React, {
|
||||||
|
trackAllPureComponents: false,
|
||||||
|
trackExtraHooks: [[require('react-redux/lib'), 'useSelector']],
|
||||||
|
include: [/^ConnectFunction/],
|
||||||
|
logOnDifferentValues: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default '';
|
@ -4,8 +4,7 @@ import CopyPlugin from 'copy-webpack-plugin';
|
|||||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
|
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
|
||||||
import webpack from 'webpack';
|
import webpack, { WebpackPluginInstance } from 'webpack';
|
||||||
import { WebpackPluginInstance } from 'webpack-dev-middleware/node_modules/webpack';
|
|
||||||
|
|
||||||
const __dirname = resolve();
|
const __dirname = resolve();
|
||||||
|
|
||||||
@ -14,7 +13,7 @@ const config: webpack.Configuration = {
|
|||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
entry: resolve(__dirname, './src/index.tsx'),
|
entry: resolve(__dirname, './src/index.tsx'),
|
||||||
output: {
|
output: {
|
||||||
filename: ({ chunk }: any): string => {
|
filename: ({ chunk }): string => {
|
||||||
const hash = chunk?.hash;
|
const hash = chunk?.hash;
|
||||||
const name = chunk?.name;
|
const name = chunk?.name;
|
||||||
return `js/${name}-${hash}.js`;
|
return `js/${name}-${hash}.js`;
|
||||||
@ -53,12 +52,12 @@ const config: webpack.Configuration = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlWebpackPlugin({ template: 'src/index.html.ejs' }),
|
new HtmlWebpackPlugin({ template: 'src/index.html.ejs' }),
|
||||||
new CompressionPlugin({
|
(new CompressionPlugin({
|
||||||
exclude: /.map$/,
|
exclude: /.map$/,
|
||||||
}) as any,
|
}) as unknown) as WebpackPluginInstance,
|
||||||
new CopyPlugin({
|
(new CopyPlugin({
|
||||||
patterns: [{ from: resolve(__dirname, 'public/'), to: '.' }],
|
patterns: [{ from: resolve(__dirname, 'public/'), to: '.' }],
|
||||||
}) as any,
|
}) as unknown) as WebpackPluginInstance,
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
process: 'process/browser',
|
process: 'process/browser',
|
||||||
}),
|
}),
|
||||||
|
@ -2,13 +2,12 @@
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
//@ts-ignore
|
||||||
|
import portFinderSync from 'portfinder-sync';
|
||||||
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
|
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
|
||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server';
|
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server';
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import portFinderSync from 'portfinder-sync';
|
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
const __dirname = resolve();
|
const __dirname = resolve();
|
||||||
@ -29,10 +28,10 @@ const config: Configuration = {
|
|||||||
liveReload: true,
|
liveReload: true,
|
||||||
port: portFinderSync.getPort(3000),
|
port: portFinderSync.getPort(3000),
|
||||||
static: {
|
static: {
|
||||||
directory: resolve(__dirname, "public"),
|
directory: resolve(__dirname, 'public'),
|
||||||
publicPath: "/",
|
publicPath: '/',
|
||||||
watch: true,
|
watch: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
target: 'web',
|
target: 'web',
|
||||||
output: {
|
output: {
|
||||||
|
@ -3118,6 +3118,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.5.1.tgz#b5fde2f0f79c1e120307c415a4c1d5eb15a6f278"
|
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.5.1.tgz#b5fde2f0f79c1e120307c415a4c1d5eb15a6f278"
|
||||||
integrity sha512-4vSVUiOPJLmr45S8rMGy7WDvpWxfFxfP/Qx/cxZFCfvoypTYpPPL1X8VIZMe0WTA+Jr7blUxwUSEZNkjoMTgSw==
|
integrity sha512-4vSVUiOPJLmr45S8rMGy7WDvpWxfFxfP/Qx/cxZFCfvoypTYpPPL1X8VIZMe0WTA+Jr7blUxwUSEZNkjoMTgSw==
|
||||||
|
|
||||||
|
"@welldone-software/why-did-you-render@^6.2.1":
|
||||||
|
version "6.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-6.2.1.tgz#6a87926cc8386b748dc07341cf495caa5be1db28"
|
||||||
|
integrity sha512-eIVKeK6ueS3tuzCqMVTaaNrPYvb9cA8NHiNgLA7Op8SD4TiT31zqNjxmhzLEK+y3sBxcwr6YhsiQGX9EThrvaw==
|
||||||
|
dependencies:
|
||||||
|
lodash "^4"
|
||||||
|
|
||||||
"@xtuc/ieee754@^1.2.0":
|
"@xtuc/ieee754@^1.2.0":
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
|
||||||
@ -5373,10 +5380,10 @@ custom-event-polyfill@^1.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
|
resolved "https://registry.yarnpkg.com/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz#9bc993ddda937c1a30ccd335614c6c58c4f87aee"
|
||||||
integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
|
integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
|
||||||
|
|
||||||
cypress@8.6.0:
|
cypress@^8.3.0:
|
||||||
version "8.6.0"
|
version "8.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.6.0.tgz#8d02fa58878b37cfc45bbfce393aa974fa8a8e22"
|
resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.7.0.tgz#2ee371f383d8f233d3425b6cc26ddeec2668b6da"
|
||||||
integrity sha512-F7qEK/6Go5FsqTueR+0wEw2vOVKNgk5847Mys8vsWkzPoEKdxs+7N9Y1dit+zhaZCLtMPyrMwjfA53ZFy+lSww==
|
integrity sha512-b1bMC3VQydC6sXzBMFnSqcvwc9dTZMgcaOzT0vpSD+Gq1yFc+72JDWi55sfUK5eIeNLAtWOGy1NNb6UlhMvB+Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cypress/request" "^2.88.6"
|
"@cypress/request" "^2.88.6"
|
||||||
"@cypress/xvfb" "^1.2.4"
|
"@cypress/xvfb" "^1.2.4"
|
||||||
@ -9929,7 +9936,7 @@ lodash.uniq@^4.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||||
|
|
||||||
"lodash@>=3.5 <5", lodash@>=4.17.21, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
|
"lodash@>=3.5 <5", lodash@>=4.17.21, lodash@^4, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
|
||||||
version "4.17.21"
|
version "4.17.21"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
@ -14658,7 +14665,7 @@ uuid@^2.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
||||||
integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=
|
integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=
|
||||||
|
|
||||||
uuid@^3.3.2, uuid@^3.4.0:
|
uuid@^3.4.0:
|
||||||
version "3.4.0"
|
version "3.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user