mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 04:39:59 +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",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||
"@typescript-eslint/parser": "^4.28.2",
|
||||
"@welldone-software/why-did-you-render": "^6.2.1",
|
||||
"autoprefixer": "^9.0.0",
|
||||
"babel-plugin-styled-components": "^1.12.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(
|
||||
() =>
|
||||
import(
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
SettingsPage,
|
||||
SignupPage,
|
||||
TraceDetailPage,
|
||||
TraceDetailPages,
|
||||
TraceGraphPage,
|
||||
UsageExplorerPage,
|
||||
} from './pageComponents';
|
||||
@ -77,6 +78,11 @@ const routes: AppRoutes[] = [
|
||||
exact: true,
|
||||
component: DashboardWidget,
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE,
|
||||
exact: true,
|
||||
component: TraceDetailPages,
|
||||
},
|
||||
];
|
||||
|
||||
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',
|
||||
operation = 'operation',
|
||||
kind = 'kind',
|
||||
latencyMax = 'latencyMax',
|
||||
latencyMin = 'latencyMin',
|
||||
selectedTags = 'selectedTags',
|
||||
aggregationOption = 'aggregationOption',
|
||||
entity = 'entity',
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ const ROUTES = {
|
||||
SERVICE_METRICS: '/application/:servicename',
|
||||
SERVICE_MAP: '/service-map',
|
||||
TRACES: '/traces',
|
||||
TRACE: '/trace',
|
||||
TRACE_GRAPH: '/traces/:id',
|
||||
SETTINGS: '/settings',
|
||||
INSTRUMENTATION: '/add-instrumentation',
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
import getChartData from 'lib/getChartData';
|
||||
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
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 { AppState } from 'store/reducers';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
@ -219,15 +219,4 @@ interface FullViewProps {
|
||||
noDataGraph?: boolean;
|
||||
}
|
||||
|
||||
export default memo(FullView, (prev, next) => {
|
||||
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;
|
||||
});
|
||||
export default FullView;
|
||||
|
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 { getDefaultOption, getOptions, Time } from './config';
|
||||
import {
|
||||
Container,
|
||||
Form,
|
||||
FormItem,
|
||||
RefreshTextContainer,
|
||||
Typography,
|
||||
} from './styles';
|
||||
import { Container, Form, FormItem } from './styles';
|
||||
const { Option } = DefaultSelect;
|
||||
import get from 'api/browser/localstorage/get';
|
||||
import set from 'api/browser/localstorage/set';
|
||||
@ -19,31 +13,72 @@ import { connect, useSelector } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { GlobalTimeLoading, UpdateTimeInterval } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal';
|
||||
import RefreshText from './Refresh';
|
||||
|
||||
const DateTimeSelection = ({
|
||||
location,
|
||||
updateTimeInterval,
|
||||
globalTimeLoading,
|
||||
}: Props): JSX.Element => {
|
||||
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 [endTime, setEndTime] = useState<moment.Moment>();
|
||||
|
||||
const [options, setOptions] = useState(getOptions(location.pathname));
|
||||
const [refreshButtonHidden, setRefreshButtonHidden] = useState<boolean>(false);
|
||||
const [refreshText, setRefreshText] = useState<string>('');
|
||||
const [customDateTimeVisible, setCustomDTPickerVisible] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
const isOnSelectHandler = useRef<boolean>(false);
|
||||
|
||||
const { maxTime, loading, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { maxTime, minTime, selectedTime, loading } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const getDefaultTime = (pathName: string): Time => {
|
||||
const defaultSelectedOption = getDefaultOption(pathName);
|
||||
@ -81,8 +116,6 @@ const DateTimeSelection = ({
|
||||
};
|
||||
|
||||
const onSelectHandler = (value: Time): void => {
|
||||
isOnSelectHandler.current = true;
|
||||
|
||||
if (value !== 'custom') {
|
||||
updateTimeInterval(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
|
||||
useEffect(() => {
|
||||
const metricsTimeDuration = get(LOCAL_STORAGE.METRICS_TIME_IN_DURATION);
|
||||
@ -187,85 +209,44 @@ const DateTimeSelection = ({
|
||||
set(LOCAL_STORAGE.METRICS_TIME_IN_DURATION, JSON.stringify({}));
|
||||
}
|
||||
|
||||
if (isOnSelectHandler.current === false) {
|
||||
const currentRoute = location.pathname;
|
||||
const params = new URLSearchParams(location.search);
|
||||
const time = getDefaultTime(currentRoute);
|
||||
const currentRoute = location.pathname;
|
||||
const time = getDefaultTime(currentRoute);
|
||||
|
||||
const currentOptions = getOptions(currentRoute);
|
||||
setOptions(currentOptions);
|
||||
const currentOptions = getOptions(currentRoute);
|
||||
setOptions(currentOptions);
|
||||
|
||||
const searchStartTime = params.get('startTime');
|
||||
const searchEndTime = params.get('endTime');
|
||||
|
||||
const localstorageStartTime = get('startTime');
|
||||
const localstorageEndTime = get('endTime');
|
||||
|
||||
const getUpdatedTime = (time: Time): Time => {
|
||||
if (searchEndTime !== null && searchStartTime !== null) {
|
||||
return 'custom';
|
||||
}
|
||||
|
||||
if (
|
||||
(localstorageEndTime === null || localstorageStartTime === null) &&
|
||||
time === 'custom'
|
||||
) {
|
||||
return getDefaultOption(location.pathname);
|
||||
}
|
||||
|
||||
return time;
|
||||
};
|
||||
|
||||
const updatedTime = getUpdatedTime(time);
|
||||
|
||||
setSelectedTimeInterval(updatedTime);
|
||||
|
||||
const getTime = (): [number, number] | undefined => {
|
||||
if (searchEndTime && searchStartTime) {
|
||||
const startMoment = moment(
|
||||
new Date(parseInt(getTimeString(searchStartTime), 10)),
|
||||
);
|
||||
const endMoment = moment(
|
||||
new Date(parseInt(getTimeString(searchEndTime), 10)),
|
||||
);
|
||||
|
||||
setStartTime(startMoment);
|
||||
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());
|
||||
const getCustomOrIntervalTime = (time: Time): Time => {
|
||||
if (searchEndTime !== null && searchStartTime !== null) {
|
||||
return 'custom';
|
||||
}
|
||||
} else {
|
||||
isOnSelectHandler.current = false;
|
||||
}
|
||||
|
||||
if (
|
||||
(localstorageEndTime === null || localstorageStartTime === null) &&
|
||||
time === 'custom'
|
||||
) {
|
||||
return getDefaultOption(currentRoute);
|
||||
}
|
||||
|
||||
return time;
|
||||
};
|
||||
|
||||
const updatedTime = getCustomOrIntervalTime(time);
|
||||
|
||||
const [preStartTime = 0, preEndTime = 0] = getTime() || [];
|
||||
|
||||
setStartTime(moment(preStartTime));
|
||||
setEndTime(moment(preEndTime));
|
||||
|
||||
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
||||
}, [
|
||||
location.pathname,
|
||||
location.search,
|
||||
startTime,
|
||||
endTime,
|
||||
getTime,
|
||||
localstorageEndTime,
|
||||
localstorageStartTime,
|
||||
searchEndTime,
|
||||
searchStartTime,
|
||||
updateTimeInterval,
|
||||
selectedTimeInterval,
|
||||
loading,
|
||||
globalTimeLoading,
|
||||
]);
|
||||
|
||||
return (
|
||||
@ -273,11 +254,11 @@ const DateTimeSelection = ({
|
||||
<Form
|
||||
form={form_dtselector}
|
||||
layout="inline"
|
||||
initialValues={{ interval: selectedTimeInterval }}
|
||||
initialValues={{ interval: selectedTime }}
|
||||
>
|
||||
<DefaultSelect
|
||||
onSelect={(value): void => onSelectHandler(value as Time)}
|
||||
value={getInputLabel(startTime, endTime, selectedTimeInterval)}
|
||||
value={getInputLabel(startTime, endTime, selectedTime)}
|
||||
data-testid="dropDown"
|
||||
>
|
||||
{options.map(({ value, label }) => (
|
||||
@ -294,9 +275,11 @@ const DateTimeSelection = ({
|
||||
</FormItem>
|
||||
</Form>
|
||||
|
||||
<RefreshTextContainer>
|
||||
<Typography>{refreshText}</Typography>
|
||||
</RefreshTextContainer>
|
||||
<RefreshText
|
||||
{...{
|
||||
onLastRefreshHandler,
|
||||
}}
|
||||
/>
|
||||
|
||||
<CustomDateTimeModal
|
||||
visible={customDateTimeVisible}
|
||||
@ -314,16 +297,21 @@ interface DispatchProps {
|
||||
interval: Time,
|
||||
dateTimeRange?: [number, number],
|
||||
) => (dispatch: Dispatch<AppActions>) => void;
|
||||
// globalTimeLoading: () => void;
|
||||
globalTimeLoading: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
updateTimeInterval: bindActionCreators(UpdateTimeInterval, dispatch),
|
||||
// globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||
});
|
||||
|
||||
type Props = DispatchProps & RouteComponentProps;
|
||||
|
||||
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 history from 'lib/history';
|
||||
import React, { useRef } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
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 AppActions from 'types/actions';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
@ -20,10 +16,7 @@ import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
|
||||
import TopEndpointsTable from '../TopEndpointsTable';
|
||||
import { Button } from './styles';
|
||||
|
||||
const Application = ({
|
||||
globalLoading,
|
||||
getWidget,
|
||||
}: DashboardProps): JSX.Element => {
|
||||
const Application = ({ getWidget }: DashboardProps): JSX.Element => {
|
||||
const { servicename } = useParams<{ servicename?: string }>();
|
||||
const selectedTimeStamp = useRef(0);
|
||||
|
||||
@ -42,8 +35,7 @@ const Application = ({
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
||||
}
|
||||
|
||||
globalLoading();
|
||||
history.push(`${ROUTES.TRACES}?${urlParams.toString()}`);
|
||||
history.push(`${ROUTES.TRACE}?${urlParams.toString()}`);
|
||||
};
|
||||
|
||||
const onClickhandler = async (
|
||||
@ -74,7 +66,7 @@ const Application = ({
|
||||
buttonElement.style.display = 'block';
|
||||
buttonElement.style.left = `${firstPoint.element.x}px`;
|
||||
buttonElement.style.top = `${firstPoint.element.y}px`;
|
||||
selectedTimeStamp.current = new Date(time).getTime();
|
||||
selectedTimeStamp.current = time.getTime();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -97,8 +89,7 @@ const Application = ({
|
||||
}
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.error, 'true');
|
||||
|
||||
globalLoading();
|
||||
history.push(`${ROUTES.TRACES}?${urlParams.toString()}`);
|
||||
history.push(`${ROUTES.TRACE}?${urlParams.toString()}`);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -238,18 +229,8 @@ const Application = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
globalLoading: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
globalLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||
});
|
||||
|
||||
interface DashboardProps extends DispatchProps {
|
||||
interface DashboardProps {
|
||||
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 { ColumnsType } from 'antd/lib/table';
|
||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import React from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
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 { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
const TopEndpointsTable = (props: TopEndpointsTableProps): JSX.Element => {
|
||||
@ -25,19 +22,18 @@ const TopEndpointsTable = (props: TopEndpointsTableProps): JSX.Element => {
|
||||
const { servicename } = params;
|
||||
urlParams.set(
|
||||
METRICS_PAGE_QUERY_PARAM.startTime,
|
||||
String(Number(minTime) / 1000000),
|
||||
(minTime / 1000000).toString(),
|
||||
);
|
||||
urlParams.set(
|
||||
METRICS_PAGE_QUERY_PARAM.endTime,
|
||||
String(Number(maxTime) / 1000000),
|
||||
(maxTime / 1000000).toString(),
|
||||
);
|
||||
if (servicename) {
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
||||
}
|
||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.operation, operation);
|
||||
|
||||
props.globalTimeLoading();
|
||||
history.push(`/traces?${urlParams.toString()}`);
|
||||
history.push(`${ROUTES.TRACE}?${urlParams.toString()}`);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<DataProps> = [
|
||||
@ -105,18 +101,8 @@ const TopEndpointsTable = (props: TopEndpointsTableProps): JSX.Element => {
|
||||
|
||||
type DataProps = topEndpointListItem;
|
||||
|
||||
interface DispatchProps {
|
||||
globalTimeLoading: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||
});
|
||||
|
||||
interface TopEndpointsTableProps extends DispatchProps {
|
||||
interface TopEndpointsTableProps {
|
||||
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 history from 'lib/history';
|
||||
import React, { useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GlobalTimeLoading, servicesListItem } from 'store/actions';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { servicesListItem } from 'store/actions/MetricsActions/metricsInterfaces';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
import SkipBoardModal from './SkipOnBoardModal';
|
||||
import { Container, Name } from './styles';
|
||||
|
||||
const Metrics = ({ globalTimeLoading }: MetricsProps): JSX.Element => {
|
||||
const Metrics = (): JSX.Element => {
|
||||
const [skipOnboarding, setSkipOnboarding] = useState(
|
||||
localStorage.getItem(SKIP_ONBOARDING) === 'true',
|
||||
);
|
||||
@ -30,7 +27,6 @@ const Metrics = ({ globalTimeLoading }: MetricsProps): JSX.Element => {
|
||||
|
||||
const onClickHandler = (to: string): void => {
|
||||
history.push(to);
|
||||
globalTimeLoading();
|
||||
};
|
||||
|
||||
if (
|
||||
@ -90,16 +86,4 @@ const Metrics = ({ globalTimeLoading }: MetricsProps): JSX.Element => {
|
||||
|
||||
type DataProps = servicesListItem;
|
||||
|
||||
interface DispatchProps {
|
||||
globalTimeLoading: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||
});
|
||||
|
||||
type MetricsProps = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Metrics);
|
||||
export default Metrics;
|
||||
|
@ -7,7 +7,7 @@ import { NavLink } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GlobalTimeLoading, ToggleDarkMode } from 'store/actions';
|
||||
import { ToggleDarkMode } from 'store/actions';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
@ -15,7 +15,7 @@ import AppReducer from 'types/reducer/app';
|
||||
import menus from './menuItems';
|
||||
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 { pathname } = useLocation();
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
@ -49,10 +49,9 @@ const SideNav = ({ toggleDarkMode, globalTimeLoading }: Props): JSX.Element => {
|
||||
(to: string) => {
|
||||
if (pathname !== to) {
|
||||
history.push(to);
|
||||
globalTimeLoading();
|
||||
}
|
||||
},
|
||||
[pathname, globalTimeLoading],
|
||||
[pathname],
|
||||
);
|
||||
|
||||
return (
|
||||
@ -86,14 +85,12 @@ type mode = 'darkMode' | 'lightMode';
|
||||
|
||||
interface DispatchProps {
|
||||
toggleDarkMode: () => void;
|
||||
globalTimeLoading: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
toggleDarkMode: bindActionCreators(ToggleDarkMode, dispatch),
|
||||
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||
});
|
||||
|
||||
type Props = DispatchProps;
|
||||
|
@ -17,7 +17,7 @@ const menus: SidebarMenu[] = [
|
||||
},
|
||||
{
|
||||
Icon: AlignLeftOutlined,
|
||||
to: ROUTES.TRACES,
|
||||
to: ROUTES.TRACE,
|
||||
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 AppRoutes from 'AppRoutes';
|
||||
|
@ -2,6 +2,7 @@ const convertDateToAmAndPm = (date: Date): string => {
|
||||
return date.toLocaleString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
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 { servicesItem } from "Src/store/actions";
|
||||
import { servicesItem } from "store/actions";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Select } from "antd";
|
||||
import styled from "styled-components";
|
||||
|
@ -69,7 +69,6 @@ const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
|
||||
initialValues={latencyFilterValues}
|
||||
>
|
||||
<Row>
|
||||
{/* <Input.Group compact> */}
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="min"
|
||||
|
@ -247,7 +247,6 @@ const _TraceCustomVisualizations = (
|
||||
},
|
||||
],
|
||||
}}
|
||||
xAxisType="timeseries"
|
||||
/>
|
||||
</CustomGraphContainer>
|
||||
</Card>
|
||||
|
@ -1,21 +1,10 @@
|
||||
import React, { useEffect } 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 React from 'react';
|
||||
|
||||
import { TraceCustomVisualizations } from './TraceCustomVisualizations';
|
||||
import { TraceFilter } from './TraceFilter';
|
||||
import { TraceList } from './TraceList';
|
||||
|
||||
const TraceDetail = ({ globalTimeLoading }: Props): JSX.Element => {
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
globalTimeLoading();
|
||||
};
|
||||
}, [globalTimeLoading]);
|
||||
|
||||
const TraceDetail = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<TraceFilter />
|
||||
@ -25,16 +14,4 @@ const TraceDetail = ({ globalTimeLoading }: Props): JSX.Element => {
|
||||
);
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
globalTimeLoading: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||
});
|
||||
|
||||
type Props = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TraceDetail);
|
||||
export default TraceDetail;
|
||||
|
@ -120,15 +120,6 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
||||
|
||||
const handleApplyFilterForm = useCallback(
|
||||
(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({
|
||||
service: values.service,
|
||||
operation: values.operation,
|
||||
@ -272,10 +263,6 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
||||
'&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) {
|
||||
fetchTraces(globalTime, request_string);
|
||||
}
|
||||
@ -305,19 +292,10 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
||||
'ms';
|
||||
|
||||
form_basefilter.setFieldsValue({ latency: latencyButtonText });
|
||||
}, [traceFilters.latency, form_basefilter]);
|
||||
|
||||
useEffect(() => {
|
||||
form_basefilter.setFieldsValue({ service: traceFilters.service });
|
||||
}, [traceFilters.service, form_basefilter]);
|
||||
|
||||
useEffect(() => {
|
||||
form_basefilter.setFieldsValue({ operation: traceFilters.operation });
|
||||
}, [traceFilters.operation, form_basefilter]);
|
||||
|
||||
useEffect(() => {
|
||||
form_basefilter.setFieldsValue({ kind: traceFilters.kind });
|
||||
}, [traceFilters.kind, form_basefilter]);
|
||||
}, [traceFilters, form_basefilter]);
|
||||
|
||||
const onLatencyButtonClick = (): void => {
|
||||
setModalVisible(true);
|
||||
@ -438,8 +416,6 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
||||
|
||||
<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>
|
||||
|
||||
<Form
|
||||
|
@ -1,67 +1,62 @@
|
||||
import { Typography } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import MetricsApplicationContainer from 'container/MetricsApplication';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import {
|
||||
GetInitialData,
|
||||
GetInitialDataProps,
|
||||
} from 'store/actions/metrics/getInitialData';
|
||||
import { ResetInitialData } from 'store/actions/metrics/resetInitialData';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
const MetricsApplication = ({ getInitialData }: MetricsProps): JSX.Element => {
|
||||
const { loading, maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
const MetricsApplication = ({
|
||||
getInitialData,
|
||||
resetInitialData,
|
||||
}: MetricsProps): JSX.Element => {
|
||||
const { selectedTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { error, errorMessage } = useSelector<AppState, MetricReducer>(
|
||||
(state) => state.metrics,
|
||||
);
|
||||
const { error, errorMessage, metricsApplicationLoading } = useSelector<
|
||||
AppState,
|
||||
MetricReducer
|
||||
>((state) => state.metrics);
|
||||
|
||||
const { servicename } = useParams<ServiceProps>();
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
useEffect(() => {
|
||||
if (servicename !== undefined && loading == false) {
|
||||
if (servicename !== undefined) {
|
||||
getInitialData({
|
||||
end: maxTime,
|
||||
service: servicename,
|
||||
start: minTime,
|
||||
step: 60,
|
||||
selectedTimeInterval: selectedTime,
|
||||
serviceName: servicename,
|
||||
});
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
// setting the data to it's initial this will avoid the re-rendering the graph
|
||||
dispatch({
|
||||
type: 'GET_INTIAL_APPLICATION_DATA',
|
||||
payload: {
|
||||
serviceOverview: [],
|
||||
topEndPoints: [],
|
||||
},
|
||||
});
|
||||
return () => {
|
||||
resetInitialData();
|
||||
};
|
||||
}, [servicename, maxTime, minTime, getInitialData, loading, dispatch]);
|
||||
}, [servicename, getInitialData, selectedTime]);
|
||||
|
||||
if (metricsApplicationLoading) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Typography>{errorMessage}</Typography>;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
return <MetricsApplicationContainer />;
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
getInitialData: (props: GetInitialDataProps) => void;
|
||||
resetInitialData: () => void;
|
||||
}
|
||||
|
||||
interface ServiceProps {
|
||||
@ -72,6 +67,7 @@ const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
getInitialData: bindActionCreators(GetInitialData, dispatch),
|
||||
resetInitialData: bindActionCreators(ResetInitialData, dispatch),
|
||||
});
|
||||
|
||||
type MetricsProps = DispatchProps;
|
||||
|
@ -5,16 +5,17 @@ import React, { useEffect } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GetService, GetServiceProps } from 'store/actions';
|
||||
import { GetService, GetServiceProps } from 'store/actions/metrics';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import MetricReducer from 'types/reducer/metrics';
|
||||
|
||||
const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
||||
const { minTime, maxTime, loading } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const { minTime, maxTime, loading, selectedTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
const { services } = useSelector<AppState, MetricReducer>(
|
||||
(state) => state.metrics,
|
||||
);
|
||||
@ -24,11 +25,10 @@ const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
||||
useEffect(() => {
|
||||
if (loading === false) {
|
||||
getService({
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
selectedTimeInterval: selectedTime,
|
||||
});
|
||||
}
|
||||
}, [getService, maxTime, minTime, loading]);
|
||||
}, [getService, loading, selectedTime]);
|
||||
|
||||
useEffect(() => {
|
||||
let timeInterval: NodeJS.Timeout;
|
||||
@ -36,8 +36,7 @@ const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
||||
if (loading === false && !isSkipped && services.length === 0) {
|
||||
timeInterval = setInterval(() => {
|
||||
getService({
|
||||
start: minTime,
|
||||
end: maxTime,
|
||||
selectedTimeInterval: selectedTime,
|
||||
});
|
||||
}, 50000);
|
||||
}
|
||||
@ -45,7 +44,7 @@ const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
||||
return (): void => {
|
||||
clearInterval(timeInterval);
|
||||
};
|
||||
}, [getService, isSkipped, loading, maxTime, minTime, services]);
|
||||
}, [getService, isSkipped, loading, maxTime, minTime, services, selectedTime]);
|
||||
|
||||
if (loading) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
@ -55,10 +54,9 @@ const Metrics = ({ getService }: MetricsProps): JSX.Element => {
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
getService: ({
|
||||
end,
|
||||
start,
|
||||
}: GetServiceProps) => (dispatch: Dispatch<AppActions>) => void;
|
||||
getService: (
|
||||
props: GetServiceProps,
|
||||
) => (dispatch: Dispatch<AppActions>, getState: () => AppState) => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
|
@ -6,7 +6,7 @@ import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { GlobalTimeLoading, UserLoggedIn } from 'store/actions';
|
||||
import { UserLoggedIn } from 'store/actions';
|
||||
import AppActions from 'types/actions';
|
||||
|
||||
import {
|
||||
@ -17,7 +17,7 @@ import {
|
||||
Title,
|
||||
} from './styles';
|
||||
|
||||
const Signup = ({ globalLoading, loggedIn }: SignupProps): JSX.Element => {
|
||||
const Signup = ({ loggedIn }: SignupProps): JSX.Element => {
|
||||
const [state, setState] = useState({ submitted: false });
|
||||
const [formState, setFormState] = useState({
|
||||
firstName: { value: '' },
|
||||
@ -59,7 +59,6 @@ const Signup = ({ globalLoading, loggedIn }: SignupProps): JSX.Element => {
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
loggedIn();
|
||||
globalLoading();
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
// @TODO throw a error notification here
|
||||
@ -125,14 +124,12 @@ const Signup = ({ globalLoading, loggedIn }: SignupProps): JSX.Element => {
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
globalLoading: () => void;
|
||||
loggedIn: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
globalLoading: bindActionCreators(GlobalTimeLoading, 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 getMinAgo from 'lib/getStartAndEndTime/getMinAgo';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
|
||||
@ -8,45 +8,14 @@ export const UpdateTimeInterval = (
|
||||
dateTimeRange: [number, number] = [0, 0],
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
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];
|
||||
minTime = dateTimeRange[0];
|
||||
}
|
||||
const { maxTime, minTime } = GetMinMax(interval, dateTimeRange);
|
||||
|
||||
dispatch({
|
||||
type: 'UPDATE_TIME_INTERVAL',
|
||||
payload: {
|
||||
maxTime: maxTime * 1000000, // in nano sec,
|
||||
minTime: minTime * 1000000,
|
||||
maxTime: maxTime,
|
||||
minTime: minTime,
|
||||
selectedTime: interval,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -5,19 +5,38 @@
|
||||
import getServiceOverview from 'api/metrics/getServiceOverview';
|
||||
import getTopEndPoints from 'api/metrics/getTopEndPoints';
|
||||
import { AxiosError } from 'axios';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { Props } from 'types/api/metrics/getDBOverview';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
export const GetInitialData = (
|
||||
props: GetInitialDataProps,
|
||||
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
||||
): ((dispatch: Dispatch<AppActions>, getState: () => AppState) => void) => {
|
||||
return async (dispatch, getState): Promise<void> => {
|
||||
try {
|
||||
const { globalTime } = getState();
|
||||
|
||||
/**
|
||||
* @description This is because we keeping the store as source of truth
|
||||
*/
|
||||
if (props.selectedTimeInterval !== globalTime.selectedTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'GET_INITIAL_APPLICATION_LOADING',
|
||||
});
|
||||
|
||||
const { maxTime, minTime } = GetMinMax(props.selectedTimeInterval, [
|
||||
globalTime.minTime / 1000000,
|
||||
globalTime.maxTime / 1000000,
|
||||
]);
|
||||
|
||||
const step = 60;
|
||||
|
||||
const [
|
||||
// getDBOverViewResponse,
|
||||
// getExternalAverageDurationResponse,
|
||||
@ -39,10 +58,15 @@ export const GetInitialData = (
|
||||
// ...props,
|
||||
// }),
|
||||
getServiceOverview({
|
||||
...props,
|
||||
end: maxTime,
|
||||
service: props.serviceName,
|
||||
start: minTime,
|
||||
step,
|
||||
}),
|
||||
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 { AxiosError } from 'axios';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { Props } from 'types/api/metrics/getService';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
export const GetService = ({
|
||||
end,
|
||||
start,
|
||||
}: GetServiceProps): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
||||
export const GetService = (
|
||||
props: GetServiceProps,
|
||||
): ((dispatch: Dispatch<AppActions>, getState: () => AppState) => void) => {
|
||||
return async (dispatch, getState): Promise<void> => {
|
||||
try {
|
||||
const { globalTime } = getState();
|
||||
|
||||
if (props.selectedTimeInterval !== globalTime.selectedTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { maxTime, minTime } = GetMinMax(props.selectedTimeInterval, [
|
||||
globalTime.minTime / 1000000,
|
||||
globalTime.maxTime / 1000000,
|
||||
]);
|
||||
|
||||
dispatch({
|
||||
type: 'GET_SERVICE_LIST_LOADING_START',
|
||||
});
|
||||
|
||||
const response = await getService({ end, start });
|
||||
const response = await getService({
|
||||
end: maxTime,
|
||||
start: minTime,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
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 {
|
||||
GLOBAL_TIME_LOADING_START,
|
||||
GlobalTimeAction,
|
||||
@ -9,6 +10,7 @@ const intitalState: GlobalReducer = {
|
||||
maxTime: Date.now() * 1000000,
|
||||
minTime: (Date.now() - 15 * 60 * 1000) * 1000000,
|
||||
loading: true,
|
||||
selectedTime: getDefaultOption(location.pathname),
|
||||
};
|
||||
|
||||
const globalTimeReducer = (
|
||||
|
@ -6,6 +6,7 @@ import globalTimeReducer from './global';
|
||||
import metricsReducers from './metric';
|
||||
import { metricsReducer } from './metrics';
|
||||
import { ServiceMapReducer } from './serviceMap';
|
||||
import { traceReducer } from './trace';
|
||||
import TraceFilterReducer from './traceFilters';
|
||||
import { traceItemReducer, tracesReducer } from './traces';
|
||||
import { usageDataReducer } from './usage';
|
||||
@ -14,6 +15,7 @@ const reducers = combineReducers({
|
||||
traceFilters: TraceFilterReducer,
|
||||
traces: tracesReducer,
|
||||
traceItem: traceItemReducer,
|
||||
trace: traceReducer,
|
||||
usageDate: usageDataReducer,
|
||||
globalTime: globalTimeReducer,
|
||||
metricsData: metricsReducer,
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
GET_SERVICE_LIST_ERROR,
|
||||
GET_SERVICE_LIST_LOADING_START,
|
||||
GET_SERVICE_LIST_SUCCESS,
|
||||
RESET_INITIAL_APPLICATION_DATA,
|
||||
MetricsActions,
|
||||
} from 'types/actions/metrics';
|
||||
import InitialValueTypes from 'types/reducer/metrics';
|
||||
@ -12,7 +13,8 @@ import InitialValueTypes from 'types/reducer/metrics';
|
||||
const InitialValue: InitialValueTypes = {
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
loading: true,
|
||||
metricsApplicationLoading: true,
|
||||
services: [],
|
||||
dbOverView: [],
|
||||
externalService: [],
|
||||
@ -56,18 +58,24 @@ const metrics = (
|
||||
case GET_INITIAL_APPLICATION_LOADING: {
|
||||
return {
|
||||
...state,
|
||||
loading: true,
|
||||
metricsApplicationLoading: true,
|
||||
};
|
||||
}
|
||||
case GET_INITIAL_APPLICATION_ERROR: {
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
metricsApplicationLoading: false,
|
||||
errorMessage: action.payload.errorMessage,
|
||||
error: true,
|
||||
};
|
||||
}
|
||||
|
||||
case RESET_INITIAL_APPLICATION_DATA: {
|
||||
return {
|
||||
...InitialValue,
|
||||
};
|
||||
}
|
||||
|
||||
case GET_INTIAL_APPLICATION_DATA: {
|
||||
const {
|
||||
// dbOverView,
|
||||
@ -80,13 +88,13 @@ const metrics = (
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
// dbOverView,
|
||||
topEndPoints,
|
||||
serviceOverview,
|
||||
// externalService,
|
||||
// externalAverageDuration,
|
||||
// externalError,
|
||||
metricsApplicationLoading: false,
|
||||
};
|
||||
}
|
||||
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: ActionTypes;
|
||||
payload: TraceFilters;
|
||||
};
|
||||
|
||||
const initialState: TraceFilters = {
|
||||
service: '',
|
||||
tags: [],
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Time } from 'container/Header/DateTimeSelection/config';
|
||||
|
||||
export const UPDATE_TIME_INTERVAL = 'UPDATE_TIME_INTERVAL';
|
||||
export const GLOBAL_TIME_LOADING_START = 'GLOBAL_TIME_LOADING_START';
|
||||
|
||||
@ -6,9 +8,13 @@ export type GlobalTime = {
|
||||
minTime: number;
|
||||
};
|
||||
|
||||
interface UpdateTime extends GlobalTime {
|
||||
selectedTime: Time;
|
||||
}
|
||||
|
||||
interface UpdateTimeInterval {
|
||||
type: typeof UPDATE_TIME_INTERVAL;
|
||||
payload: GlobalTime;
|
||||
payload: UpdateTime;
|
||||
}
|
||||
|
||||
interface GlobalTimeLoading {
|
||||
|
@ -2,11 +2,13 @@ import { AppAction } from './app';
|
||||
import { DashboardActions } from './dashboard';
|
||||
import { GlobalTimeAction } from './globalTime';
|
||||
import { MetricsActions } from './metrics';
|
||||
import { TraceActions } from './trace';
|
||||
|
||||
type AppActions =
|
||||
| DashboardActions
|
||||
| AppAction
|
||||
| GlobalTimeAction
|
||||
| MetricsActions;
|
||||
| MetricsActions
|
||||
| TraceActions;
|
||||
|
||||
export default AppActions;
|
||||
|
@ -13,7 +13,7 @@ export const GET_INITIAL_APPLICATION_LOADING =
|
||||
'GET_INITIAL_APPLICATION_LOADING';
|
||||
export const GET_INITIAL_APPLICATION_ERROR = 'GET_INITIAL_APPLICATION_ERROR';
|
||||
export const GET_INTIAL_APPLICATION_DATA = 'GET_INTIAL_APPLICATION_DATA';
|
||||
|
||||
export const RESET_INITIAL_APPLICATION_DATA = 'RESET_INITIAL_APPLICATION_DATA';
|
||||
export interface GetServiceList {
|
||||
type: typeof GET_SERVICE_LIST_SUCCESS;
|
||||
payload: ServicesList[];
|
||||
@ -44,8 +44,13 @@ export interface GetInitialApplicationData {
|
||||
};
|
||||
}
|
||||
|
||||
export interface ResetInitialApplicationData {
|
||||
type: typeof RESET_INITIAL_APPLICATION_DATA;
|
||||
}
|
||||
|
||||
export type MetricsActions =
|
||||
| GetServiceListError
|
||||
| GetServiceListLoading
|
||||
| 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 BadRequest = 400;
|
||||
|
||||
export type Unauthorized = 401;
|
||||
|
||||
export type NotFound = 404;
|
||||
@ -17,6 +19,7 @@ export type ErrorStatusCode =
|
||||
| Forbidden
|
||||
| Unauthorized
|
||||
| NotFound
|
||||
| ServerError;
|
||||
| ServerError
|
||||
| BadRequest;
|
||||
|
||||
export type StatusCode = SuccessStatusCode | ErrorStatusCode;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Time } from 'container/Header/DateTimeSelection/config';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
|
||||
export interface GlobalReducer {
|
||||
maxTime: GlobalTime['maxTime'];
|
||||
minTime: GlobalTime['minTime'];
|
||||
loading: boolean;
|
||||
selectedTime: Time;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import { TopEndPoints } from 'types/api/metrics/getTopEndPoints';
|
||||
interface MetricReducer {
|
||||
services: ServicesList[];
|
||||
loading: boolean;
|
||||
metricsApplicationLoading: boolean;
|
||||
error: boolean;
|
||||
errorMessage: string;
|
||||
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 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.
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
||||
|
||||
export const isOnboardingSkipped = () => {
|
||||
export const isOnboardingSkipped = (): boolean => {
|
||||
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 { resolve } from 'path';
|
||||
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { WebpackPluginInstance } from 'webpack-dev-middleware/node_modules/webpack';
|
||||
import webpack, { WebpackPluginInstance } from 'webpack';
|
||||
|
||||
const __dirname = resolve();
|
||||
|
||||
@ -14,7 +13,7 @@ const config: webpack.Configuration = {
|
||||
devtool: 'source-map',
|
||||
entry: resolve(__dirname, './src/index.tsx'),
|
||||
output: {
|
||||
filename: ({ chunk }: any): string => {
|
||||
filename: ({ chunk }): string => {
|
||||
const hash = chunk?.hash;
|
||||
const name = chunk?.name;
|
||||
return `js/${name}-${hash}.js`;
|
||||
@ -53,12 +52,12 @@ const config: webpack.Configuration = {
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({ template: 'src/index.html.ejs' }),
|
||||
new CompressionPlugin({
|
||||
(new CompressionPlugin({
|
||||
exclude: /.map$/,
|
||||
}) as any,
|
||||
new CopyPlugin({
|
||||
}) as unknown) as WebpackPluginInstance,
|
||||
(new CopyPlugin({
|
||||
patterns: [{ from: resolve(__dirname, 'public/'), to: '.' }],
|
||||
}) as any,
|
||||
}) as unknown) as WebpackPluginInstance,
|
||||
new webpack.ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
@ -71,4 +70,4 @@ const config: webpack.Configuration = {
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
export default config;
|
||||
|
@ -2,13 +2,12 @@
|
||||
import dotenv from 'dotenv';
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import { resolve } from 'path';
|
||||
//@ts-ignore
|
||||
import portFinderSync from 'portfinder-sync';
|
||||
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
|
||||
import webpack from 'webpack';
|
||||
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server';
|
||||
|
||||
// @ts-ignore
|
||||
import portFinderSync from 'portfinder-sync';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const __dirname = resolve();
|
||||
@ -29,10 +28,10 @@ const config: Configuration = {
|
||||
liveReload: true,
|
||||
port: portFinderSync.getPort(3000),
|
||||
static: {
|
||||
directory: resolve(__dirname, "public"),
|
||||
publicPath: "/",
|
||||
directory: resolve(__dirname, 'public'),
|
||||
publicPath: '/',
|
||||
watch: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
target: 'web',
|
||||
output: {
|
||||
@ -86,4 +85,4 @@ const config: Configuration = {
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
export default config;
|
||||
|
@ -3118,6 +3118,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.5.1.tgz#b5fde2f0f79c1e120307c415a4c1d5eb15a6f278"
|
||||
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":
|
||||
version "1.2.0"
|
||||
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"
|
||||
integrity sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==
|
||||
|
||||
cypress@8.6.0:
|
||||
version "8.6.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.6.0.tgz#8d02fa58878b37cfc45bbfce393aa974fa8a8e22"
|
||||
integrity sha512-F7qEK/6Go5FsqTueR+0wEw2vOVKNgk5847Mys8vsWkzPoEKdxs+7N9Y1dit+zhaZCLtMPyrMwjfA53ZFy+lSww==
|
||||
cypress@^8.3.0:
|
||||
version "8.7.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.7.0.tgz#2ee371f383d8f233d3425b6cc26ddeec2668b6da"
|
||||
integrity sha512-b1bMC3VQydC6sXzBMFnSqcvwc9dTZMgcaOzT0vpSD+Gq1yFc+72JDWi55sfUK5eIeNLAtWOGy1NNb6UlhMvB+Q==
|
||||
dependencies:
|
||||
"@cypress/request" "^2.88.6"
|
||||
"@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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@ -14658,7 +14665,7 @@ uuid@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
|
||||
integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=
|
||||
|
||||
uuid@^3.3.2, uuid@^3.4.0:
|
||||
uuid@^3.4.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||
|
Loading…
x
Reference in New Issue
Block a user