mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-16 05:55:53 +08:00
Fix(FE): global time (#332)
* 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 * fix: Time selection for custom is fixed * fix: usage graph is fixed * fix: global time selector is fixed and empty graph on click handler is added * fix: metrics application is now fixed
This commit is contained in:
parent
1ebf1a3675
commit
d2b107ec7f
@ -1,8 +1,7 @@
|
|||||||
import NotFound from 'components/NotFound';
|
import NotFound from 'components/NotFound';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
|
import AppLayout from 'container/AppLayout';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import AppLayout from 'modules/AppLayout';
|
|
||||||
import { RouteProvider } from 'modules/RouteProvider';
|
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { Route, Router, Switch } from 'react-router-dom';
|
import { Route, Router, Switch } from 'react-router-dom';
|
||||||
|
|
||||||
@ -10,7 +9,6 @@ import routes from './routes';
|
|||||||
|
|
||||||
const App = (): JSX.Element => (
|
const App = (): JSX.Element => (
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<RouteProvider>
|
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||||
<Switch>
|
<Switch>
|
||||||
@ -23,7 +21,6 @@ const App = (): JSX.Element => (
|
|||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</RouteProvider>
|
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import Loadable from 'components/Loadable';
|
import Loadable from 'components/Loadable';
|
||||||
|
|
||||||
|
export const ServicesTablePage = Loadable(
|
||||||
|
() => import(/* webpackChunkName: "ServicesTablePage" */ 'pages/Metrics'),
|
||||||
|
);
|
||||||
|
|
||||||
export const ServiceMetricsPage = Loadable(
|
export const ServiceMetricsPage = Loadable(
|
||||||
() =>
|
() =>
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "ServiceMetricsPage" */ 'modules/Metrics/ServiceMetricsDef'
|
/* webpackChunkName: "ServiceMetricsPage" */ 'pages/MetricApplication'
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -35,13 +39,6 @@ export const UsageExplorerPage = Loadable(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ServicesTablePage = Loadable(
|
|
||||||
() =>
|
|
||||||
import(
|
|
||||||
/* webpackChunkName: "ServicesTablePage" */ 'modules/Metrics/ServicesTableDef'
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const SignupPage = Loadable(
|
export const SignupPage = Loadable(
|
||||||
() => import(/* webpackChunkName: "SignupPage" */ 'pages/SignUp'),
|
() => import(/* webpackChunkName: "SignupPage" */ 'pages/SignUp'),
|
||||||
);
|
);
|
||||||
|
5
frontend/src/api/browser/localstorage/get.ts
Normal file
5
frontend/src/api/browser/localstorage/get.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const get = (key: string): string | null => {
|
||||||
|
return localStorage.getItem(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default get;
|
5
frontend/src/api/browser/localstorage/remove.ts
Normal file
5
frontend/src/api/browser/localstorage/remove.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const remove = (key: string): void => {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default remove;
|
5
frontend/src/api/browser/localstorage/set.ts
Normal file
5
frontend/src/api/browser/localstorage/set.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const set = (key: string, value: string): void => {
|
||||||
|
localStorage.setItem(key, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default set;
|
26
frontend/src/api/metrics/getDBOverView.ts
Normal file
26
frontend/src/api/metrics/getDBOverView.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/metrics/getDBOverview';
|
||||||
|
|
||||||
|
const getDBOverView = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/service/dbOverview?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getDBOverView;
|
29
frontend/src/api/metrics/getExternalAverageDuration.ts
Normal file
29
frontend/src/api/metrics/getExternalAverageDuration.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
PayloadProps,
|
||||||
|
Props,
|
||||||
|
} from 'types/api/metrics/getExternalAverageDuration';
|
||||||
|
|
||||||
|
const getExternalAverageDuration = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/service/externalAvgDuration?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getExternalAverageDuration;
|
26
frontend/src/api/metrics/getExternalError.ts
Normal file
26
frontend/src/api/metrics/getExternalError.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/metrics/getExternalError';
|
||||||
|
|
||||||
|
const getExternalError = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/service/externalErrors?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getExternalError;
|
26
frontend/src/api/metrics/getExternalService.ts
Normal file
26
frontend/src/api/metrics/getExternalService.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/metrics/getExternalService';
|
||||||
|
|
||||||
|
const getExternalService = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/service/external?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getExternalService;
|
26
frontend/src/api/metrics/getService.ts
Normal file
26
frontend/src/api/metrics/getService.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/metrics/getService';
|
||||||
|
|
||||||
|
const getService = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/services?&start=${props.start}&end=${props.end}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getService;
|
26
frontend/src/api/metrics/getServiceOverview.ts
Normal file
26
frontend/src/api/metrics/getServiceOverview.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/metrics/getServiceOverview';
|
||||||
|
|
||||||
|
const getServiceOverview = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/service/overview?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getServiceOverview;
|
26
frontend/src/api/metrics/getTopEndPoints.ts
Normal file
26
frontend/src/api/metrics/getTopEndPoints.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/metrics/getTopEndPoints';
|
||||||
|
|
||||||
|
const getTopEndPoints = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/service/top_endpoints?&start=${props.start}&end=${props.end}&service=${props.service}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getTopEndPoints;
|
@ -1,8 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
|
ActiveElement,
|
||||||
BarController,
|
BarController,
|
||||||
BarElement,
|
BarElement,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
Chart,
|
Chart,
|
||||||
|
ChartData,
|
||||||
|
ChartEvent,
|
||||||
ChartOptions,
|
ChartOptions,
|
||||||
ChartType,
|
ChartType,
|
||||||
Decimation,
|
Decimation,
|
||||||
@ -13,7 +16,6 @@ import {
|
|||||||
LineController,
|
LineController,
|
||||||
LineElement,
|
LineElement,
|
||||||
PointElement,
|
PointElement,
|
||||||
ScaleOptions,
|
|
||||||
SubTitle,
|
SubTitle,
|
||||||
TimeScale,
|
TimeScale,
|
||||||
TimeSeriesScale,
|
TimeSeriesScale,
|
||||||
@ -53,8 +55,6 @@ const Graph = ({
|
|||||||
type,
|
type,
|
||||||
title,
|
title,
|
||||||
isStacked,
|
isStacked,
|
||||||
label,
|
|
||||||
xAxisType,
|
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
}: GraphProps): JSX.Element => {
|
}: GraphProps): JSX.Element => {
|
||||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
@ -97,25 +97,18 @@ const Graph = ({
|
|||||||
legend: {
|
legend: {
|
||||||
// just making sure that label is present
|
// just making sure that label is present
|
||||||
display: !(
|
display: !(
|
||||||
data.datasets.find((e) => e.label !== undefined) === undefined
|
data.datasets.find((e) => {
|
||||||
|
if (e.label?.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return e.label !== undefined;
|
||||||
|
}) === undefined
|
||||||
),
|
),
|
||||||
labels: {
|
labels: {
|
||||||
usePointStyle: true,
|
usePointStyle: true,
|
||||||
pointStyle: 'circle',
|
pointStyle: 'circle',
|
||||||
},
|
},
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
// labels: {
|
|
||||||
// generateLabels: (chart: Chart): LegendItem[] => {
|
|
||||||
// return (data.datasets || []).map((e, index) => {
|
|
||||||
// return {
|
|
||||||
// text: e.label || '',
|
|
||||||
// datasetIndex: index,
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// pointStyle: 'circle',
|
|
||||||
// usePointStyle: true,
|
|
||||||
// },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
@ -123,16 +116,17 @@ const Graph = ({
|
|||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
animate: false,
|
|
||||||
grid: {
|
grid: {
|
||||||
display: true,
|
display: true,
|
||||||
color: getGridColor(),
|
color: getGridColor(),
|
||||||
},
|
},
|
||||||
labels: label,
|
|
||||||
adapters: {
|
adapters: {
|
||||||
date: chartjsAdapter,
|
date: chartjsAdapter,
|
||||||
},
|
},
|
||||||
type: xAxisType,
|
time: {
|
||||||
|
unit: 'minute',
|
||||||
|
},
|
||||||
|
type: 'timeseries',
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
display: true,
|
display: true,
|
||||||
@ -151,77 +145,26 @@ const Graph = ({
|
|||||||
cubicInterpolationMode: 'monotone',
|
cubicInterpolationMode: 'monotone',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onClick: onClickHandler,
|
onClick: (event, element, chart) => {
|
||||||
|
if (onClickHandler) {
|
||||||
|
onClickHandler(event, element, chart, data);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
lineChartRef.current = new Chart(chartRef.current, {
|
lineChartRef.current = new Chart(chartRef.current, {
|
||||||
type: type,
|
type: type,
|
||||||
data: data,
|
data: data,
|
||||||
options,
|
options,
|
||||||
// plugins: [
|
|
||||||
// {
|
|
||||||
// id: 'htmlLegendPlugin',
|
|
||||||
// afterUpdate: (chart: Chart): void => {
|
|
||||||
// if (
|
|
||||||
// chart &&
|
|
||||||
// chart.options &&
|
|
||||||
// chart.options.plugins &&
|
|
||||||
// chart.options.plugins.legend &&
|
|
||||||
// chart.options.plugins.legend.labels &&
|
|
||||||
// chart.options.plugins.legend.labels.generateLabels
|
|
||||||
// ) {
|
|
||||||
// const labels = chart.options.plugins?.legend?.labels?.generateLabels(
|
|
||||||
// chart,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const id = 'htmlLegend';
|
|
||||||
|
|
||||||
// const response = document.getElementById(id);
|
|
||||||
|
|
||||||
// if (labels && response && response?.childNodes.length === 0) {
|
|
||||||
// const labelComponent = labels.map((e, index) => {
|
|
||||||
// return {
|
|
||||||
// element: Legends({
|
|
||||||
// text: e.text,
|
|
||||||
// color: colors[index] || 'white',
|
|
||||||
// }),
|
|
||||||
// dataIndex: e.datasetIndex,
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
|
|
||||||
// labelComponent.map((e) => {
|
|
||||||
// const el = stringToHTML(e.element);
|
|
||||||
|
|
||||||
// if (el) {
|
|
||||||
// el.addEventListener('click', () => {
|
|
||||||
// chart.setDatasetVisibility(
|
|
||||||
// e.dataIndex,
|
|
||||||
// !chart.isDatasetVisible(e.dataIndex),
|
|
||||||
// );
|
|
||||||
// chart.update();
|
|
||||||
// });
|
|
||||||
// response.append(el);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [chartRef, data, type, title, isStacked, label, xAxisType, getGridColor]);
|
}, [chartRef, data, type, title, isStacked, getGridColor, onClickHandler]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
buildChart();
|
buildChart();
|
||||||
}, [buildChart]);
|
}, [buildChart]);
|
||||||
|
|
||||||
return (
|
return <canvas ref={chartRef} />;
|
||||||
<>
|
|
||||||
<canvas ref={chartRef} />
|
|
||||||
{/* <LegendsContainer id="htmlLegend" /> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface GraphProps {
|
interface GraphProps {
|
||||||
@ -230,8 +173,14 @@ interface GraphProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
isStacked?: boolean;
|
isStacked?: boolean;
|
||||||
label?: string[];
|
label?: string[];
|
||||||
xAxisType?: ScaleOptions['type'];
|
onClickHandler?: graphOnClickHandler;
|
||||||
onClickHandler?: ChartOptions['onClick'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type graphOnClickHandler = (
|
||||||
|
event: ChartEvent,
|
||||||
|
elements: ActiveElement[],
|
||||||
|
chart: Chart,
|
||||||
|
data: ChartData,
|
||||||
|
) => void;
|
||||||
|
|
||||||
export default Graph;
|
export default Graph;
|
||||||
|
@ -1,39 +1,38 @@
|
|||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import SideNav from 'components/SideNav';
|
import get from 'api/browser/localstorage/get';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
|
import TopNav from 'container/Header';
|
||||||
|
import SideNav from 'container/SideNav';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React, { ReactNode, useEffect } from 'react';
|
import React, { ReactNode, useEffect } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
import TopNav from './Nav/TopNav';
|
|
||||||
import { useRoute } from './RouteProvider';
|
|
||||||
|
|
||||||
const { Content, Footer } = Layout;
|
const { Content, Footer } = Layout;
|
||||||
|
|
||||||
interface BaseLayoutProps {
|
interface BaseLayoutProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BaseLayout: React.FC<BaseLayoutProps> = ({ children }) => {
|
const BaseLayout: React.FC<BaseLayoutProps> = ({ children }) => {
|
||||||
const location = useLocation();
|
|
||||||
const { dispatch } = useRoute();
|
|
||||||
const currentYear = new Date().getFullYear();
|
|
||||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
const isLoggedInLocalStorage = get('isLoggedIn');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch({ type: 'ROUTE_IS_LOADED', payload: location.pathname });
|
if (isLoggedIn && history.location.pathname === '/') {
|
||||||
}, [location, dispatch]);
|
history.push(ROUTES.APPLICATION);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
if (!isLoggedIn && isLoggedInLocalStorage !== null) {
|
||||||
if (isLoggedIn) {
|
|
||||||
history.push(ROUTES.APPLICATION);
|
history.push(ROUTES.APPLICATION);
|
||||||
} else {
|
} else {
|
||||||
|
if (isLoggedInLocalStorage === null) {
|
||||||
history.push(ROUTES.SIGN_UP);
|
history.push(ROUTES.SIGN_UP);
|
||||||
}
|
}
|
||||||
}, [isLoggedIn]);
|
}
|
||||||
|
}, [isLoggedIn, isLoggedInLocalStorage]);
|
||||||
|
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: '100vh' }}>
|
<Layout style={{ minHeight: '100vh' }}>
|
26
frontend/src/container/AppLayout/styles.ts
Normal file
26
frontend/src/container/AppLayout/styles.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Layout as LayoutComponent } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const Layout = styled.div`
|
||||||
|
&&& {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Content = styled(LayoutComponent.Content)`
|
||||||
|
&&& {
|
||||||
|
margin: 0 1rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Footer = styled(LayoutComponent.Footer)`
|
||||||
|
&&& {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Main = styled.main`
|
||||||
|
min-height: 80vh;
|
||||||
|
`;
|
@ -1,6 +1,6 @@
|
|||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import { ChartData } from 'chart.js';
|
import { ChartData, ChartOptions } from 'chart.js';
|
||||||
import Graph from 'components/Graph';
|
import Graph, { graphOnClickHandler } from 'components/Graph';
|
||||||
import ValueGraph from 'components/ValueGraph';
|
import ValueGraph from 'components/ValueGraph';
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
@ -14,6 +14,7 @@ const GridGraphComponent = ({
|
|||||||
title,
|
title,
|
||||||
opacity,
|
opacity,
|
||||||
isStacked,
|
isStacked,
|
||||||
|
onClickHandler,
|
||||||
}: GridGraphComponentProps): JSX.Element | null => {
|
}: GridGraphComponentProps): JSX.Element | null => {
|
||||||
const location = history.location.pathname;
|
const location = history.location.pathname;
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ const GridGraphComponent = ({
|
|||||||
isStacked,
|
isStacked,
|
||||||
opacity,
|
opacity,
|
||||||
xAxisType: 'time',
|
xAxisType: 'time',
|
||||||
|
onClickHandler: onClickHandler,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -66,6 +68,7 @@ export interface GridGraphComponentProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
opacity?: string;
|
opacity?: string;
|
||||||
isStacked?: boolean;
|
isStacked?: boolean;
|
||||||
|
onClickHandler?: graphOnClickHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GridGraphComponent;
|
export default GridGraphComponent;
|
||||||
|
@ -0,0 +1,90 @@
|
|||||||
|
import Graph, { graphOnClickHandler } from 'components/Graph';
|
||||||
|
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
|
||||||
|
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||||
|
import { colors } from 'lib/getRandomColor';
|
||||||
|
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
||||||
|
import getTimeString from 'lib/getTimeString';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
const EmptyGraph = ({
|
||||||
|
selectedTime,
|
||||||
|
widget,
|
||||||
|
onClickHandler,
|
||||||
|
}: EmptyGraphProps): JSX.Element => {
|
||||||
|
const { minTime, maxTime, loading } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
const maxMinTime = GetMaxMinTime({
|
||||||
|
graphType: widget.panelTypes,
|
||||||
|
maxTime,
|
||||||
|
minTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { end, start } = getStartAndEndTime({
|
||||||
|
type: selectedTime.enum,
|
||||||
|
maxTime: maxMinTime.maxTime,
|
||||||
|
minTime: maxMinTime.minTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dateFunction = useCallback(() => {
|
||||||
|
if (!loading) {
|
||||||
|
const dates: Date[] = [];
|
||||||
|
|
||||||
|
const startString = getTimeString(start);
|
||||||
|
const endString = getTimeString(end);
|
||||||
|
|
||||||
|
const parsedStart = parseInt(startString, 10);
|
||||||
|
const parsedEnd = parseInt(endString, 10);
|
||||||
|
|
||||||
|
let startDate = parsedStart;
|
||||||
|
const endDate = parsedEnd;
|
||||||
|
|
||||||
|
while (endDate >= startDate) {
|
||||||
|
const newDate = new Date(startDate);
|
||||||
|
|
||||||
|
startDate = startDate + 20000;
|
||||||
|
|
||||||
|
dates.push(newDate);
|
||||||
|
}
|
||||||
|
return dates;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, [start, end, loading]);
|
||||||
|
|
||||||
|
const date = dateFunction();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Graph
|
||||||
|
{...{
|
||||||
|
type: 'line',
|
||||||
|
onClickHandler: onClickHandler,
|
||||||
|
data: {
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: new Array(date?.length).fill(0),
|
||||||
|
borderColor: colors[0],
|
||||||
|
showLine: true,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
spanGaps: true,
|
||||||
|
pointRadius: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
labels: date,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface EmptyGraphProps {
|
||||||
|
selectedTime: timePreferance;
|
||||||
|
widget: Widgets;
|
||||||
|
onClickHandler: graphOnClickHandler | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmptyGraph;
|
@ -2,6 +2,7 @@ import { Button, Typography } from 'antd';
|
|||||||
import getQueryResult from 'api/widgets/getQuery';
|
import getQueryResult from 'api/widgets/getQuery';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { ChartData } from 'chart.js';
|
import { ChartData } from 'chart.js';
|
||||||
|
import { graphOnClickHandler } from 'components/Graph';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import TimePreference from 'components/TimePreferenceDropDown';
|
import TimePreference from 'components/TimePreferenceDropDown';
|
||||||
import GridGraphComponent from 'container/GridGraphComponent';
|
import GridGraphComponent from 'container/GridGraphComponent';
|
||||||
@ -12,15 +13,21 @@ import {
|
|||||||
import getChartData from 'lib/getChartData';
|
import getChartData from 'lib/getChartData';
|
||||||
import GetMaxMinTime from 'lib/getMaxMinTime';
|
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||||
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { memo, useCallback, useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { GlobalTime } from 'store/actions';
|
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
|
import EmptyGraph from './EmptyGraph';
|
||||||
import { GraphContainer, NotFoundContainer, TimeContainer } from './styles';
|
import { GraphContainer, NotFoundContainer, TimeContainer } from './styles';
|
||||||
|
|
||||||
const FullView = ({ widget }: FullViewProps): JSX.Element => {
|
const FullView = ({
|
||||||
|
widget,
|
||||||
|
fullViewOptions = true,
|
||||||
|
onClickHandler,
|
||||||
|
noDataGraph = false,
|
||||||
|
}: FullViewProps): JSX.Element => {
|
||||||
const { minTime, maxTime } = useSelector<AppState, GlobalTime>(
|
const { minTime, maxTime } = useSelector<AppState, GlobalTime>(
|
||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
@ -65,7 +72,7 @@ const FullView = ({ widget }: FullViewProps): JSX.Element => {
|
|||||||
end,
|
end,
|
||||||
query: query.query,
|
query: query.query,
|
||||||
start: start,
|
start: start,
|
||||||
step: '30',
|
step: '60',
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
query: query.query,
|
query: query.query,
|
||||||
@ -118,28 +125,26 @@ const FullView = ({ widget }: FullViewProps): JSX.Element => {
|
|||||||
onFetchDataHandler();
|
onFetchDataHandler();
|
||||||
}, [onFetchDataHandler]);
|
}, [onFetchDataHandler]);
|
||||||
|
|
||||||
|
if (state.error && !state.loading) {
|
||||||
|
return (
|
||||||
|
<NotFoundContainer>
|
||||||
|
<Typography>{state.errorMessage}</Typography>
|
||||||
|
</NotFoundContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (state.loading || state.payload === undefined) {
|
if (state.loading || state.payload === undefined) {
|
||||||
return <Spinner height="80vh" size="large" tip="Loading..." />;
|
return (
|
||||||
|
<div>
|
||||||
|
<Spinner height="80vh" size="large" tip="Loading..." />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.loading === false && state.payload.datasets.length === 0) {
|
if (state.loading === false && state.payload.datasets.length === 0) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TimePreference
|
{fullViewOptions && (
|
||||||
{...{
|
|
||||||
selectedTime,
|
|
||||||
setSelectedTime,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<NotFoundContainer>
|
|
||||||
<Typography>No Data</Typography>
|
|
||||||
</NotFoundContainer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TimeContainer>
|
<TimeContainer>
|
||||||
<TimePreference
|
<TimePreference
|
||||||
{...{
|
{...{
|
||||||
@ -151,6 +156,38 @@ const FullView = ({ widget }: FullViewProps): JSX.Element => {
|
|||||||
Refresh
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
</TimeContainer>
|
</TimeContainer>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{noDataGraph ? (
|
||||||
|
<EmptyGraph
|
||||||
|
onClickHandler={onClickHandler}
|
||||||
|
widget={widget}
|
||||||
|
selectedTime={selectedTime}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<NotFoundContainer>
|
||||||
|
<Typography>No Data</Typography>
|
||||||
|
</NotFoundContainer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{fullViewOptions && (
|
||||||
|
<TimeContainer>
|
||||||
|
<TimePreference
|
||||||
|
{...{
|
||||||
|
selectedTime,
|
||||||
|
setSelectedTime,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button onClick={onFetchDataHandler} type="primary">
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</TimeContainer>
|
||||||
|
)}
|
||||||
|
|
||||||
<GraphContainer>
|
<GraphContainer>
|
||||||
<GridGraphComponent
|
<GridGraphComponent
|
||||||
@ -160,6 +197,7 @@ const FullView = ({ widget }: FullViewProps): JSX.Element => {
|
|||||||
isStacked: widget.isStacked,
|
isStacked: widget.isStacked,
|
||||||
opacity: widget.opacity,
|
opacity: widget.opacity,
|
||||||
title: widget.title,
|
title: widget.title,
|
||||||
|
onClickHandler: onClickHandler,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
@ -176,6 +214,20 @@ interface FullViewState {
|
|||||||
|
|
||||||
interface FullViewProps {
|
interface FullViewProps {
|
||||||
widget: Widgets;
|
widget: Widgets;
|
||||||
|
fullViewOptions?: boolean;
|
||||||
|
onClickHandler?: graphOnClickHandler;
|
||||||
|
noDataGraph?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FullView;
|
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;
|
||||||
|
});
|
||||||
|
@ -6,6 +6,7 @@ export const GraphContainer = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const NotFoundContainer = styled.div`
|
export const NotFoundContainer = styled.div`
|
||||||
|
@ -12,13 +12,13 @@ import { useSelector } from 'react-redux';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { GlobalTime } from 'store/actions';
|
|
||||||
import {
|
import {
|
||||||
DeleteWidget,
|
DeleteWidget,
|
||||||
DeleteWidgetProps,
|
DeleteWidgetProps,
|
||||||
} from 'store/actions/dashboard/deleteWidget';
|
} from 'store/actions/dashboard/deleteWidget';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
import Bar from './Bar';
|
import Bar from './Bar';
|
||||||
@ -65,7 +65,7 @@ const GridCardGraph = ({
|
|||||||
end,
|
end,
|
||||||
query: query.query,
|
query: query.query,
|
||||||
start: start,
|
start: start,
|
||||||
step: '30',
|
step: '60',
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
48
frontend/src/container/Header/Breadcrumbs/index.tsx
Normal file
48
frontend/src/container/Header/Breadcrumbs/index.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Breadcrumb } from 'antd';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
const breadcrumbNameMap = {
|
||||||
|
[ROUTES.APPLICATION]: 'Application',
|
||||||
|
[ROUTES.TRACES]: 'Traces',
|
||||||
|
[ROUTES.SERVICE_MAP]: 'Service Map',
|
||||||
|
[ROUTES.USAGE_EXPLORER]: 'Usage Explorer',
|
||||||
|
[ROUTES.INSTRUMENTATION]: 'Add instrumentation',
|
||||||
|
[ROUTES.SETTINGS]: 'Settings',
|
||||||
|
[ROUTES.DASHBOARD]: 'Dashboard',
|
||||||
|
};
|
||||||
|
|
||||||
|
import { RouteComponentProps, withRouter } from 'react-router';
|
||||||
|
|
||||||
|
const ShowBreadcrumbs = (props: RouteComponentProps): JSX.Element => {
|
||||||
|
const pathArray = props.location.pathname.split('/').filter((i) => i);
|
||||||
|
|
||||||
|
const extraBreadcrumbItems = pathArray.map((_, index) => {
|
||||||
|
const url = `/${pathArray.slice(0, index + 1).join('/')}`;
|
||||||
|
|
||||||
|
if (breadcrumbNameMap[url] === undefined) {
|
||||||
|
return (
|
||||||
|
<Breadcrumb.Item key={url}>
|
||||||
|
<Link to={url}>{url.split('/').slice(-1)[0]}</Link>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Breadcrumb.Item key={url}>
|
||||||
|
<Link to={url}>{breadcrumbNameMap[url]}</Link>
|
||||||
|
</Breadcrumb.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const breadcrumbItems = [
|
||||||
|
<Breadcrumb.Item key="home">
|
||||||
|
<Link to="/">Home</Link>
|
||||||
|
</Breadcrumb.Item>,
|
||||||
|
].concat(extraBreadcrumbItems);
|
||||||
|
|
||||||
|
return <Breadcrumb>{breadcrumbItems}</Breadcrumb>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withRouter(ShowBreadcrumbs);
|
@ -2,22 +2,15 @@ import { DatePicker, Modal } from 'antd';
|
|||||||
import { Moment } from 'moment';
|
import { Moment } from 'moment';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { DateTimeRangeType } from 'store/actions';
|
export type DateTimeRangeType = [Moment | null, Moment | null] | null;
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
interface CustomDateTimeModalProps {
|
const CustomDateTimeModal = ({
|
||||||
visible: boolean;
|
|
||||||
onCreate: (dateTimeRange: DateTimeRangeType) => void; //Store is defined in antd forms library
|
|
||||||
onCancel: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({
|
|
||||||
//destructuring props
|
|
||||||
visible,
|
visible,
|
||||||
onCreate,
|
onCreate,
|
||||||
onCancel,
|
onCancel,
|
||||||
}) => {
|
}: CustomDateTimeModalProps): JSX.Element => {
|
||||||
const [
|
const [
|
||||||
customDateTimeRange,
|
customDateTimeRange,
|
||||||
setCustomDateTimeRange,
|
setCustomDateTimeRange,
|
||||||
@ -26,6 +19,7 @@ const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({
|
|||||||
function handleRangePickerOk(date_time: DateTimeRangeType): void {
|
function handleRangePickerOk(date_time: DateTimeRangeType): void {
|
||||||
setCustomDateTimeRange(date_time);
|
setCustomDateTimeRange(date_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disabledDate(current: Moment): boolean {
|
function disabledDate(current: Moment): boolean {
|
||||||
if (current > moment()) {
|
if (current > moment()) {
|
||||||
return true;
|
return true;
|
||||||
@ -53,4 +47,10 @@ const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface CustomDateTimeModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onCreate: (dateTimeRange: DateTimeRangeType) => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export default CustomDateTimeModal;
|
export default CustomDateTimeModal;
|
60
frontend/src/container/Header/DateTimeSelection/config.ts
Normal file
60
frontend/src/container/Header/DateTimeSelection/config.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
|
||||||
|
type fiveMin = '5min';
|
||||||
|
type fifteenMin = '15min';
|
||||||
|
type thrityMin = '30min';
|
||||||
|
type oneMin = '1min';
|
||||||
|
type sixHour = '6hr';
|
||||||
|
type oneHour = '1hr';
|
||||||
|
type oneDay = '1day';
|
||||||
|
type oneWeek = '1week';
|
||||||
|
type custom = 'custom';
|
||||||
|
|
||||||
|
export type Time =
|
||||||
|
| fiveMin
|
||||||
|
| fifteenMin
|
||||||
|
| thrityMin
|
||||||
|
| oneMin
|
||||||
|
| sixHour
|
||||||
|
| oneHour
|
||||||
|
| custom
|
||||||
|
| oneWeek
|
||||||
|
| oneDay;
|
||||||
|
|
||||||
|
export const Options: Option[] = [
|
||||||
|
{ value: '5min', label: 'Last 5 min' },
|
||||||
|
{ value: '15min', label: 'Last 15 min' },
|
||||||
|
{ value: '30min', label: 'Last 30 min' },
|
||||||
|
{ value: '1hr', label: 'Last 1 hour' },
|
||||||
|
{ value: '6hr', label: 'Last 6 hour' },
|
||||||
|
{ value: '1day', label: 'Last 1 day' },
|
||||||
|
{ value: '1week', label: 'Last 1 week' },
|
||||||
|
{ value: 'custom', label: 'Custom' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface Option {
|
||||||
|
value: Time;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServiceMapOptions: Option[] = [
|
||||||
|
{ value: '1min', label: 'Last 1 min' },
|
||||||
|
{ value: '5min', label: 'Last 5 min' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getDefaultOption = (route: string): Time => {
|
||||||
|
if (route === ROUTES.SERVICE_MAP) {
|
||||||
|
return ServiceMapOptions[0].value;
|
||||||
|
}
|
||||||
|
if (route === ROUTES.APPLICATION) {
|
||||||
|
return Options[0].value;
|
||||||
|
}
|
||||||
|
return Options[2].value;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOptions = (routes: string): Option[] => {
|
||||||
|
if (routes === ROUTES.SERVICE_MAP) {
|
||||||
|
return ServiceMapOptions;
|
||||||
|
}
|
||||||
|
return Options;
|
||||||
|
};
|
328
frontend/src/container/Header/DateTimeSelection/index.tsx
Normal file
328
frontend/src/container/Header/DateTimeSelection/index.tsx
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
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';
|
||||||
|
const { Option } = DefaultSelect;
|
||||||
|
import get from 'api/browser/localstorage/get';
|
||||||
|
import set from 'api/browser/localstorage/set';
|
||||||
|
import { LOCAL_STORAGE } from 'constants/localStorage';
|
||||||
|
import getTimeString from 'lib/getTimeString';
|
||||||
|
import moment from 'moment';
|
||||||
|
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 { AppState } from 'store/reducers';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal';
|
||||||
|
|
||||||
|
const DateTimeSelection = ({
|
||||||
|
location,
|
||||||
|
updateTimeInterval,
|
||||||
|
}: Props): JSX.Element => {
|
||||||
|
const [form_dtselector] = Form.useForm();
|
||||||
|
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 getDefaultTime = (pathName: string): Time => {
|
||||||
|
const defaultSelectedOption = getDefaultOption(pathName);
|
||||||
|
|
||||||
|
const routes = get(LOCAL_STORAGE.METRICS_TIME_IN_DURATION);
|
||||||
|
|
||||||
|
if (routes !== null) {
|
||||||
|
const routesObject = JSON.parse(routes);
|
||||||
|
const selectedTime = routesObject[pathName];
|
||||||
|
|
||||||
|
if (selectedTime) {
|
||||||
|
return selectedTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultSelectedOption;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [selectedTimeInterval, setSelectedTimeInterval] = useState<Time>(
|
||||||
|
getDefaultTime(location.pathname),
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateLocalStorageForRoutes = (value: Time): void => {
|
||||||
|
const preRoutes = get(LOCAL_STORAGE.METRICS_TIME_IN_DURATION);
|
||||||
|
if (preRoutes !== null) {
|
||||||
|
const preRoutesObject = JSON.parse(preRoutes);
|
||||||
|
|
||||||
|
const preRoute = {
|
||||||
|
...preRoutesObject,
|
||||||
|
};
|
||||||
|
preRoute[location.pathname] = value;
|
||||||
|
|
||||||
|
set(LOCAL_STORAGE.METRICS_TIME_IN_DURATION, JSON.stringify(preRoute));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectHandler = (value: Time): void => {
|
||||||
|
isOnSelectHandler.current = true;
|
||||||
|
|
||||||
|
if (value !== 'custom') {
|
||||||
|
updateTimeInterval(value);
|
||||||
|
const selectedLabel = getInputLabel(undefined, undefined, value);
|
||||||
|
setSelectedTimeInterval(selectedLabel as Time);
|
||||||
|
updateLocalStorageForRoutes(value);
|
||||||
|
} else {
|
||||||
|
setRefreshButtonHidden(true);
|
||||||
|
setCustomDTPickerVisible(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRefreshHandler = (): void => {
|
||||||
|
onSelectHandler(selectedTimeInterval);
|
||||||
|
onLastRefreshHandler();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInputLabel = (
|
||||||
|
startTime?: moment.Moment,
|
||||||
|
endTime?: moment.Moment,
|
||||||
|
timeInterval: Time = '15min',
|
||||||
|
): string | Time => {
|
||||||
|
if (startTime && endTime && timeInterval === 'custom') {
|
||||||
|
const format = 'YYYY/MM/DD HH:mm';
|
||||||
|
|
||||||
|
const startString = startTime.format(format);
|
||||||
|
const endString = endTime.format(format);
|
||||||
|
|
||||||
|
return `${startString} - ${endString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeInterval;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLastRefreshHandler = useCallback(() => {
|
||||||
|
const currentTime = moment();
|
||||||
|
|
||||||
|
const lastRefresh = moment(
|
||||||
|
selectedTimeInterval === 'custom' ? minTime / 1000000 : maxTime / 1000000,
|
||||||
|
);
|
||||||
|
const duration = moment.duration(currentTime.diff(lastRefresh));
|
||||||
|
|
||||||
|
const secondsDiff = Math.floor(duration.asSeconds());
|
||||||
|
const minutedDiff = Math.floor(duration.asMinutes());
|
||||||
|
const hoursDiff = Math.floor(duration.asHours());
|
||||||
|
const daysDiff = Math.floor(duration.asDays());
|
||||||
|
const monthsDiff = Math.floor(duration.asMonths());
|
||||||
|
|
||||||
|
if (monthsDiff > 0) {
|
||||||
|
return `Last refresh -${monthsDiff} months ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (daysDiff > 0) {
|
||||||
|
return `Last refresh - ${daysDiff} days ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hoursDiff > 0) {
|
||||||
|
return `Last refresh - ${hoursDiff} hrs ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutedDiff > 0) {
|
||||||
|
return `Last refresh - ${minutedDiff} mins ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Last refresh - ${secondsDiff} sec ago`;
|
||||||
|
}, [maxTime, minTime, selectedTimeInterval]);
|
||||||
|
|
||||||
|
const onCustomDateHandler = (dateTimeRange: DateTimeRangeType): void => {
|
||||||
|
if (dateTimeRange !== null) {
|
||||||
|
const [startTimeMoment, endTimeMoment] = dateTimeRange;
|
||||||
|
if (startTimeMoment && endTimeMoment) {
|
||||||
|
setSelectedTimeInterval('custom');
|
||||||
|
setStartTime(startTimeMoment);
|
||||||
|
setEndTime(endTimeMoment);
|
||||||
|
setCustomDTPickerVisible(false);
|
||||||
|
updateTimeInterval('custom', [
|
||||||
|
startTimeMoment?.toDate().getTime() || 0,
|
||||||
|
endTimeMoment?.toDate().getTime() || 0,
|
||||||
|
]);
|
||||||
|
set('startTime', startTimeMoment.toString());
|
||||||
|
set('endTime', endTimeMoment.toString());
|
||||||
|
updateLocalStorageForRoutes('custom');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
if (metricsTimeDuration === null) {
|
||||||
|
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 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());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isOnSelectHandler.current = false;
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
location.pathname,
|
||||||
|
location.search,
|
||||||
|
startTime,
|
||||||
|
endTime,
|
||||||
|
updateTimeInterval,
|
||||||
|
selectedTimeInterval,
|
||||||
|
loading,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Form
|
||||||
|
form={form_dtselector}
|
||||||
|
layout="inline"
|
||||||
|
initialValues={{ interval: selectedTimeInterval }}
|
||||||
|
>
|
||||||
|
<DefaultSelect
|
||||||
|
onSelect={(value): void => onSelectHandler(value as Time)}
|
||||||
|
value={getInputLabel(startTime, endTime, selectedTimeInterval)}
|
||||||
|
>
|
||||||
|
{options.map(({ value, label }) => (
|
||||||
|
<Option key={value + label} value={value}>
|
||||||
|
{label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</DefaultSelect>
|
||||||
|
|
||||||
|
<FormItem hidden={refreshButtonHidden}>
|
||||||
|
<Button type="primary" onClick={onRefreshHandler}>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<RefreshTextContainer>
|
||||||
|
<Typography>{refreshText}</Typography>
|
||||||
|
</RefreshTextContainer>
|
||||||
|
|
||||||
|
<CustomDateTimeModal
|
||||||
|
visible={customDateTimeVisible}
|
||||||
|
onCreate={onCustomDateHandler}
|
||||||
|
onCancel={(): void => {
|
||||||
|
setCustomDTPickerVisible(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
updateTimeInterval: (
|
||||||
|
interval: Time,
|
||||||
|
dateTimeRange?: [number, number],
|
||||||
|
) => (dispatch: Dispatch<AppActions>) => void;
|
||||||
|
// globalTimeLoading: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (
|
||||||
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
|
): DispatchProps => ({
|
||||||
|
updateTimeInterval: bindActionCreators(UpdateTimeInterval, dispatch),
|
||||||
|
// globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = DispatchProps & RouteComponentProps;
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(withRouter(DateTimeSelection));
|
28
frontend/src/container/Header/DateTimeSelection/styles.ts
Normal file
28
frontend/src/container/Header/DateTimeSelection/styles.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { Form as FormComponent, Typography as TypographyComponent } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const Container = styled.div`
|
||||||
|
justify-content: flex-end;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Form = styled(FormComponent)`
|
||||||
|
&&& {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Typography = styled(TypographyComponent)`
|
||||||
|
&&& {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FormItem = styled(Form.Item)`
|
||||||
|
&&& {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RefreshTextContainer = styled.div`
|
||||||
|
min-height: 2rem;
|
||||||
|
`;
|
@ -1,17 +1,19 @@
|
|||||||
import { Col, Row } from 'antd';
|
import { Col } from 'antd';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import DateTimeSelector from './DateTimeSelector';
|
import ShowBreadcrumbs from './Breadcrumbs';
|
||||||
import ShowBreadcrumbs from './ShowBreadcrumbs';
|
import DateTimeSelector from './DateTimeSelection';
|
||||||
|
import { Container } from './styles';
|
||||||
|
|
||||||
const TopNav = (): JSX.Element | null => {
|
const TopNav = (): JSX.Element | null => {
|
||||||
if (history.location.pathname === ROUTES.SIGN_UP) {
|
if (history.location.pathname === ROUTES.SIGN_UP) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Container>
|
||||||
<Col span={16}>
|
<Col span={16}>
|
||||||
<ShowBreadcrumbs />
|
<ShowBreadcrumbs />
|
||||||
</Col>
|
</Col>
|
||||||
@ -19,7 +21,7 @@ const TopNav = (): JSX.Element | null => {
|
|||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<DateTimeSelector />
|
<DateTimeSelector />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
8
frontend/src/container/Header/styles.ts
Normal file
8
frontend/src/container/Header/styles.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Row } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const Container = styled(Row)`
|
||||||
|
&&& {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
`;
|
255
frontend/src/container/MetricsApplication/Tabs/Application.tsx
Normal file
255
frontend/src/container/MetricsApplication/Tabs/Application.tsx
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
|
||||||
|
import Graph from 'components/Graph';
|
||||||
|
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||||
|
import ROUTES from 'constants/routes';
|
||||||
|
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 { 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';
|
||||||
|
|
||||||
|
import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles';
|
||||||
|
import TopEndpointsTable from '../TopEndpointsTable';
|
||||||
|
import { Button } from './styles';
|
||||||
|
|
||||||
|
const Application = ({
|
||||||
|
globalLoading,
|
||||||
|
getWidget,
|
||||||
|
}: DashboardProps): JSX.Element => {
|
||||||
|
const { servicename } = useParams<{ servicename?: string }>();
|
||||||
|
const selectedTimeStamp = useRef(0);
|
||||||
|
|
||||||
|
const { topEndPoints, serviceOverview } = useSelector<AppState, MetricReducer>(
|
||||||
|
(state) => state.metrics,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onTracePopupClick = (timestamp: number): void => {
|
||||||
|
const currentTime = timestamp;
|
||||||
|
const tPlusOne = timestamp + 1 * 60 * 1000;
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams();
|
||||||
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
||||||
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
||||||
|
if (servicename) {
|
||||||
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
||||||
|
}
|
||||||
|
|
||||||
|
globalLoading();
|
||||||
|
history.push(`${ROUTES.TRACES}?${urlParams.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickhandler = async (
|
||||||
|
event: ChartEvent,
|
||||||
|
elements: ActiveElement[],
|
||||||
|
chart: Chart,
|
||||||
|
data: ChartData,
|
||||||
|
from: string,
|
||||||
|
): Promise<void> => {
|
||||||
|
if (event.native) {
|
||||||
|
const points = chart.getElementsAtEventForMode(
|
||||||
|
event.native,
|
||||||
|
'nearest',
|
||||||
|
{ intersect: true },
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
const id = `${from}_button`;
|
||||||
|
const buttonElement = document.getElementById(id);
|
||||||
|
|
||||||
|
if (points.length !== 0) {
|
||||||
|
const firstPoint = points[0];
|
||||||
|
|
||||||
|
if (data.labels) {
|
||||||
|
const time = data?.labels[firstPoint.index] as Date;
|
||||||
|
|
||||||
|
if (buttonElement) {
|
||||||
|
buttonElement.style.display = 'block';
|
||||||
|
buttonElement.style.left = `${firstPoint.element.x}px`;
|
||||||
|
buttonElement.style.top = `${firstPoint.element.y}px`;
|
||||||
|
selectedTimeStamp.current = new Date(time).getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (buttonElement && buttonElement.style.display === 'block') {
|
||||||
|
buttonElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onErrorTrackHandler = (timestamp: number): void => {
|
||||||
|
const currentTime = timestamp;
|
||||||
|
const tPlusOne = timestamp + 1 * 60 * 1000;
|
||||||
|
|
||||||
|
const urlParams = new URLSearchParams();
|
||||||
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
||||||
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
||||||
|
if (servicename) {
|
||||||
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
||||||
|
}
|
||||||
|
urlParams.set(METRICS_PAGE_QUERY_PARAM.error, 'true');
|
||||||
|
|
||||||
|
globalLoading();
|
||||||
|
history.push(`${ROUTES.TRACES}?${urlParams.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
id="Application_button"
|
||||||
|
onClick={(): void => {
|
||||||
|
onTracePopupClick(selectedTimeStamp.current);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View Traces
|
||||||
|
</Button>
|
||||||
|
<Card>
|
||||||
|
<GraphTitle>Application latency in ms</GraphTitle>
|
||||||
|
<GraphContainer>
|
||||||
|
<Graph
|
||||||
|
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||||
|
onClickhandler(ChartEvent, activeElements, chart, data, 'Application');
|
||||||
|
}}
|
||||||
|
type="line"
|
||||||
|
data={{
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
data: serviceOverview.map((e) => e.p99),
|
||||||
|
borderColor: colors[0],
|
||||||
|
label: 'p99 Latency',
|
||||||
|
showLine: true,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
spanGaps: true,
|
||||||
|
pointRadius: 1.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: serviceOverview.map((e) => e.p95),
|
||||||
|
borderColor: colors[1],
|
||||||
|
label: 'p95 Latency',
|
||||||
|
showLine: true,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
spanGaps: true,
|
||||||
|
pointRadius: 1.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: serviceOverview.map((e) => e.p50),
|
||||||
|
borderColor: colors[2],
|
||||||
|
label: 'p50 Latency',
|
||||||
|
showLine: true,
|
||||||
|
borderWidth: 1.5,
|
||||||
|
spanGaps: true,
|
||||||
|
pointRadius: 1.5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
labels: serviceOverview.map((e) => {
|
||||||
|
return new Date(e.timestamp / 1000000);
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={12}>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
id="Request_button"
|
||||||
|
onClick={(): void => {
|
||||||
|
onTracePopupClick(selectedTimeStamp.current);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View Traces
|
||||||
|
</Button>
|
||||||
|
<Card>
|
||||||
|
<GraphTitle>Request per sec</GraphTitle>
|
||||||
|
<GraphContainer>
|
||||||
|
<FullView
|
||||||
|
noDataGraph
|
||||||
|
fullViewOptions={false}
|
||||||
|
onClickHandler={(event, element, chart, data): void => {
|
||||||
|
onClickhandler(event, element, chart, data, 'Request');
|
||||||
|
}}
|
||||||
|
widget={getWidget([
|
||||||
|
{
|
||||||
|
query: `sum(rate(signoz_latency_count{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"}[2m]))`,
|
||||||
|
legend: 'Request per second',
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
id="Error_button"
|
||||||
|
onClick={(): void => {
|
||||||
|
onErrorTrackHandler(selectedTimeStamp.current);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View Traces
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<Card>
|
||||||
|
<GraphTitle>Error Percentage (%)</GraphTitle>
|
||||||
|
<GraphContainer>
|
||||||
|
<FullView
|
||||||
|
noDataGraph
|
||||||
|
fullViewOptions={false}
|
||||||
|
onClickHandler={(ChartEvent, activeElements, chart, data): void => {
|
||||||
|
onClickhandler(ChartEvent, activeElements, chart, data, 'Error');
|
||||||
|
}}
|
||||||
|
widget={getWidget([
|
||||||
|
{
|
||||||
|
query: `sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", status_code="STATUS_CODE_ERROR"}[1m]) OR rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", http_status_code=~"5.."}[1m]) OR vector(0))*100/sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"}[1m]))`,
|
||||||
|
legend: 'Error Percentage (%)',
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={12}>
|
||||||
|
<Card>
|
||||||
|
<TopEndpointsTable data={topEndPoints} />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
globalLoading: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (
|
||||||
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
|
): DispatchProps => ({
|
||||||
|
globalLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface DashboardProps extends DispatchProps {
|
||||||
|
getWidget: (query: Widgets['query']) => Widgets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(Application);
|
59
frontend/src/container/MetricsApplication/Tabs/DBCall.tsx
Normal file
59
frontend/src/container/MetricsApplication/Tabs/DBCall.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { Col } from 'antd';
|
||||||
|
import FullView from 'container/GridGraphLayout/Graph/FullView';
|
||||||
|
import React from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
|
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
|
||||||
|
|
||||||
|
const DBCall = ({ getWidget }: DBCallProps): JSX.Element => {
|
||||||
|
const { servicename } = useParams<{ servicename?: string }>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card>
|
||||||
|
<GraphTitle>Database Calls RPS</GraphTitle>
|
||||||
|
<GraphContainer>
|
||||||
|
<FullView
|
||||||
|
noDataGraph
|
||||||
|
fullViewOptions={false}
|
||||||
|
widget={getWidget([
|
||||||
|
{
|
||||||
|
query: `sum(rate(signoz_db_latency_count{service_name="${servicename}"}[1m])) by (db_system)`,
|
||||||
|
legend: '{{db_system}}',
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={12}>
|
||||||
|
<Card>
|
||||||
|
<GraphTitle>Database Calls Avg Duration (in ms)</GraphTitle>
|
||||||
|
<GraphContainer>
|
||||||
|
<FullView
|
||||||
|
noDataGraph
|
||||||
|
fullViewOptions={false}
|
||||||
|
widget={getWidget([
|
||||||
|
{
|
||||||
|
query: `sum(rate(signoz_db_latency_sum{service_name="${servicename}"}[5m]))/sum(rate(signoz_db_latency_count{service_name="${servicename}"}[5m]))`,
|
||||||
|
legend: '',
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DBCallProps {
|
||||||
|
getWidget: (query: Widgets['query']) => Widgets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DBCall;
|
97
frontend/src/container/MetricsApplication/Tabs/External.tsx
Normal file
97
frontend/src/container/MetricsApplication/Tabs/External.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { Col } from 'antd';
|
||||||
|
import FullView from 'container/GridGraphLayout/Graph/FullView';
|
||||||
|
import React from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
|
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
|
||||||
|
|
||||||
|
const External = ({ getWidget }: ExternalProps): JSX.Element => {
|
||||||
|
const { servicename } = useParams<{ servicename?: string }>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card>
|
||||||
|
<GraphTitle>External Call Error Percentage (%)</GraphTitle>
|
||||||
|
<GraphContainer>
|
||||||
|
<FullView
|
||||||
|
fullViewOptions={false}
|
||||||
|
noDataGraph
|
||||||
|
widget={getWidget([
|
||||||
|
{
|
||||||
|
query: `(sum(rate(signoz_external_call_latency_count{service_name="${servicename}", status_code="STATUS_CODE_ERROR"}[1m]) OR rate(signoz_external_call_latency_count{service_name="${servicename}", http_status_code=~"5.."}[1m]) OR vector(0)) by (http_url))*100/sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[1m])) by (http_url)`,
|
||||||
|
legend: '{{http_url}}',
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={12}>
|
||||||
|
<Card>
|
||||||
|
<GraphTitle>External Call duration</GraphTitle>
|
||||||
|
<GraphContainer>
|
||||||
|
<FullView
|
||||||
|
noDataGraph
|
||||||
|
fullViewOptions={false}
|
||||||
|
widget={getWidget([
|
||||||
|
{
|
||||||
|
query: `sum(rate(signoz_external_call_latency_sum{service_name="${servicename}"}[5m]))/sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[5m]))`,
|
||||||
|
legend: 'Average Duration',
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Card>
|
||||||
|
<GraphTitle>External Call RPS(by Address)</GraphTitle>
|
||||||
|
<GraphContainer>
|
||||||
|
<FullView
|
||||||
|
noDataGraph
|
||||||
|
fullViewOptions={false}
|
||||||
|
widget={getWidget([
|
||||||
|
{
|
||||||
|
query: `sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[5m])) by (http_url)`,
|
||||||
|
legend: '{{http_url}}',
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={12}>
|
||||||
|
<Card>
|
||||||
|
<GraphTitle>External Call duration(by Address)</GraphTitle>
|
||||||
|
<GraphContainer>
|
||||||
|
<FullView
|
||||||
|
noDataGraph
|
||||||
|
fullViewOptions={false}
|
||||||
|
widget={getWidget([
|
||||||
|
{
|
||||||
|
query: `sum(rate(signoz_external_call_latency_sum{service_name="${servicename}"}[5m])/rate(signoz_external_call_latency_count{service_name="${servicename}"}[5m])) by (http_url)`,
|
||||||
|
legend: '{{http_url}}',
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
/>
|
||||||
|
</GraphContainer>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ExternalProps {
|
||||||
|
getWidget: (query: Widgets['query']) => Widgets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default External;
|
10
frontend/src/container/MetricsApplication/Tabs/styles.ts
Normal file
10
frontend/src/container/MetricsApplication/Tabs/styles.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Button as ButtonComponent } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const Button = styled(ButtonComponent)`
|
||||||
|
&&& {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
122
frontend/src/container/MetricsApplication/TopEndpointsTable.tsx
Normal file
122
frontend/src/container/MetricsApplication/TopEndpointsTable.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { Button, Table, Tooltip } from 'antd';
|
||||||
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
|
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||||
|
import React from 'react';
|
||||||
|
import { connect, 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 => {
|
||||||
|
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
|
const history = useHistory();
|
||||||
|
const params = useParams<{ servicename: string }>();
|
||||||
|
|
||||||
|
const handleOnClick = (operation: string): void => {
|
||||||
|
const urlParams = new URLSearchParams();
|
||||||
|
const { servicename } = params;
|
||||||
|
urlParams.set(
|
||||||
|
METRICS_PAGE_QUERY_PARAM.startTime,
|
||||||
|
String(Number(minTime) / 1000000),
|
||||||
|
);
|
||||||
|
urlParams.set(
|
||||||
|
METRICS_PAGE_QUERY_PARAM.endTime,
|
||||||
|
String(Number(maxTime) / 1000000),
|
||||||
|
);
|
||||||
|
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()}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ColumnsType<DataProps> = [
|
||||||
|
{
|
||||||
|
title: 'Name',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/display-name
|
||||||
|
render: (text: string): JSX.Element => (
|
||||||
|
<Tooltip placement="topLeft" title={text}>
|
||||||
|
<Button
|
||||||
|
className="topEndpointsButton"
|
||||||
|
type="link"
|
||||||
|
onClick={(): void => handleOnClick(text)}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'P50 (in ms)',
|
||||||
|
dataIndex: 'p50',
|
||||||
|
key: 'p50',
|
||||||
|
sorter: (a: DataProps, b: DataProps): number => a.p50 - b.p50,
|
||||||
|
render: (value: number): string => (value / 1000000).toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'P95 (in ms)',
|
||||||
|
dataIndex: 'p95',
|
||||||
|
key: 'p95',
|
||||||
|
sorter: (a: DataProps, b: DataProps): number => a.p95 - b.p95,
|
||||||
|
render: (value: number): string => (value / 1000000).toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'P99 (in ms)',
|
||||||
|
dataIndex: 'p99',
|
||||||
|
key: 'p99',
|
||||||
|
sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99,
|
||||||
|
render: (value: number): string => (value / 1000000).toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Number of Calls',
|
||||||
|
dataIndex: 'numCalls',
|
||||||
|
key: 'numCalls',
|
||||||
|
sorter: (a: topEndpointListItem, b: topEndpointListItem): number =>
|
||||||
|
a.numCalls - b.numCalls,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
showHeader
|
||||||
|
title={(): string => {
|
||||||
|
return 'Top Endpoints';
|
||||||
|
}}
|
||||||
|
dataSource={props.data}
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="name"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type DataProps = topEndpointListItem;
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
globalTimeLoading: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (
|
||||||
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
|
): DispatchProps => ({
|
||||||
|
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
interface TopEndpointsTableProps extends DispatchProps {
|
||||||
|
data: topEndpointListItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(TopEndpointsTable);
|
50
frontend/src/container/MetricsApplication/index.tsx
Normal file
50
frontend/src/container/MetricsApplication/index.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { Tabs } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
|
import Application from './Tabs/Application';
|
||||||
|
import DBCall from './Tabs/DBCall';
|
||||||
|
import External from './Tabs/External';
|
||||||
|
|
||||||
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
|
const ServiceMetrics = (): JSX.Element => {
|
||||||
|
const getWidget = (query: Widgets['query']): Widgets => {
|
||||||
|
return {
|
||||||
|
description: '',
|
||||||
|
id: '',
|
||||||
|
isStacked: false,
|
||||||
|
nullZeroValues: '',
|
||||||
|
opacity: '0',
|
||||||
|
panelTypes: 'TIME_SERIES',
|
||||||
|
query: query,
|
||||||
|
queryData: {
|
||||||
|
data: [],
|
||||||
|
error: false,
|
||||||
|
errorMessage: '',
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
timePreferance: 'GLOBAL_TIME',
|
||||||
|
title: '',
|
||||||
|
stepSize: 60,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs defaultActiveKey="1">
|
||||||
|
<TabPane animated destroyInactiveTabPane tab="Application Metrics" key="1">
|
||||||
|
<Application getWidget={getWidget} />
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
<TabPane animated destroyInactiveTabPane tab="External Calls" key="2">
|
||||||
|
<External getWidget={getWidget} />
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
<TabPane animated destroyInactiveTabPane tab="Database Calls" key="3">
|
||||||
|
<DBCall getWidget={getWidget} />
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServiceMetrics;
|
46
frontend/src/container/MetricsApplication/styles.ts
Normal file
46
frontend/src/container/MetricsApplication/styles.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
Card as CardComponent,
|
||||||
|
Col as ColComponent,
|
||||||
|
Row as RowComponent,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const Card = styled(CardComponent)`
|
||||||
|
&&& {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
padding: 0;
|
||||||
|
min-height: 40vh;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Row = styled(RowComponent)`
|
||||||
|
&&& {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Col = styled(ColComponent)`
|
||||||
|
display &&& {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GraphContainer = styled.div`
|
||||||
|
min-height: 40vh;
|
||||||
|
max-height: 40vh;
|
||||||
|
|
||||||
|
div {
|
||||||
|
min-height: 40vh;
|
||||||
|
max-height: 40vh;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GraphTitle = styled(Typography)`
|
||||||
|
&&& {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
`;
|
@ -0,0 +1,48 @@
|
|||||||
|
import { Button, Typography } from 'antd';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const SkipOnBoardingModal = ({ onContinueClick }: Props): JSX.Element => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={'Setup instrumentation'}
|
||||||
|
isModalVisible={true}
|
||||||
|
closable={false}
|
||||||
|
footer={[
|
||||||
|
<Button key="submit" type="primary" onClick={onContinueClick}>
|
||||||
|
Continue without instrumentation
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<iframe
|
||||||
|
width="100%"
|
||||||
|
height="265"
|
||||||
|
src="https://www.youtube.com/embed/Ly34WBQ2640"
|
||||||
|
frameBorder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowFullScreen
|
||||||
|
></iframe>
|
||||||
|
<div>
|
||||||
|
<Typography>No instrumentation data.</Typography>
|
||||||
|
<Typography>
|
||||||
|
Please instrument your application as mentioned
|
||||||
|
<a
|
||||||
|
href={'https://signoz.io/docs/instrumentation/overview'}
|
||||||
|
target={'_blank'}
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onContinueClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SkipOnBoardingModal;
|
105
frontend/src/container/MetricsTable/index.tsx
Normal file
105
frontend/src/container/MetricsTable/index.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import Table, { ColumnsType } from 'antd/lib/table';
|
||||||
|
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 { 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 [skipOnboarding, setSkipOnboarding] = useState(
|
||||||
|
localStorage.getItem(SKIP_ONBOARDING) === 'true',
|
||||||
|
);
|
||||||
|
|
||||||
|
const { services, loading, error } = useSelector<AppState, MetricReducer>(
|
||||||
|
(state) => state.metrics,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onContinueClick = (): void => {
|
||||||
|
localStorage.setItem(SKIP_ONBOARDING, 'true');
|
||||||
|
setSkipOnboarding(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickHandler = (to: string): void => {
|
||||||
|
history.push(to);
|
||||||
|
globalTimeLoading();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
(services.length === 0 && loading === false && !skipOnboarding) ||
|
||||||
|
(loading == false && error === true)
|
||||||
|
) {
|
||||||
|
return <SkipBoardModal onContinueClick={onContinueClick} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnsType<DataProps> = [
|
||||||
|
{
|
||||||
|
title: 'Application',
|
||||||
|
dataIndex: 'serviceName',
|
||||||
|
key: 'serviceName',
|
||||||
|
// eslint-disable-next-line react/display-name
|
||||||
|
render: (text: string): JSX.Element => (
|
||||||
|
<div onClick={(): void => onClickHandler(ROUTES.APPLICATION + '/' + text)}>
|
||||||
|
<Name>{text}</Name>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'P99 latency (in ms)',
|
||||||
|
dataIndex: 'p99',
|
||||||
|
key: 'p99',
|
||||||
|
sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99,
|
||||||
|
render: (value: number): string => (value / 1000000).toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Error Rate (in %)',
|
||||||
|
dataIndex: 'errorRate',
|
||||||
|
key: 'errorRate',
|
||||||
|
sorter: (a: DataProps, b: DataProps): number => a.errorRate - b.errorRate,
|
||||||
|
render: (value: number): string => value.toFixed(2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Requests Per Second',
|
||||||
|
dataIndex: 'callRate',
|
||||||
|
key: 'callRate',
|
||||||
|
sorter: (a: DataProps, b: DataProps): number => a.callRate - b.callRate,
|
||||||
|
render: (value: number): string => value.toFixed(2),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Table
|
||||||
|
loading={loading}
|
||||||
|
dataSource={services}
|
||||||
|
columns={columns}
|
||||||
|
pagination={false}
|
||||||
|
rowKey="serviceName"
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
15
frontend/src/container/MetricsTable/styles.ts
Normal file
15
frontend/src/container/MetricsTable/styles.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Typography } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const Container = styled.div`
|
||||||
|
margin-top: 2rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Name = styled(Typography)`
|
||||||
|
&&& {
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #177ddc;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
@ -8,11 +8,7 @@ import { connect, useSelector } from 'react-redux';
|
|||||||
import { useHistory, useLocation, useParams } from 'react-router';
|
import { useHistory, useLocation, useParams } from 'react-router';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import {
|
import { ApplySettingsToPanel, ApplySettingsToPanelProps } from 'store/actions';
|
||||||
ApplySettingsToPanel,
|
|
||||||
ApplySettingsToPanelProps,
|
|
||||||
GlobalTime,
|
|
||||||
} from 'store/actions';
|
|
||||||
import {
|
import {
|
||||||
GetQueryResults,
|
GetQueryResults,
|
||||||
GetQueryResultsProps,
|
GetQueryResultsProps,
|
||||||
@ -23,6 +19,7 @@ import {
|
|||||||
} from 'store/actions/dashboard/saveDashboard';
|
} from 'store/actions/dashboard/saveDashboard';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
import DashboardReducer from 'types/reducer/dashboards';
|
import DashboardReducer from 'types/reducer/dashboards';
|
||||||
|
|
||||||
import LeftContainer from './LeftContainer';
|
import LeftContainer from './LeftContainer';
|
||||||
|
@ -7,7 +7,7 @@ import { NavLink } from 'react-router-dom';
|
|||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
import { ToggleDarkMode } from 'store/actions';
|
import { GlobalTimeLoading, ToggleDarkMode } from 'store/actions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
@ -15,7 +15,7 @@ import AppReducer from 'types/reducer/app';
|
|||||||
import menus from './menuItems';
|
import menus from './menuItems';
|
||||||
import { Logo, Sider, ThemeSwitcherWrapper } from './styles';
|
import { Logo, Sider, ThemeSwitcherWrapper } from './styles';
|
||||||
|
|
||||||
const SideNav = ({ toggleDarkMode }: Props): JSX.Element => {
|
const SideNav = ({ toggleDarkMode, globalTimeLoading }: Props): JSX.Element => {
|
||||||
const [collapsed, setCollapsed] = useState<boolean>(false);
|
const [collapsed, setCollapsed] = useState<boolean>(false);
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
@ -45,16 +45,22 @@ const SideNav = ({ toggleDarkMode }: Props): JSX.Element => {
|
|||||||
setCollapsed((collapsed) => !collapsed);
|
setCollapsed((collapsed) => !collapsed);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onClickHandler = useCallback((to: string) => {
|
const onClickHandler = useCallback(
|
||||||
|
(to: string) => {
|
||||||
|
if (pathname !== to) {
|
||||||
history.push(to);
|
history.push(to);
|
||||||
}, []);
|
globalTimeLoading();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[pathname, globalTimeLoading],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={200}>
|
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={200}>
|
||||||
<ThemeSwitcherWrapper>
|
<ThemeSwitcherWrapper>
|
||||||
<ToggleButton checked={isDarkMode} onChange={toggleTheme} />
|
<ToggleButton checked={isDarkMode} onChange={toggleTheme} />
|
||||||
</ThemeSwitcherWrapper>
|
</ThemeSwitcherWrapper>
|
||||||
<NavLink to="/">
|
<NavLink to={ROUTES.APPLICATION}>
|
||||||
<Logo src={'/signoz.svg'} alt="SigNoz" collapsed={collapsed} />
|
<Logo src={'/signoz.svg'} alt="SigNoz" collapsed={collapsed} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
@ -80,12 +86,14 @@ type mode = 'darkMode' | 'lightMode';
|
|||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
toggleDarkMode: () => void;
|
toggleDarkMode: () => void;
|
||||||
|
globalTimeLoading: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
const mapDispatchToProps = (
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
): DispatchProps => ({
|
): DispatchProps => ({
|
||||||
toggleDarkMode: bindActionCreators(ToggleDarkMode, dispatch),
|
toggleDarkMode: bindActionCreators(ToggleDarkMode, dispatch),
|
||||||
|
globalTimeLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Props = DispatchProps;
|
type Props = DispatchProps;
|
@ -20,6 +20,11 @@ const menus: SidebarMenu[] = [
|
|||||||
to: ROUTES.TRACES,
|
to: ROUTES.TRACES,
|
||||||
name: 'Traces',
|
name: 'Traces',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Icon: DashboardFilled,
|
||||||
|
to: ROUTES.ALL_DASHBOARD,
|
||||||
|
name: 'Dashboard',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
to: ROUTES.SERVICE_MAP,
|
to: ROUTES.SERVICE_MAP,
|
||||||
name: 'Service Map',
|
name: 'Service Map',
|
||||||
@ -40,11 +45,6 @@ const menus: SidebarMenu[] = [
|
|||||||
to: ROUTES.INSTRUMENTATION,
|
to: ROUTES.INSTRUMENTATION,
|
||||||
name: 'Add instrumentation',
|
name: 'Add instrumentation',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Icon: DashboardFilled,
|
|
||||||
to: ROUTES.ALL_DASHBOARD,
|
|
||||||
name: 'Dashboard',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
interface SidebarMenu {
|
interface SidebarMenu {
|
@ -7,7 +7,7 @@ import { colors } from './getRandomColor';
|
|||||||
|
|
||||||
const getChartData = ({ queryData }: GetChartDataProps): ChartData => {
|
const getChartData = ({ queryData }: GetChartDataProps): ChartData => {
|
||||||
const response = queryData.data.map(({ query, queryData, legend }) => {
|
const response = queryData.data.map(({ query, queryData, legend }) => {
|
||||||
return queryData.map((e, index) => {
|
return queryData.map((e) => {
|
||||||
const { values = [], metric } = e || {};
|
const { values = [], metric } = e || {};
|
||||||
const labelNames = getLabelName(
|
const labelNames = getLabelName(
|
||||||
metric,
|
metric,
|
||||||
@ -18,13 +18,13 @@ const getChartData = ({ queryData }: GetChartDataProps): ChartData => {
|
|||||||
const dataValue = values?.map((e) => {
|
const dataValue = values?.map((e) => {
|
||||||
const [first = 0, second = ''] = e || [];
|
const [first = 0, second = ''] = e || [];
|
||||||
return {
|
return {
|
||||||
first: new Date(parseInt(convertIntoEpoc(first), 10)),
|
first: new Date(parseInt(convertIntoEpoc(first * 1000), 10)), // converting in ms
|
||||||
second: Number(parseFloat(second).toFixed(2)),
|
second: Number(parseFloat(second).toFixed(2)),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: labelNames,
|
label: labelNames !== 'undefined' ? labelNames : '',
|
||||||
first: dataValue.map((e) => e.first),
|
first: dataValue.map((e) => e.first),
|
||||||
second: dataValue.map((e) => e.second),
|
second: dataValue.map((e) => e.second),
|
||||||
};
|
};
|
||||||
|
@ -40,7 +40,9 @@ const getLabelName = (
|
|||||||
const post = postArray.map((e) => `${e}="${metric[e]}"`).join(',');
|
const post = postArray.map((e) => `${e}="${metric[e]}"`).join(',');
|
||||||
const pre = preArray.map((e) => `${e}="${metric[e]}"`).join(',');
|
const pre = preArray.map((e) => `${e}="${metric[e]}"`).join(',');
|
||||||
|
|
||||||
const result = `${metric[keysArray[index]]}`;
|
const value = metric[keysArray[index]];
|
||||||
|
|
||||||
|
const result = `${value === undefined ? '' : value}`;
|
||||||
|
|
||||||
if (post.length === 0 && pre.length === 0) {
|
if (post.length === 0 && pre.length === 0) {
|
||||||
return result;
|
return result;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { GlobalTime } from 'store/actions';
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
import { Widgets } from 'types/api/dashboard/getAll';
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
const GetMaxMinTime = ({
|
const GetMaxMinTime = ({
|
||||||
|
15
frontend/src/lib/getTimeString.ts
Normal file
15
frontend/src/lib/getTimeString.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const getTimeString = (time: string): string => {
|
||||||
|
const timeString = time.split('.').join('').slice(0, 13);
|
||||||
|
|
||||||
|
if (timeString.length < 13) {
|
||||||
|
const lengthMissing = timeString.length - 13;
|
||||||
|
|
||||||
|
const numberZero = new Array(Math.abs(lengthMissing)).fill(0).join('');
|
||||||
|
|
||||||
|
return timeString + numberZero;
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeString;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getTimeString;
|
@ -1,118 +0,0 @@
|
|||||||
import { ActiveElement, Chart, ChartEvent } from 'chart.js';
|
|
||||||
import Graph from 'components/Graph';
|
|
||||||
import React, { useMemo, useState } from 'react';
|
|
||||||
import { metricItem } from 'store/actions/MetricsActions';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { GraphContainer } from './styles';
|
|
||||||
|
|
||||||
const ChartPopUpUnique = styled.div<{
|
|
||||||
ycoordinate: number;
|
|
||||||
xcoordinate: number;
|
|
||||||
}>`
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid rgba(219, 112, 147, 0.5);
|
|
||||||
z-index: 10;
|
|
||||||
position: absolute;
|
|
||||||
top: ${(props): number => props.ycoordinate}px;
|
|
||||||
left: ${(props): number => props.xcoordinate}px;
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 2px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PopUpElements = styled.p`
|
|
||||||
color: black;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
padding-left: 4px;
|
|
||||||
padding-right: 4px;
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface ErrorRateChartProps {
|
|
||||||
data: metricItem[];
|
|
||||||
onTracePopupClick: (props: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ErrorRateChart = ({
|
|
||||||
data,
|
|
||||||
onTracePopupClick,
|
|
||||||
}: ErrorRateChartProps): JSX.Element => {
|
|
||||||
const [state, setState] = useState({
|
|
||||||
xcoordinate: 0,
|
|
||||||
ycoordinate: 0,
|
|
||||||
showpopUp: false,
|
|
||||||
firstpoint_ts: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const gotoTracesHandler = (): void => {
|
|
||||||
onTracePopupClick(state.firstpoint_ts);
|
|
||||||
};
|
|
||||||
|
|
||||||
const data_chartJS: Chart['data'] = useMemo(() => {
|
|
||||||
return {
|
|
||||||
labels: data.map((s) => new Date(s.timestamp / 1000000)),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Error Percentage (%)',
|
|
||||||
data: data.map((s: { errorRate: any }) => s.errorRate),
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(227, 74, 51,1)', // Can also add transparency in border color
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const onClickhandler = (
|
|
||||||
event: ChartEvent,
|
|
||||||
elements: ActiveElement[],
|
|
||||||
chart: Chart,
|
|
||||||
): void => {
|
|
||||||
{
|
|
||||||
if (event.native) {
|
|
||||||
const points = chart.getElementsAtEventForMode(
|
|
||||||
event.native,
|
|
||||||
'nearest',
|
|
||||||
{ intersect: true },
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
if (points.length) {
|
|
||||||
const firstPoint = points[0];
|
|
||||||
setState({
|
|
||||||
xcoordinate: firstPoint.element.x,
|
|
||||||
ycoordinate: firstPoint.element.y,
|
|
||||||
showpopUp: true,
|
|
||||||
firstpoint_ts: data[firstPoint.index].timestamp,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{state.showpopUp && (
|
|
||||||
<ChartPopUpUnique
|
|
||||||
xcoordinate={state.xcoordinate}
|
|
||||||
ycoordinate={state.ycoordinate}
|
|
||||||
>
|
|
||||||
<PopUpElements onClick={gotoTracesHandler}>View Traces</PopUpElements>
|
|
||||||
</ChartPopUpUnique>
|
|
||||||
)}
|
|
||||||
<div style={{ textAlign: 'center' }}>Error Percentage (%)</div>
|
|
||||||
|
|
||||||
<GraphContainer>
|
|
||||||
<Graph
|
|
||||||
onClickHandler={onClickhandler}
|
|
||||||
xAxisType="timeseries"
|
|
||||||
type="line"
|
|
||||||
data={data_chartJS}
|
|
||||||
/>
|
|
||||||
</GraphContainer>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ErrorRateChart;
|
|
@ -1,105 +0,0 @@
|
|||||||
import Graph from 'components/Graph';
|
|
||||||
import { filter, uniqBy } from 'lodash';
|
|
||||||
import React from 'react';
|
|
||||||
import { withRouter } from 'react-router';
|
|
||||||
import { RouteComponentProps } from 'react-router-dom';
|
|
||||||
import { externalMetricsItem } from 'store/actions/MetricsActions';
|
|
||||||
|
|
||||||
import { GraphContainer } from '../styles';
|
|
||||||
import { borderColors } from './graphConfig';
|
|
||||||
|
|
||||||
interface ExternalApiGraphProps extends RouteComponentProps<any> {
|
|
||||||
data: externalMetricsItem[];
|
|
||||||
keyIdentifier?: string;
|
|
||||||
label?: string;
|
|
||||||
title: string;
|
|
||||||
dataIdentifier: string;
|
|
||||||
fnDataIdentifier?: (s: number | string) => number | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExternalApiGraph {
|
|
||||||
chartRef: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExternalApiGraph extends React.Component<ExternalApiGraphProps> {
|
|
||||||
constructor(props: ExternalApiGraphProps) {
|
|
||||||
super(props);
|
|
||||||
this.chartRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
xcoordinate: 0,
|
|
||||||
ycoordinate: 0,
|
|
||||||
showpopUp: false,
|
|
||||||
firstpoint_ts: 0,
|
|
||||||
// graphInfo:{}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
label,
|
|
||||||
data,
|
|
||||||
dataIdentifier,
|
|
||||||
keyIdentifier,
|
|
||||||
fnDataIdentifier,
|
|
||||||
} = this.props;
|
|
||||||
const getDataSets = () => {
|
|
||||||
if (!keyIdentifier) {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: label || '',
|
|
||||||
data: data.map((s: externalMetricsItem) =>
|
|
||||||
fnDataIdentifier
|
|
||||||
? fnDataIdentifier(s[dataIdentifier])
|
|
||||||
: s[dataIdentifier],
|
|
||||||
),
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: borderColors[0],
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
const uniq = uniqBy(data, keyIdentifier);
|
|
||||||
return uniq.map((obj: externalMetricsItem, i: number) => {
|
|
||||||
const _data = filter(
|
|
||||||
data,
|
|
||||||
(s: externalMetricsItem) => s[keyIdentifier] === obj[keyIdentifier],
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
label: obj[keyIdentifier],
|
|
||||||
data: _data.map((s: externalMetricsItem) =>
|
|
||||||
fnDataIdentifier
|
|
||||||
? fnDataIdentifier(s[dataIdentifier])
|
|
||||||
: s[dataIdentifier],
|
|
||||||
),
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: borderColors[i] || borderColors[0], // Can also add transparency in border color
|
|
||||||
borderWidth: 2,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const data_chartJS = () => {
|
|
||||||
const uniqTimestamp = uniqBy(data, 'timestamp');
|
|
||||||
|
|
||||||
return {
|
|
||||||
labels: uniqTimestamp.map(
|
|
||||||
(s: externalMetricsItem) => new Date(s.timestamp / 1000000),
|
|
||||||
),
|
|
||||||
datasets: getDataSets(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div style={{ textAlign: 'center' }}>{title}</div>
|
|
||||||
<GraphContainer>
|
|
||||||
<Graph data={data_chartJS()} xAxisType="timeseries" type="line" />
|
|
||||||
</GraphContainer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withRouter(ExternalApiGraph);
|
|
@ -1,105 +0,0 @@
|
|||||||
import { ChartOptions } from 'chart.js';
|
|
||||||
|
|
||||||
export const getOptions = (theme: string): ChartOptions => {
|
|
||||||
return {
|
|
||||||
maintainAspectRatio: true,
|
|
||||||
responsive: true,
|
|
||||||
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: '',
|
|
||||||
fontSize: 20,
|
|
||||||
position: 'top',
|
|
||||||
padding: 8,
|
|
||||||
fontFamily: 'Arial',
|
|
||||||
fontStyle: 'regular',
|
|
||||||
fontColor: theme === 'dark' ? 'rgb(200, 200, 200)' : 'rgb(20, 20, 20)',
|
|
||||||
},
|
|
||||||
|
|
||||||
legend: {
|
|
||||||
display: true,
|
|
||||||
position: 'bottom',
|
|
||||||
align: 'center',
|
|
||||||
|
|
||||||
labels: {
|
|
||||||
fontColor: theme === 'dark' ? 'rgb(200, 200, 200)' : 'rgb(20, 20, 20)',
|
|
||||||
fontSize: 10,
|
|
||||||
boxWidth: 10,
|
|
||||||
usePointStyle: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
tooltips: {
|
|
||||||
mode: 'label',
|
|
||||||
bodyFontSize: 12,
|
|
||||||
titleFontSize: 12,
|
|
||||||
|
|
||||||
callbacks: {
|
|
||||||
label: function (tooltipItem, data) {
|
|
||||||
if (typeof tooltipItem.yLabel === 'number') {
|
|
||||||
return (
|
|
||||||
data.datasets![tooltipItem.datasetIndex!].label +
|
|
||||||
' : ' +
|
|
||||||
tooltipItem.yLabel.toFixed(2)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
scales: {
|
|
||||||
yAxes: [
|
|
||||||
{
|
|
||||||
stacked: false,
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: false,
|
|
||||||
fontSize: 10,
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 6,
|
|
||||||
},
|
|
||||||
|
|
||||||
gridLines: {
|
|
||||||
// You can change the color, the dash effect, the main axe color, etc.
|
|
||||||
borderDash: [1, 4],
|
|
||||||
color: '#D3D3D3',
|
|
||||||
lineWidth: 0.25,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
xAxes: [
|
|
||||||
{
|
|
||||||
type: 'time',
|
|
||||||
// time: {
|
|
||||||
// unit: 'second'
|
|
||||||
// },
|
|
||||||
distribution: 'linear',
|
|
||||||
//'linear': data are spread according to their time (distances can vary)
|
|
||||||
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: false,
|
|
||||||
fontSize: 10,
|
|
||||||
autoSkip: true,
|
|
||||||
maxTicksLimit: 10,
|
|
||||||
},
|
|
||||||
// gridLines: false, --> not a valid option
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const borderColors = [
|
|
||||||
'#00feff',
|
|
||||||
'rgba(227, 74, 51, 1.0)',
|
|
||||||
'rgba(250,174,50,1)',
|
|
||||||
'#058b00',
|
|
||||||
'#a47f00',
|
|
||||||
'rgba(57, 255, 20, 1.0)',
|
|
||||||
'#45a1ff',
|
|
||||||
'#ffe900',
|
|
||||||
'#30e60b',
|
|
||||||
'#8000d7',
|
|
||||||
'#ededf0',
|
|
||||||
];
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './ExternalApiGraph';
|
|
@ -1,28 +0,0 @@
|
|||||||
import { ChartData, ChartOptions } from 'chart.js';
|
|
||||||
import Graph from 'components/Graph';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { GraphContainer } from '../styles';
|
|
||||||
|
|
||||||
const LatencyChart = ({
|
|
||||||
data,
|
|
||||||
onClickhandler,
|
|
||||||
}: LatencyChartProps): JSX.Element => {
|
|
||||||
return (
|
|
||||||
<GraphContainer>
|
|
||||||
<Graph
|
|
||||||
onClickHandler={onClickhandler}
|
|
||||||
xAxisType="timeseries"
|
|
||||||
type="line"
|
|
||||||
data={data}
|
|
||||||
/>
|
|
||||||
</GraphContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface LatencyChartProps {
|
|
||||||
data: ChartData;
|
|
||||||
onClickhandler: ChartOptions['onClick'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LatencyChart;
|
|
@ -1,142 +0,0 @@
|
|||||||
import {
|
|
||||||
ActiveElement,
|
|
||||||
Chart,
|
|
||||||
ChartData,
|
|
||||||
ChartEvent,
|
|
||||||
ChartOptions,
|
|
||||||
} from 'chart.js';
|
|
||||||
import React, { useMemo, useState } from 'react';
|
|
||||||
import { metricItem } from 'store/actions/MetricsActions';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { GraphTitle } from '../styles';
|
|
||||||
import LatencyLine from './LatencyLine';
|
|
||||||
|
|
||||||
const ChartPopUpUnique = styled.div<{
|
|
||||||
ycoordinate: number;
|
|
||||||
xcoordinate: number;
|
|
||||||
showPopUp: boolean;
|
|
||||||
}>`
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid rgba(219, 112, 147, 0.5);
|
|
||||||
z-index: 10;
|
|
||||||
position: absolute;
|
|
||||||
top: ${(props): number => props.ycoordinate}px;
|
|
||||||
left: ${(props): number => props.xcoordinate}px;
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 2px;
|
|
||||||
display: ${({ showPopUp }): string => (showPopUp ? 'block' : 'none')};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PopUpElements = styled.p`
|
|
||||||
color: black;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
padding-left: 4px;
|
|
||||||
padding-right: 4px;
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface LatencyLineChartProps {
|
|
||||||
data: metricItem[];
|
|
||||||
popupClickHandler: (props: any) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LatencyLineChart = ({
|
|
||||||
data,
|
|
||||||
popupClickHandler,
|
|
||||||
}: LatencyLineChartProps): JSX.Element => {
|
|
||||||
const [state, setState] = useState({
|
|
||||||
xcoordinate: 0,
|
|
||||||
ycoordinate: 0,
|
|
||||||
showpopUp: false,
|
|
||||||
firstpoint_ts: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onClickhandler: ChartOptions['onClick'] = async (
|
|
||||||
event: ChartEvent,
|
|
||||||
elements: ActiveElement[],
|
|
||||||
chart: Chart,
|
|
||||||
): Promise<void> => {
|
|
||||||
if (event.native) {
|
|
||||||
const points = chart.getElementsAtEventForMode(
|
|
||||||
event.native,
|
|
||||||
'nearest',
|
|
||||||
{ intersect: true },
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (points.length) {
|
|
||||||
const firstPoint = points[0];
|
|
||||||
|
|
||||||
setState({
|
|
||||||
xcoordinate: firstPoint.element.x,
|
|
||||||
ycoordinate: firstPoint.element.y,
|
|
||||||
showpopUp: true,
|
|
||||||
firstpoint_ts: data[firstPoint.index].timestamp,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (state.showpopUp) {
|
|
||||||
setState((state) => ({
|
|
||||||
...state,
|
|
||||||
showpopUp: false,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartData: ChartData<'line'> = useMemo(() => {
|
|
||||||
return {
|
|
||||||
labels: data.map((s) => new Date(s.timestamp / 1000000)),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'p99 Latency',
|
|
||||||
data: data.map((s) => s.p99 / 1000000), //converting latency from nano sec to ms
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(250,174,50,1)', // Can also add transparency in border color
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'p95 Latency',
|
|
||||||
data: data.map((s) => s.p95 / 1000000), //converting latency from nano sec to ms
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(227, 74, 51, 1.0)',
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'p50 Latency',
|
|
||||||
data: data.map((s) => s.p50 / 1000000), //converting latency from nano sec to ms
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(57, 255, 20, 1.0)',
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ChartPopUpUnique
|
|
||||||
xcoordinate={state.xcoordinate}
|
|
||||||
ycoordinate={state.ycoordinate}
|
|
||||||
showPopUp={state.showpopUp}
|
|
||||||
>
|
|
||||||
<PopUpElements
|
|
||||||
onClick={(): void => {
|
|
||||||
popupClickHandler(state.firstpoint_ts);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
View Traces
|
|
||||||
</PopUpElements>
|
|
||||||
</ChartPopUpUnique>
|
|
||||||
|
|
||||||
<GraphTitle>Application latency in ms</GraphTitle>
|
|
||||||
|
|
||||||
<LatencyLine data={chartData} onClickhandler={onClickhandler} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LatencyLineChart;
|
|
@ -1,127 +0,0 @@
|
|||||||
import { ActiveElement, Chart, ChartEvent } from 'chart.js';
|
|
||||||
import Graph from 'components/Graph';
|
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import history from 'lib/history';
|
|
||||||
import React, { useMemo, useState } from 'react';
|
|
||||||
import { metricItem } from 'store/actions/MetricsActions';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import { GraphContainer } from './styles';
|
|
||||||
|
|
||||||
const ChartPopUpUnique = styled.div<{
|
|
||||||
ycoordinate: number;
|
|
||||||
xcoordinate: number;
|
|
||||||
}>`
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid rgba(219, 112, 147, 0.5);
|
|
||||||
z-index: 10;
|
|
||||||
position: absolute;
|
|
||||||
top: ${(props): number => props.ycoordinate}px;
|
|
||||||
left: ${(props): number => props.xcoordinate}px;
|
|
||||||
font-size: 12px;
|
|
||||||
border-radius: 2px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PopUpElements = styled.p`
|
|
||||||
color: black;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
padding-left: 4px;
|
|
||||||
padding-right: 4px;
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface RequestRateChartProps {
|
|
||||||
data: metricItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const RequestRateChart = ({ data }: RequestRateChartProps): JSX.Element => {
|
|
||||||
const [state, setState] = useState({
|
|
||||||
xcoordinate: 0,
|
|
||||||
ycoordinate: 0,
|
|
||||||
showpopUp: false,
|
|
||||||
});
|
|
||||||
const gotoTracesHandler = (): void => {
|
|
||||||
history.push(ROUTES.TRACES);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickHandler = async (
|
|
||||||
event: ChartEvent,
|
|
||||||
elements: ActiveElement[],
|
|
||||||
charts: Chart,
|
|
||||||
): Promise<void> => {
|
|
||||||
if (event.native) {
|
|
||||||
const points = charts.getElementsAtEventForMode(
|
|
||||||
event.native,
|
|
||||||
'nearest',
|
|
||||||
{ intersect: true },
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (points.length) {
|
|
||||||
const firstPoint = points[0];
|
|
||||||
|
|
||||||
setState({
|
|
||||||
xcoordinate: firstPoint.element.x,
|
|
||||||
ycoordinate: firstPoint.element.y,
|
|
||||||
showpopUp: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (state.showpopUp) {
|
|
||||||
setState((state) => ({
|
|
||||||
...state,
|
|
||||||
showpopUp: false,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const GraphTracePopUp = (): JSX.Element | null => {
|
|
||||||
if (state.showpopUp) {
|
|
||||||
return (
|
|
||||||
<ChartPopUpUnique
|
|
||||||
xcoordinate={state.xcoordinate}
|
|
||||||
ycoordinate={state.ycoordinate}
|
|
||||||
>
|
|
||||||
<PopUpElements onClick={gotoTracesHandler}>View Traces</PopUpElements>
|
|
||||||
</ChartPopUpUnique>
|
|
||||||
);
|
|
||||||
} else return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const data_chartJS: Chart['data'] = useMemo(() => {
|
|
||||||
return {
|
|
||||||
labels: data.map((s) => new Date(s.timestamp / 1000000)),
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Request per sec',
|
|
||||||
data: data.map((s) => s.callRate),
|
|
||||||
pointRadius: 0.5,
|
|
||||||
borderColor: 'rgba(250,174,50,1)', // Can also add transparency in border color
|
|
||||||
borderWidth: 2,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
{GraphTracePopUp()}
|
|
||||||
<div style={{ textAlign: 'center' }}>Request per sec</div>
|
|
||||||
<GraphContainer>
|
|
||||||
<Graph
|
|
||||||
onClickHandler={onClickHandler}
|
|
||||||
xAxisType="timeseries"
|
|
||||||
type="line"
|
|
||||||
data={data_chartJS}
|
|
||||||
/>
|
|
||||||
</GraphContainer>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RequestRateChart;
|
|
@ -1,260 +0,0 @@
|
|||||||
import { Col, Tabs } from 'antd';
|
|
||||||
import Spinner from 'components/Spinner';
|
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import history from 'lib/history';
|
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import { GlobalTime, updateTimeInterval } from 'store/actions';
|
|
||||||
import {
|
|
||||||
dbOverviewMetricsItem,
|
|
||||||
externalErrCodeMetricsItem,
|
|
||||||
externalMetricsAvgDurationItem,
|
|
||||||
externalMetricsItem,
|
|
||||||
getInitialMerticDataProps,
|
|
||||||
metricItem,
|
|
||||||
topEndpointListItem,
|
|
||||||
} from 'store/actions/MetricsActions';
|
|
||||||
import {
|
|
||||||
getDbOverViewMetrics,
|
|
||||||
getExternalAvgDurationMetrics,
|
|
||||||
getExternalErrCodeMetrics,
|
|
||||||
getExternalMetrics,
|
|
||||||
getInitialMerticData,
|
|
||||||
getServicesMetrics,
|
|
||||||
getTopEndpoints,
|
|
||||||
} from 'store/actions/MetricsActions';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
|
|
||||||
import ErrorRateChart from '../ErrorRateChart';
|
|
||||||
import ExternalApiGraph from '../ExternalApi';
|
|
||||||
import LatencyLineChart from '../LatencyLineChart';
|
|
||||||
import RequestRateChart from '../RequestRateChart';
|
|
||||||
import TopEndpointsTable from '../TopEndpointsTable';
|
|
||||||
import { Card, Row } from './styles';
|
|
||||||
const { TabPane } = Tabs;
|
|
||||||
|
|
||||||
const _ServiceMetrics = (props: ServicesMetricsProps): JSX.Element => {
|
|
||||||
const { servicename } = useParams<{ servicename?: string }>();
|
|
||||||
const { globalTime, getInitialMerticData } = props;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (servicename !== undefined) {
|
|
||||||
getInitialMerticData({
|
|
||||||
globalTime: globalTime,
|
|
||||||
serviceName: servicename,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [getInitialMerticData, servicename, globalTime]);
|
|
||||||
|
|
||||||
const onTracePopupClick = (timestamp: number): void => {
|
|
||||||
const currentTime = timestamp / 1000000;
|
|
||||||
const tPlusOne = timestamp / 1000000 + 1 * 60 * 1000;
|
|
||||||
|
|
||||||
updateTimeInterval('custom', [currentTime, tPlusOne]); // updateTimeInterval takes second range in ms -- give -5 min to selected time,
|
|
||||||
|
|
||||||
const urlParams = new URLSearchParams();
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
|
||||||
if (servicename) {
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
|
||||||
}
|
|
||||||
|
|
||||||
history.push(`${ROUTES.TRACES}?${urlParams.toString()}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onErrTracePopupClick = (timestamp: number): void => {
|
|
||||||
const currentTime = timestamp / 1000000;
|
|
||||||
const tPlusOne = timestamp / 1000000 + 1 * 60 * 1000;
|
|
||||||
|
|
||||||
updateTimeInterval('custom', [currentTime, tPlusOne]); // updateTimeInterval takes second range in ms -- give -5 min to selected time,
|
|
||||||
|
|
||||||
const urlParams = new URLSearchParams();
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.startTime, currentTime.toString());
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.endTime, tPlusOne.toString());
|
|
||||||
if (servicename) {
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
|
||||||
}
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.error, 'true');
|
|
||||||
|
|
||||||
history.push(`${ROUTES.TRACES}?${urlParams.toString()}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (props.loading) {
|
|
||||||
return <Spinner tip="Loading..." height="100vh" size="large" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tabs defaultActiveKey="1">
|
|
||||||
<TabPane tab="Application Metrics" key="1">
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<LatencyLineChart
|
|
||||||
data={props.serviceMetrics}
|
|
||||||
popupClickHandler={onTracePopupClick}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<RequestRateChart data={props.serviceMetrics} />
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<ErrorRateChart
|
|
||||||
onTracePopupClick={onErrTracePopupClick}
|
|
||||||
data={props.serviceMetrics}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<TopEndpointsTable data={props.topEndpointsList} />
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</TabPane>
|
|
||||||
|
|
||||||
<TabPane tab="External Calls" key="2">
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<ExternalApiGraph
|
|
||||||
title="External Call Error Percentage (%)"
|
|
||||||
keyIdentifier="externalHttpUrl"
|
|
||||||
dataIdentifier="errorRate"
|
|
||||||
data={props.externalErrCodeMetrics}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<ExternalApiGraph
|
|
||||||
label="Average Duration"
|
|
||||||
title="External Call duration"
|
|
||||||
dataIdentifier="avgDuration"
|
|
||||||
fnDataIdentifier={(s) => Number(s) / 1000000}
|
|
||||||
data={props.externalAvgDurationMetrics}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<ExternalApiGraph
|
|
||||||
title="External Call RPS(by Address)"
|
|
||||||
keyIdentifier="externalHttpUrl"
|
|
||||||
dataIdentifier="callRate"
|
|
||||||
data={props.externalMetrics}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<ExternalApiGraph
|
|
||||||
title="External Call duration(by Address)"
|
|
||||||
keyIdentifier="externalHttpUrl"
|
|
||||||
dataIdentifier="avgDuration"
|
|
||||||
fnDataIdentifier={(s) => Number(s) / 1000000}
|
|
||||||
data={props.externalMetrics}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</TabPane>
|
|
||||||
|
|
||||||
<TabPane tab="Database Calls" key="3">
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<ExternalApiGraph
|
|
||||||
title="Database Calls RPS"
|
|
||||||
keyIdentifier="dbSystem"
|
|
||||||
dataIdentifier="callRate"
|
|
||||||
data={props.dbOverviewMetrics}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col span={12}>
|
|
||||||
<Card>
|
|
||||||
<ExternalApiGraph
|
|
||||||
label="Average Duration"
|
|
||||||
title="Database Calls Avg Duration (in ms)"
|
|
||||||
dataIdentifier="avgDuration"
|
|
||||||
fnDataIdentifier={(s) => Number(s) / 1000000}
|
|
||||||
data={props.dbOverviewMetrics}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ServicesMetricsProps {
|
|
||||||
serviceMetrics: metricItem[];
|
|
||||||
dbOverviewMetrics: dbOverviewMetricsItem[];
|
|
||||||
getServicesMetrics: () => void;
|
|
||||||
getExternalMetrics: () => void;
|
|
||||||
getExternalErrCodeMetrics: () => void;
|
|
||||||
getExternalAvgDurationMetrics: () => void;
|
|
||||||
getDbOverViewMetrics: () => void;
|
|
||||||
externalMetrics: externalMetricsItem[];
|
|
||||||
topEndpointsList: topEndpointListItem[];
|
|
||||||
externalAvgDurationMetrics: externalMetricsAvgDurationItem[];
|
|
||||||
externalErrCodeMetrics: externalErrCodeMetricsItem[];
|
|
||||||
getTopEndpoints: () => void;
|
|
||||||
globalTime: GlobalTime;
|
|
||||||
updateTimeInterval: () => void;
|
|
||||||
getInitialMerticData: (props: getInitialMerticDataProps) => void;
|
|
||||||
loading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (
|
|
||||||
state: AppState,
|
|
||||||
): {
|
|
||||||
serviceMetrics: metricItem[];
|
|
||||||
topEndpointsList: topEndpointListItem[];
|
|
||||||
externalAvgDurationMetrics: externalMetricsAvgDurationItem[];
|
|
||||||
externalErrCodeMetrics: externalErrCodeMetricsItem[];
|
|
||||||
externalMetrics: externalMetricsItem[];
|
|
||||||
dbOverviewMetrics: dbOverviewMetricsItem[];
|
|
||||||
globalTime: GlobalTime;
|
|
||||||
loading: boolean;
|
|
||||||
} => {
|
|
||||||
return {
|
|
||||||
externalErrCodeMetrics: state.metricsData.externalErrCodeMetricsItem,
|
|
||||||
serviceMetrics: state.metricsData.metricItems,
|
|
||||||
topEndpointsList: state.metricsData.topEndpointListItem,
|
|
||||||
externalMetrics: state.metricsData.externalMetricsItem,
|
|
||||||
globalTime: state.globalTime,
|
|
||||||
dbOverviewMetrics: state.metricsData.dbOverviewMetricsItem,
|
|
||||||
externalAvgDurationMetrics: state.metricsData.externalMetricsAvgDurationItem,
|
|
||||||
loading: state.metricsData.loading,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ServiceMetrics = connect(mapStateToProps, {
|
|
||||||
getServicesMetrics: getServicesMetrics,
|
|
||||||
getExternalMetrics: getExternalMetrics,
|
|
||||||
getExternalErrCodeMetrics: getExternalErrCodeMetrics,
|
|
||||||
getExternalAvgDurationMetrics: getExternalAvgDurationMetrics,
|
|
||||||
getTopEndpoints: getTopEndpoints,
|
|
||||||
updateTimeInterval: updateTimeInterval,
|
|
||||||
getDbOverViewMetrics: getDbOverViewMetrics,
|
|
||||||
getInitialMerticData: getInitialMerticData,
|
|
||||||
})(_ServiceMetrics);
|
|
@ -1,18 +0,0 @@
|
|||||||
import { Card as CardComponent, Row as RowComponent } from 'antd';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
export const Card = styled(CardComponent)`
|
|
||||||
&&& {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Row = styled(RowComponent)`
|
|
||||||
&&& {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
`;
|
|
@ -1 +0,0 @@
|
|||||||
export { ServiceMetrics as default } from './ServiceMetrics';
|
|
@ -1,186 +0,0 @@
|
|||||||
import { Button, Space, Table } from 'antd';
|
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
|
||||||
import Modal from 'components/Modal';
|
|
||||||
import Spinner from 'components/Spinner';
|
|
||||||
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { NavLink } from 'react-router-dom';
|
|
||||||
import { getServicesList, GlobalTime } from 'store/actions';
|
|
||||||
import { servicesListItem } from 'store/actions/MetricsActions';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
|
|
||||||
import { Wrapper } from './styles';
|
|
||||||
|
|
||||||
const _ServicesTable = (props: ServicesTableProps): JSX.Element => {
|
|
||||||
const [initialDataFetch, setDataFetched] = useState(false);
|
|
||||||
const [errorObject, setErrorObject] = useState({
|
|
||||||
message: '',
|
|
||||||
isError: false,
|
|
||||||
});
|
|
||||||
const isEmptyServiceList =
|
|
||||||
!initialDataFetch && props.servicesList.length === 0;
|
|
||||||
const refetchFromBackend = isEmptyServiceList || errorObject.isError;
|
|
||||||
const [skipOnboarding, setSkipOnboarding] = useState(
|
|
||||||
localStorage.getItem(SKIP_ONBOARDING) === 'true',
|
|
||||||
);
|
|
||||||
|
|
||||||
const onContinueClick = (): void => {
|
|
||||||
localStorage.setItem(SKIP_ONBOARDING, 'true');
|
|
||||||
setSkipOnboarding(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const { globalTime, getServicesList } = props;
|
|
||||||
|
|
||||||
const getApiServiceData = useCallback(() => {
|
|
||||||
getServicesList(globalTime)
|
|
||||||
.then(() => {
|
|
||||||
setDataFetched(true);
|
|
||||||
setErrorObject({ message: '', isError: false });
|
|
||||||
})
|
|
||||||
.catch((e: string) => {
|
|
||||||
setErrorObject({ message: e, isError: true });
|
|
||||||
setDataFetched(true);
|
|
||||||
});
|
|
||||||
}, [globalTime, getServicesList]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getApiServiceData();
|
|
||||||
}, [globalTime, getApiServiceData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.servicesList.length > 1) {
|
|
||||||
localStorage.removeItem(SKIP_ONBOARDING);
|
|
||||||
}
|
|
||||||
}, [props.servicesList, errorObject]);
|
|
||||||
|
|
||||||
if (!initialDataFetch) {
|
|
||||||
return <Spinner height="90vh" size="large" tip="Fetching data..." />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refetchFromBackend && !skipOnboarding) {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={'Setup instrumentation'}
|
|
||||||
isModalVisible={true}
|
|
||||||
closable={false}
|
|
||||||
footer={[
|
|
||||||
<Button key="submit" type="primary" onClick={onContinueClick}>
|
|
||||||
Continue without instrumentation
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<iframe
|
|
||||||
width="100%"
|
|
||||||
height="265"
|
|
||||||
src="https://www.youtube.com/embed/Ly34WBQ2640"
|
|
||||||
frameBorder="0"
|
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
||||||
allowFullScreen
|
|
||||||
></iframe>
|
|
||||||
<div>
|
|
||||||
No instrumentation data.
|
|
||||||
<br />
|
|
||||||
Please instrument your application as mentioned{' '}
|
|
||||||
<a
|
|
||||||
href={'https://signoz.io/docs/instrumentation/overview'}
|
|
||||||
target={'_blank'}
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
here
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns: ColumnsType<DataProps> = [
|
|
||||||
{
|
|
||||||
title: 'Application',
|
|
||||||
dataIndex: 'serviceName',
|
|
||||||
key: 'serviceName',
|
|
||||||
// eslint-disable-next-line react/display-name
|
|
||||||
render: (text: string): JSX.Element => (
|
|
||||||
<NavLink
|
|
||||||
style={{ textTransform: 'capitalize' }}
|
|
||||||
to={ROUTES.APPLICATION + '/' + text}
|
|
||||||
>
|
|
||||||
<strong>{text}</strong>
|
|
||||||
</NavLink>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'P99 latency (in ms)',
|
|
||||||
dataIndex: 'p99',
|
|
||||||
key: 'p99',
|
|
||||||
sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99,
|
|
||||||
render: (value: number): string => (value / 1000000).toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Error Rate (in %)',
|
|
||||||
dataIndex: 'errorRate',
|
|
||||||
key: 'errorRate',
|
|
||||||
sorter: (a: DataProps, b: DataProps): number => a.errorRate - b.errorRate,
|
|
||||||
render: (value: number): string => value.toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Requests Per Second',
|
|
||||||
dataIndex: 'callRate',
|
|
||||||
key: 'callRate',
|
|
||||||
sorter: (a: DataProps, b: DataProps): number => a.callRate - b.callRate,
|
|
||||||
render: (value: number): string => value.toFixed(2),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<Table
|
|
||||||
dataSource={props.servicesList}
|
|
||||||
columns={columns}
|
|
||||||
pagination={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{props.servicesList[0] !== undefined &&
|
|
||||||
props.servicesList[0].numCalls === 0 && (
|
|
||||||
<Space
|
|
||||||
style={{ width: '100%', margin: '40px 0', justifyContent: 'center' }}
|
|
||||||
>
|
|
||||||
No applications present. Please add instrumentation (follow this
|
|
||||||
<a
|
|
||||||
href={'https://signoz.io/docs/instrumentation/overview'}
|
|
||||||
target={'_blank'}
|
|
||||||
style={{ marginLeft: 3 }}
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
guide
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type DataProps = servicesListItem;
|
|
||||||
|
|
||||||
interface ServicesTableProps {
|
|
||||||
servicesList: servicesListItem[];
|
|
||||||
getServicesList: (props: GlobalTime) => Promise<void>;
|
|
||||||
globalTime: GlobalTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (
|
|
||||||
state: AppState,
|
|
||||||
): { servicesList: servicesListItem[]; globalTime: GlobalTime } => {
|
|
||||||
return {
|
|
||||||
servicesList: state.metricsData.serviceList,
|
|
||||||
globalTime: state.globalTime,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ServicesTable = connect(mapStateToProps, {
|
|
||||||
getServicesList: getServicesList,
|
|
||||||
})(_ServicesTable);
|
|
@ -1,5 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
export const Wrapper = styled.div`
|
|
||||||
padding: 40px;
|
|
||||||
`;
|
|
@ -1 +0,0 @@
|
|||||||
export { ServicesTable as default } from './ServiceTable';
|
|
@ -1,12 +0,0 @@
|
|||||||
@media only screen and (min-width: 768px) {
|
|
||||||
.topEndpointsButton {
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topEndpointsButton span {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: 120px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,133 +0,0 @@
|
|||||||
import './TopEndpointsTable.css';
|
|
||||||
|
|
||||||
import { Button, Table, Tooltip } from 'antd';
|
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
|
||||||
import { GlobalTime } from 'store/actions';
|
|
||||||
import { topEndpointListItem } from 'store/actions/MetricsActions';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const Wrapper = styled.div`
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
padding-left: 8px;
|
|
||||||
padding-right: 8px;
|
|
||||||
@media only screen and (max-width: 767px) {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.ant-table table {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.ant-table tfoot > tr > td,
|
|
||||||
.ant-table tfoot > tr > th,
|
|
||||||
.ant-table-tbody > tr > td,
|
|
||||||
.ant-table-thead > tr > th {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.ant-table-column-sorters {
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface TopEndpointsTableProps {
|
|
||||||
data: topEndpointListItem[];
|
|
||||||
globalTime: GlobalTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _TopEndpointsTable = (props: TopEndpointsTableProps) => {
|
|
||||||
const history = useHistory();
|
|
||||||
const params = useParams<{ servicename: string }>();
|
|
||||||
const handleOnClick = (operation: string) => {
|
|
||||||
const urlParams = new URLSearchParams();
|
|
||||||
const { servicename } = params;
|
|
||||||
const { maxTime, minTime } = props.globalTime;
|
|
||||||
urlParams.set(
|
|
||||||
METRICS_PAGE_QUERY_PARAM.startTime,
|
|
||||||
String(Number(minTime) / 1000000),
|
|
||||||
);
|
|
||||||
urlParams.set(
|
|
||||||
METRICS_PAGE_QUERY_PARAM.endTime,
|
|
||||||
String(Number(maxTime) / 1000000),
|
|
||||||
);
|
|
||||||
if (servicename) {
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.service, servicename);
|
|
||||||
}
|
|
||||||
urlParams.set(METRICS_PAGE_QUERY_PARAM.operation, operation);
|
|
||||||
history.push(`/traces?${urlParams.toString()}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns: any = [
|
|
||||||
{
|
|
||||||
title: 'Name',
|
|
||||||
dataIndex: 'name',
|
|
||||||
key: 'name',
|
|
||||||
|
|
||||||
render: (text: string) => (
|
|
||||||
<Tooltip placement="topLeft" title={text}>
|
|
||||||
<Button
|
|
||||||
className="topEndpointsButton"
|
|
||||||
type="link"
|
|
||||||
onClick={() => handleOnClick(text)}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'P50 (in ms)',
|
|
||||||
dataIndex: 'p50',
|
|
||||||
key: 'p50',
|
|
||||||
sorter: (a: any, b: any) => a.p50 - b.p50,
|
|
||||||
// sortDirections: ['descend', 'ascend'],
|
|
||||||
render: (value: number) => (value / 1000000).toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'P95 (in ms)',
|
|
||||||
dataIndex: 'p95',
|
|
||||||
key: 'p95',
|
|
||||||
sorter: (a: any, b: any) => a.p95 - b.p95,
|
|
||||||
// sortDirections: ['descend', 'ascend'],
|
|
||||||
render: (value: number) => (value / 1000000).toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'P99 (in ms)',
|
|
||||||
dataIndex: 'p99',
|
|
||||||
key: 'p99',
|
|
||||||
sorter: (a: any, b: any) => a.p99 - b.p99,
|
|
||||||
// sortDirections: ['descend', 'ascend'],
|
|
||||||
render: (value: number) => (value / 1000000).toFixed(2),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Number of Calls',
|
|
||||||
dataIndex: 'numCalls',
|
|
||||||
key: 'numCalls',
|
|
||||||
sorter: (a: any, b: any) => a.numCalls - b.numCalls,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
<h6> Top Endpoints</h6>
|
|
||||||
<Table dataSource={props.data} columns={columns} pagination={false} />
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = (
|
|
||||||
state: AppState,
|
|
||||||
): {
|
|
||||||
globalTime: GlobalTime;
|
|
||||||
} => {
|
|
||||||
return { globalTime: state.globalTime };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TopEndpointsTable = connect(
|
|
||||||
mapStateToProps,
|
|
||||||
null,
|
|
||||||
)(_TopEndpointsTable);
|
|
||||||
|
|
||||||
export default TopEndpointsTable;
|
|
@ -1,12 +0,0 @@
|
|||||||
import { Typography } from 'antd';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
export const GraphContainer = styled.div`
|
|
||||||
min-height: 27vh;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const GraphTitle = styled(Typography)`
|
|
||||||
&&& {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
`;
|
|
@ -1,349 +0,0 @@
|
|||||||
import { Button, Form, Select as DefaultSelect, Space } from 'antd';
|
|
||||||
import FormItem from 'antd/lib/form/FormItem';
|
|
||||||
import { LOCAL_STORAGE } from 'constants/localStorage';
|
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import history from 'lib/history';
|
|
||||||
import { cloneDeep } from 'lodash';
|
|
||||||
import moment from 'moment';
|
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { GlobalTime, updateTimeInterval } from 'store/actions';
|
|
||||||
import { DateTimeRangeType } from 'store/actions';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import {
|
|
||||||
DefaultOptionsBasedOnRoute,
|
|
||||||
Options,
|
|
||||||
ServiceMapOptions,
|
|
||||||
} from './config';
|
|
||||||
import CustomDateTimeModal from './CustomDateTimeModal';
|
|
||||||
import { getLocalStorageRouteKey } from './utils';
|
|
||||||
const { Option } = DefaultSelect;
|
|
||||||
|
|
||||||
const DateTimeWrapper = styled.div`
|
|
||||||
margin-top: 20px;
|
|
||||||
justify-content: flex-end !important;
|
|
||||||
`;
|
|
||||||
interface DateTimeSelectorProps {
|
|
||||||
currentpath?: string;
|
|
||||||
updateTimeInterval: (
|
|
||||||
interval: string,
|
|
||||||
datetimeRange?: [number, number],
|
|
||||||
) => void;
|
|
||||||
globalTime: GlobalTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This components is mounted all the time. Use event listener to track changes.
|
|
||||||
*/
|
|
||||||
const _DateTimeSelector = (props: DateTimeSelectorProps): JSX.Element => {
|
|
||||||
const location = useLocation();
|
|
||||||
const LocalStorageRouteKey: string = getLocalStorageRouteKey(
|
|
||||||
location.pathname,
|
|
||||||
);
|
|
||||||
const { globalTime, updateTimeInterval } = props;
|
|
||||||
|
|
||||||
const timeDurationInLocalStorage = useMemo(() => {
|
|
||||||
return (
|
|
||||||
JSON.parse(localStorage.getItem(LOCAL_STORAGE.METRICS_TIME_IN_DURATION)) ||
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const options =
|
|
||||||
location.pathname === ROUTES.SERVICE_MAP ? ServiceMapOptions : Options;
|
|
||||||
let defaultTime = DefaultOptionsBasedOnRoute[LocalStorageRouteKey]
|
|
||||||
? DefaultOptionsBasedOnRoute[LocalStorageRouteKey]
|
|
||||||
: DefaultOptionsBasedOnRoute.default;
|
|
||||||
|
|
||||||
if (timeDurationInLocalStorage[LocalStorageRouteKey]) {
|
|
||||||
defaultTime = timeDurationInLocalStorage[LocalStorageRouteKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDefaultTime = useCallback(() => {
|
|
||||||
if (DefaultOptionsBasedOnRoute[LocalStorageRouteKey]) {
|
|
||||||
return DefaultOptionsBasedOnRoute[LocalStorageRouteKey];
|
|
||||||
}
|
|
||||||
if (timeDurationInLocalStorage[LocalStorageRouteKey]) {
|
|
||||||
return timeDurationInLocalStorage[LocalStorageRouteKey];
|
|
||||||
}
|
|
||||||
return DefaultOptionsBasedOnRoute.default;
|
|
||||||
}, [LocalStorageRouteKey, timeDurationInLocalStorage]);
|
|
||||||
|
|
||||||
const [currentLocalStorageRouteKey, setCurrentLocalStorageRouteKey] = useState(
|
|
||||||
LocalStorageRouteKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [customDTPickerVisible, setCustomDTPickerVisible] = useState(false);
|
|
||||||
const [timeInterval, setTimeInterval] = useState(getDefaultTime());
|
|
||||||
const [startTime, setStartTime] = useState<moment.Moment | null>(null);
|
|
||||||
const [endTime, setEndTime] = useState<moment.Moment | null>(null);
|
|
||||||
const [refreshButtonHidden, setRefreshButtonHidden] = useState(false);
|
|
||||||
const [refreshText, setRefreshText] = useState('');
|
|
||||||
const [refreshButtonClick, setRefreshButtonClick] = useState(0);
|
|
||||||
const [form_dtselector] = Form.useForm();
|
|
||||||
|
|
||||||
const setToLocalStorage = useCallback(
|
|
||||||
(val: string) => {
|
|
||||||
let timeDurationInLocalStorageObj = cloneDeep(timeDurationInLocalStorage);
|
|
||||||
if (timeDurationInLocalStorageObj) {
|
|
||||||
timeDurationInLocalStorageObj[LocalStorageRouteKey] = val;
|
|
||||||
} else {
|
|
||||||
timeDurationInLocalStorageObj = {
|
|
||||||
[LocalStorageRouteKey]: val,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
window.localStorage.setItem(
|
|
||||||
LOCAL_STORAGE.METRICS_TIME_IN_DURATION,
|
|
||||||
JSON.stringify(timeDurationInLocalStorageObj),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[LocalStorageRouteKey, timeDurationInLocalStorage],
|
|
||||||
);
|
|
||||||
|
|
||||||
const setMetricsTimeInterval = useCallback(
|
|
||||||
(value: string) => {
|
|
||||||
updateTimeInterval(value);
|
|
||||||
setTimeInterval(value);
|
|
||||||
setEndTime(null);
|
|
||||||
setStartTime(null);
|
|
||||||
setToLocalStorage(value);
|
|
||||||
},
|
|
||||||
[setToLocalStorage, updateTimeInterval],
|
|
||||||
);
|
|
||||||
|
|
||||||
const setCustomTime = useCallback(
|
|
||||||
(startTime: moment.Moment, endTime: moment.Moment) => {
|
|
||||||
updateTimeInterval('custom', [startTime.valueOf(), endTime.valueOf()]);
|
|
||||||
setEndTime(endTime);
|
|
||||||
setStartTime(startTime);
|
|
||||||
},
|
|
||||||
[updateTimeInterval],
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateTimeOnQueryParamChange = useCallback(() => {
|
|
||||||
const urlParams = new URLSearchParams(location.search);
|
|
||||||
const intervalInQueryParam = urlParams.get(METRICS_PAGE_QUERY_PARAM.interval);
|
|
||||||
const startTimeString = urlParams.get(METRICS_PAGE_QUERY_PARAM.startTime);
|
|
||||||
const endTimeString = urlParams.get(METRICS_PAGE_QUERY_PARAM.endTime);
|
|
||||||
|
|
||||||
// first pref: handle both startTime and endTime
|
|
||||||
if (
|
|
||||||
startTimeString &&
|
|
||||||
startTimeString.length > 0 &&
|
|
||||||
endTimeString &&
|
|
||||||
endTimeString.length > 0
|
|
||||||
) {
|
|
||||||
const startTime = moment(Number(startTimeString));
|
|
||||||
const endTime = moment(Number(endTimeString));
|
|
||||||
setCustomTime(startTime, endTime);
|
|
||||||
} else if (currentLocalStorageRouteKey !== LocalStorageRouteKey) {
|
|
||||||
setMetricsTimeInterval(defaultTime);
|
|
||||||
setCurrentLocalStorageRouteKey(LocalStorageRouteKey);
|
|
||||||
}
|
|
||||||
// first pref: handle intervalInQueryParam
|
|
||||||
else if (intervalInQueryParam) {
|
|
||||||
setMetricsTimeInterval(intervalInQueryParam);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
LocalStorageRouteKey,
|
|
||||||
currentLocalStorageRouteKey,
|
|
||||||
setCustomTime,
|
|
||||||
defaultTime,
|
|
||||||
setMetricsTimeInterval,
|
|
||||||
location,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMetricsTimeInterval(defaultTime);
|
|
||||||
}, [defaultTime, setMetricsTimeInterval]);
|
|
||||||
|
|
||||||
// On URL Change
|
|
||||||
useEffect(() => {
|
|
||||||
updateTimeOnQueryParamChange();
|
|
||||||
}, [location, updateTimeOnQueryParamChange]);
|
|
||||||
|
|
||||||
const updateUrlForTimeInterval = (value: string): void => {
|
|
||||||
const preSearch = new URLSearchParams(location.search);
|
|
||||||
|
|
||||||
const widgetId = preSearch.get('widgetId');
|
|
||||||
const graphType = preSearch.get('graphType');
|
|
||||||
|
|
||||||
let result = '';
|
|
||||||
|
|
||||||
if (widgetId !== null) {
|
|
||||||
result = result + `&widgetId=${widgetId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (graphType !== null) {
|
|
||||||
result = result + `&graphType=${graphType}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
history.push({
|
|
||||||
search: `?${METRICS_PAGE_QUERY_PARAM.interval}=${value}${result}`,
|
|
||||||
}); //pass time in URL query param for all choices except custom in datetime picker
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateUrlForCustomTime = (
|
|
||||||
startTime: moment.Moment,
|
|
||||||
endTime: moment.Moment,
|
|
||||||
): void => {
|
|
||||||
const preSearch = new URLSearchParams(location.search);
|
|
||||||
|
|
||||||
const widgetId = preSearch.get('widgetId');
|
|
||||||
const graphType = preSearch.get('graphType');
|
|
||||||
|
|
||||||
let result = '';
|
|
||||||
|
|
||||||
if (widgetId !== null) {
|
|
||||||
result = result + `&widgetId=${widgetId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (graphType !== null) {
|
|
||||||
result = result + `&graphType=${graphType}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
history.push(
|
|
||||||
`?${METRICS_PAGE_QUERY_PARAM.startTime}=${startTime.valueOf()}&${
|
|
||||||
METRICS_PAGE_QUERY_PARAM.endTime
|
|
||||||
}=${endTime.valueOf()}${result}`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnSelect = (value: string): void => {
|
|
||||||
if (value === 'custom') {
|
|
||||||
setCustomDTPickerVisible(true);
|
|
||||||
} else {
|
|
||||||
updateUrlForTimeInterval(value);
|
|
||||||
setRefreshButtonHidden(false); // for normal intervals, show refresh button
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//function called on clicking apply in customDateTimeModal
|
|
||||||
const handleCustomDate = (dateTimeRange: DateTimeRangeType): void => {
|
|
||||||
// pass values in ms [minTime, maxTime]
|
|
||||||
if (
|
|
||||||
dateTimeRange !== null &&
|
|
||||||
dateTimeRange !== undefined &&
|
|
||||||
dateTimeRange[0] !== null &&
|
|
||||||
dateTimeRange[1] !== null
|
|
||||||
) {
|
|
||||||
const startTime = dateTimeRange[0].valueOf();
|
|
||||||
const endTime = dateTimeRange[1].valueOf();
|
|
||||||
|
|
||||||
updateUrlForCustomTime(moment(startTime), moment(endTime));
|
|
||||||
//setting globaltime
|
|
||||||
setRefreshButtonHidden(true);
|
|
||||||
form_dtselector.setFieldsValue({
|
|
||||||
interval:
|
|
||||||
dateTimeRange[0].format('YYYY/MM/DD HH:mm') +
|
|
||||||
'-' +
|
|
||||||
dateTimeRange[1].format('YYYY/MM/DD HH:mm'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setCustomDTPickerVisible(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeSinceLastRefresh = useCallback(() => {
|
|
||||||
const currentTime = moment();
|
|
||||||
const lastRefresh = moment(globalTime.maxTime / 1000000);
|
|
||||||
const duration = moment.duration(currentTime.diff(lastRefresh));
|
|
||||||
|
|
||||||
const secondsDiff = Math.floor(duration.asSeconds());
|
|
||||||
const minutedDiff = Math.floor(duration.asMinutes());
|
|
||||||
const hoursDiff = Math.floor(duration.asHours());
|
|
||||||
|
|
||||||
if (hoursDiff > 0) {
|
|
||||||
return `Last refresh - ${hoursDiff} hrs ago`;
|
|
||||||
} else if (minutedDiff > 0) {
|
|
||||||
return `Last refresh - ${minutedDiff} mins ago`;
|
|
||||||
}
|
|
||||||
return `Last refresh - ${secondsDiff} sec ago`;
|
|
||||||
}, [globalTime]);
|
|
||||||
|
|
||||||
const handleRefresh = (): void => {
|
|
||||||
setRefreshButtonClick(refreshButtonClick + 1);
|
|
||||||
setMetricsTimeInterval(timeInterval);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setRefreshText('');
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setRefreshText(timeSinceLastRefresh());
|
|
||||||
}, 2000);
|
|
||||||
return (): void => {
|
|
||||||
clearInterval(interval);
|
|
||||||
};
|
|
||||||
}, [refreshButtonClick, timeSinceLastRefresh]);
|
|
||||||
|
|
||||||
if (history.location.pathname.startsWith(ROUTES.USAGE_EXPLORER)) {
|
|
||||||
return <></>;
|
|
||||||
} else {
|
|
||||||
const inputLabeLToShow =
|
|
||||||
startTime && endTime
|
|
||||||
? `${startTime.format('YYYY/MM/DD HH:mm')} - ${endTime.format(
|
|
||||||
'YYYY/MM/DD HH:mm',
|
|
||||||
)}`
|
|
||||||
: timeInterval;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DateTimeWrapper>
|
|
||||||
<Space style={{ float: 'right', display: 'block' }}>
|
|
||||||
<Space>
|
|
||||||
<Form
|
|
||||||
form={form_dtselector}
|
|
||||||
layout="inline"
|
|
||||||
initialValues={{ interval: '15min' }}
|
|
||||||
style={{ marginTop: 10, marginBottom: 10 }}
|
|
||||||
>
|
|
||||||
<DefaultSelect onSelect={handleOnSelect} value={inputLabeLToShow}>
|
|
||||||
{options.map(({ value, label }) => (
|
|
||||||
<Option key={value + label} value={value}>
|
|
||||||
{label}
|
|
||||||
</Option>
|
|
||||||
))}
|
|
||||||
</DefaultSelect>
|
|
||||||
|
|
||||||
<FormItem hidden={refreshButtonHidden} name="refresh_button">
|
|
||||||
<Button type="primary" onClick={handleRefresh}>
|
|
||||||
Refresh
|
|
||||||
</Button>
|
|
||||||
</FormItem>
|
|
||||||
</Form>
|
|
||||||
</Space>
|
|
||||||
<Space
|
|
||||||
style={{
|
|
||||||
float: 'right',
|
|
||||||
display: 'block',
|
|
||||||
marginRight: 20,
|
|
||||||
minHeight: 23,
|
|
||||||
width: 200,
|
|
||||||
textAlign: 'right',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{refreshText}
|
|
||||||
</Space>
|
|
||||||
<CustomDateTimeModal
|
|
||||||
visible={customDTPickerVisible}
|
|
||||||
onCreate={handleCustomDate}
|
|
||||||
onCancel={(): void => {
|
|
||||||
setCustomDTPickerVisible(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</DateTimeWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const mapStateToProps = (state: AppState): { globalTime: GlobalTime } => {
|
|
||||||
return { globalTime: state.globalTime };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DateTimeSelector = connect(mapStateToProps, {
|
|
||||||
updateTimeInterval: updateTimeInterval,
|
|
||||||
})(_DateTimeSelector);
|
|
||||||
|
|
||||||
export default DateTimeSelector;
|
|
@ -1,55 +0,0 @@
|
|||||||
import { Breadcrumb } from 'antd';
|
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import React from 'react';
|
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
const BreadCrumbWrapper = styled.div`
|
|
||||||
padding-top: 20px;
|
|
||||||
padding-left: 20px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const breadcrumbNameMap: any = {
|
|
||||||
// PNOTE - TO DO - Remove any and do typechecking - like https://stackoverflow.com/questions/56568423/typescript-no-index-signature-with-a-parameter-of-type-string-was-found-on-ty
|
|
||||||
[ROUTES.APPLICATION]: 'Application',
|
|
||||||
[ROUTES.TRACES]: 'Traces',
|
|
||||||
[ROUTES.SERVICE_MAP]: 'Service Map',
|
|
||||||
[ROUTES.USAGE_EXPLORER]: 'Usage Explorer',
|
|
||||||
[ROUTES.INSTRUMENTATION]: 'Add instrumentation',
|
|
||||||
[ROUTES.SETTINGS]: 'Settings',
|
|
||||||
};
|
|
||||||
import history from 'lib/history';
|
|
||||||
|
|
||||||
const ShowBreadcrumbs = (): JSX.Element => {
|
|
||||||
// const { location } = props;
|
|
||||||
const location = history.location;
|
|
||||||
const pathSnippets = location.pathname.split('/').filter((i) => i);
|
|
||||||
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
|
|
||||||
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
|
|
||||||
if (breadcrumbNameMap[url] === undefined) {
|
|
||||||
return (
|
|
||||||
<Breadcrumb.Item key={url}>
|
|
||||||
<Link to={url}>{url.split('/').slice(-1)[0]}</Link>
|
|
||||||
</Breadcrumb.Item>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Breadcrumb.Item key={url}>
|
|
||||||
<Link to={url}>{breadcrumbNameMap[url]}</Link>
|
|
||||||
</Breadcrumb.Item>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const breadcrumbItems = [
|
|
||||||
<Breadcrumb.Item key="home">
|
|
||||||
<Link to="/">Home</Link>
|
|
||||||
</Breadcrumb.Item>,
|
|
||||||
].concat(extraBreadcrumbItems);
|
|
||||||
return (
|
|
||||||
<BreadCrumbWrapper>
|
|
||||||
<Breadcrumb>{breadcrumbItems}</Breadcrumb>
|
|
||||||
</BreadCrumbWrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ShowBreadcrumbs;
|
|
@ -1,24 +0,0 @@
|
|||||||
import ROUTES from 'constants/routes';
|
|
||||||
|
|
||||||
export const Options = [
|
|
||||||
{ value: '5min', label: 'Last 5 min' },
|
|
||||||
{ value: '15min', label: 'Last 15 min' },
|
|
||||||
{ value: '30min', label: 'Last 30 min' },
|
|
||||||
{ value: '1hr', label: 'Last 1 hour' },
|
|
||||||
{ value: '6hr', label: 'Last 6 hour' },
|
|
||||||
{ value: '1day', label: 'Last 1 day' },
|
|
||||||
{ value: '1week', label: 'Last 1 week' },
|
|
||||||
{ value: 'custom', label: 'Custom' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ServiceMapOptions = [
|
|
||||||
{ value: '1min', label: 'Last 1 min' },
|
|
||||||
{ value: '5min', label: 'Last 5 min' },
|
|
||||||
];
|
|
||||||
|
|
||||||
export const DefaultOptionsBasedOnRoute = {
|
|
||||||
[ROUTES.SERVICE_MAP]: ServiceMapOptions[0].value,
|
|
||||||
[ROUTES.APPLICATION]: Options[0].value,
|
|
||||||
[ROUTES.SERVICE_METRICS]: Options[2].value,
|
|
||||||
default: Options[2].value,
|
|
||||||
};
|
|
@ -1,83 +0,0 @@
|
|||||||
import ROUTES from 'constants/routes';
|
|
||||||
import React, { createContext, Dispatch, ReactNode, useContext } from 'react';
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
[key: string]: {
|
|
||||||
route: string;
|
|
||||||
isLoaded: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ActionTypes {
|
|
||||||
UPDATE_IS_LOADED = 'ROUTE_IS_LOADED',
|
|
||||||
}
|
|
||||||
|
|
||||||
type Action = {
|
|
||||||
type: ActionTypes;
|
|
||||||
payload: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ContextType {
|
|
||||||
state: State;
|
|
||||||
dispatch: Dispatch<Action>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RouteContext = createContext<ContextType | null>(null);
|
|
||||||
|
|
||||||
interface RouteProviderProps {
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
interface RouteObj {
|
|
||||||
[key: string]: {
|
|
||||||
route: string;
|
|
||||||
isLoaded: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateLocation = (state: State, action: Action): State => {
|
|
||||||
if (action.type === ActionTypes.UPDATE_IS_LOADED) {
|
|
||||||
/*
|
|
||||||
Update the isLoaded property in routes obj
|
|
||||||
if the route matches the current pathname
|
|
||||||
|
|
||||||
Why: Checkout this issue https://github.com/SigNoz/signoz/issues/110
|
|
||||||
To avoid calling the api's twice for Date picker,
|
|
||||||
We will only call once the route is changed
|
|
||||||
*/
|
|
||||||
Object.keys(ROUTES).map((items) => {
|
|
||||||
state[items].isLoaded = state[items].route === action.payload;
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getInitialState = () => {
|
|
||||||
const routes: RouteObj = {};
|
|
||||||
Object.keys(ROUTES).map((items) => {
|
|
||||||
routes[items] = {
|
|
||||||
route: `${ROUTES[items]}`,
|
|
||||||
isLoaded: false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return routes;
|
|
||||||
};
|
|
||||||
|
|
||||||
const RouteProvider: React.FC<RouteProviderProps> = ({ children }) => {
|
|
||||||
const [state, dispatch] = React.useReducer(updateLocation, getInitialState());
|
|
||||||
const value = { state, dispatch };
|
|
||||||
return <RouteContext.Provider value={value}>{children}</RouteContext.Provider>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const useRoute = (): ContextType => {
|
|
||||||
const context = useContext(RouteContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('useRoute must be used within a RouteProvider');
|
|
||||||
}
|
|
||||||
return context as ContextType;
|
|
||||||
};
|
|
||||||
export { RouteProvider, useRoute };
|
|
@ -1,5 +1,4 @@
|
|||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { useRoute } from 'modules/RouteProvider';
|
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { ForceGraph2D } from 'react-force-graph';
|
import { ForceGraph2D } from 'react-force-graph';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@ -7,11 +6,11 @@ import { RouteComponentProps, withRouter } from 'react-router-dom';
|
|||||||
import {
|
import {
|
||||||
getDetailedServiceMapItems,
|
getDetailedServiceMapItems,
|
||||||
getServiceMapItems,
|
getServiceMapItems,
|
||||||
GlobalTime,
|
|
||||||
serviceMapStore,
|
serviceMapStore,
|
||||||
} from 'store/actions';
|
} from 'store/actions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
|
|
||||||
import SelectService from './SelectService';
|
import SelectService from './SelectService';
|
||||||
import { getGraphData, getTooltip, getZoomPx, transformLabel } from './utils';
|
import { getGraphData, getTooltip, getZoomPx, transformLabel } from './utils';
|
||||||
@ -54,7 +53,6 @@ export interface graphDataType {
|
|||||||
|
|
||||||
const ServiceMap = (props: ServiceMapProps) => {
|
const ServiceMap = (props: ServiceMapProps) => {
|
||||||
const fgRef = useRef();
|
const fgRef = useRef();
|
||||||
const { state } = useRoute();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getDetailedServiceMapItems,
|
getDetailedServiceMapItems,
|
||||||
@ -68,10 +66,8 @@ const ServiceMap = (props: ServiceMapProps) => {
|
|||||||
Call the apis only when the route is loaded.
|
Call the apis only when the route is loaded.
|
||||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||||
*/
|
*/
|
||||||
if (state.SERVICE_MAP.isLoaded) {
|
|
||||||
getServiceMapItems(globalTime);
|
getServiceMapItems(globalTime);
|
||||||
getDetailedServiceMapItems(globalTime);
|
getDetailedServiceMapItems(globalTime);
|
||||||
}
|
|
||||||
}, [globalTime]);
|
}, [globalTime]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { Form, Select, Space } from 'antd';
|
import { Form, Select, Space } from 'antd';
|
||||||
import Graph from 'components/Graph';
|
import Graph from 'components/Graph';
|
||||||
import { useRoute } from 'modules/RouteProvider';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
import { GlobalTime, TraceFilters } from 'store/actions';
|
import { TraceFilters } from 'store/actions';
|
||||||
import { getFilteredTraceMetrics } from 'store/actions/MetricsActions';
|
import { getFilteredTraceMetrics } from 'store/actions/MetricsActions';
|
||||||
import { customMetricsItem } from 'store/actions/MetricsActions';
|
import { customMetricsItem } from 'store/actions/MetricsActions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
import { colors } from 'lib/getRandomColor';
|
import { colors } from 'lib/getRandomColor';
|
||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -85,7 +86,6 @@ const _TraceCustomVisualizations = (
|
|||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const [selectedEntity, setSelectedEntity] = useState('calls');
|
const [selectedEntity, setSelectedEntity] = useState('calls');
|
||||||
const [selectedAggOption, setSelectedAggOption] = useState('count');
|
const [selectedAggOption, setSelectedAggOption] = useState('count');
|
||||||
const { state } = useRoute();
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const selectedStep = '60';
|
const selectedStep = '60';
|
||||||
const {
|
const {
|
||||||
@ -94,6 +94,9 @@ const _TraceCustomVisualizations = (
|
|||||||
globalTime,
|
globalTime,
|
||||||
traceFilters,
|
traceFilters,
|
||||||
} = props;
|
} = props;
|
||||||
|
const { loading } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
// Step should be multiples of 60, 60 -> 1 min
|
// Step should be multiples of 60, 60 -> 1 min
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -129,16 +132,16 @@ const _TraceCustomVisualizations = (
|
|||||||
Call the apis only when the route is loaded.
|
Call the apis only when the route is loaded.
|
||||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||||
*/
|
*/
|
||||||
if (state.TRACES.isLoaded) {
|
if (loading === false) {
|
||||||
getFilteredTraceMetrics(request_string, plusMinus15);
|
getFilteredTraceMetrics(request_string, plusMinus15);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
selectedEntity,
|
selectedEntity,
|
||||||
selectedAggOption,
|
selectedAggOption,
|
||||||
traceFilters,
|
traceFilters,
|
||||||
globalTime,
|
|
||||||
getFilteredTraceMetrics,
|
getFilteredTraceMetrics,
|
||||||
state.TRACES.isLoaded,
|
globalTime,
|
||||||
|
loading,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//Custom metrics API called if time, tracefilters, selected entity or agg option changes
|
//Custom metrics API called if time, tracefilters, selected entity or agg option changes
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
import React from 'react';
|
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 { TraceCustomVisualizations } from './TraceCustomVisualizations';
|
import { TraceCustomVisualizations } from './TraceCustomVisualizations';
|
||||||
import { TraceFilter } from './TraceFilter';
|
import { TraceFilter } from './TraceFilter';
|
||||||
import { TraceList } from './TraceList';
|
import { TraceList } from './TraceList';
|
||||||
|
|
||||||
const TraceDetail = (): JSX.Element => {
|
const TraceDetail = ({ globalTimeLoading }: Props): JSX.Element => {
|
||||||
|
useEffect(() => {
|
||||||
|
return (): void => {
|
||||||
|
globalTimeLoading();
|
||||||
|
};
|
||||||
|
}, [globalTimeLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TraceFilter />
|
<TraceFilter />
|
||||||
@ -14,4 +25,16 @@ const TraceDetail = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TraceDetail;
|
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);
|
||||||
|
@ -3,8 +3,6 @@ import FormItem from 'antd/lib/form/FormItem';
|
|||||||
import { Store } from 'antd/lib/form/interface';
|
import { Store } from 'antd/lib/form/interface';
|
||||||
import api from 'api';
|
import api from 'api';
|
||||||
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
import { METRICS_PAGE_QUERY_PARAM } from 'constants/query';
|
||||||
import useMountedState from 'hooks/useMountedState';
|
|
||||||
import { useRoute } from 'modules/RouteProvider';
|
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -12,16 +10,13 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import {
|
import { fetchTraces, TraceFilters, updateTraceFilters } from 'store/actions';
|
||||||
fetchTraces,
|
|
||||||
GlobalTime,
|
|
||||||
TraceFilters,
|
|
||||||
updateTraceFilters,
|
|
||||||
} from 'store/actions';
|
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
import { FilterStateDisplay } from './FilterStateDisplay';
|
import { FilterStateDisplay } from './FilterStateDisplay';
|
||||||
import LatencyModalForm from './LatencyModalForm';
|
import LatencyModalForm from './LatencyModalForm';
|
||||||
@ -59,11 +54,11 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
const urlParams = useMemo(() => {
|
const urlParams = useMemo(() => {
|
||||||
return new URLSearchParams(location.search.split('?')[1]);
|
return new URLSearchParams(location.search.split('?')[1]);
|
||||||
}, [location.search]);
|
}, [location.search]);
|
||||||
const isMount = useMountedState();
|
|
||||||
|
|
||||||
const isMounted = isMount();
|
const { loading } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
const { state } = useRoute();
|
|
||||||
const { updateTraceFilters, traceFilters, globalTime, fetchTraces } = props;
|
const { updateTraceFilters, traceFilters, globalTime, fetchTraces } = props;
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
|
|
||||||
@ -86,12 +81,30 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
[traceFilters, updateTraceFilters],
|
[traceFilters, updateTraceFilters],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const populateData = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
if (loading === false) {
|
||||||
|
const service_request = '/service/' + value + '/operations';
|
||||||
|
api.get<string[]>(service_request).then((response) => {
|
||||||
|
// form_basefilter.resetFields(['operation',])
|
||||||
|
setOperationsList(response.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const tagkeyoptions_request = '/tags?service=' + value;
|
||||||
|
api.get<TagKeyOptionItem[]>(tagkeyoptions_request).then((response) => {
|
||||||
|
setTagKeyOptions(response.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[loading],
|
||||||
|
);
|
||||||
|
|
||||||
const handleChangeService = useCallback(
|
const handleChangeService = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
populateData(value);
|
populateData(value);
|
||||||
updateTraceFilters({ ...traceFilters, service: value });
|
updateTraceFilters({ ...traceFilters, service: value });
|
||||||
},
|
},
|
||||||
[traceFilters, updateTraceFilters],
|
[traceFilters, updateTraceFilters, populateData],
|
||||||
);
|
);
|
||||||
|
|
||||||
const spanKindList: ISpanKind[] = [
|
const spanKindList: ISpanKind[] = [
|
||||||
@ -181,7 +194,7 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
const counter = useRef(0);
|
const counter = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMounted && counter.current === 0) {
|
if (loading === false && counter.current === 0) {
|
||||||
counter.current = 1;
|
counter.current = 1;
|
||||||
api
|
api
|
||||||
.get<string[]>(`/services/list`)
|
.get<string[]>(`/services/list`)
|
||||||
@ -237,7 +250,8 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
traceFilters,
|
traceFilters,
|
||||||
urlParams,
|
urlParams,
|
||||||
updateTraceFilters,
|
updateTraceFilters,
|
||||||
isMounted,
|
populateData,
|
||||||
|
loading,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -262,10 +276,10 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
Call the apis only when the route is loaded.
|
Call the apis only when the route is loaded.
|
||||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||||
*/
|
*/
|
||||||
if (state.TRACES.isLoaded) {
|
if (loading === false) {
|
||||||
fetchTraces(globalTime, request_string);
|
fetchTraces(globalTime, request_string);
|
||||||
}
|
}
|
||||||
}, [globalTime, traceFilters, fetchTraces, state]);
|
}, [traceFilters, fetchTraces, loading, globalTime]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let latencyButtonText = 'Latency';
|
let latencyButtonText = 'Latency';
|
||||||
@ -305,19 +319,6 @@ const _TraceFilter = (props: TraceFilterProps): JSX.Element => {
|
|||||||
form_basefilter.setFieldsValue({ kind: traceFilters.kind });
|
form_basefilter.setFieldsValue({ kind: traceFilters.kind });
|
||||||
}, [traceFilters.kind, form_basefilter]);
|
}, [traceFilters.kind, form_basefilter]);
|
||||||
|
|
||||||
function populateData(value: string): void {
|
|
||||||
const service_request = '/service/' + value + '/operations';
|
|
||||||
api.get<string[]>(service_request).then((response) => {
|
|
||||||
// form_basefilter.resetFields(['operation',])
|
|
||||||
setOperationsList(response.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
const tagkeyoptions_request = '/tags?service=' + value;
|
|
||||||
api.get<TagKeyOptionItem[]>(tagkeyoptions_request).then((response) => {
|
|
||||||
setTagKeyOptions(response.data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const onLatencyButtonClick = (): void => {
|
const onLatencyButtonClick = (): void => {
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
import { Select, Space } from 'antd';
|
import { Select, Space } from 'antd';
|
||||||
// import { Bar } from 'react-chartjs-2';
|
|
||||||
import Graph from 'components/Graph';
|
import Graph from 'components/Graph';
|
||||||
import { useRoute } from 'modules/RouteProvider';
|
|
||||||
import moment from 'moment';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
import {
|
import { getServicesList, getUsageData, usageDataItem } from 'store/actions';
|
||||||
getServicesList,
|
|
||||||
getUsageData,
|
|
||||||
GlobalTime,
|
|
||||||
usageDataItem,
|
|
||||||
} from 'store/actions';
|
|
||||||
import { servicesListItem } from 'store/actions/MetricsActions';
|
import { servicesListItem } from 'store/actions/MetricsActions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { isOnboardingSkipped } from 'utils/app';
|
import { isOnboardingSkipped } from 'utils/app';
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
import { Card } from './styles';
|
import { Card } from './styles';
|
||||||
|
|
||||||
interface UsageExplorerProps {
|
interface UsageExplorerProps {
|
||||||
@ -56,8 +51,9 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
|||||||
const [selectedTime, setSelectedTime] = useState(timeDaysOptions[1]);
|
const [selectedTime, setSelectedTime] = useState(timeDaysOptions[1]);
|
||||||
const [selectedInterval, setSelectedInterval] = useState(interval[2]);
|
const [selectedInterval, setSelectedInterval] = useState(interval[2]);
|
||||||
const [selectedService, setSelectedService] = useState<string>('');
|
const [selectedService, setSelectedService] = useState<string>('');
|
||||||
|
const { loading } = useSelector<AppState, GlobalReducer>(
|
||||||
const { state } = useRoute();
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedTime && selectedInterval) {
|
if (selectedTime && selectedInterval) {
|
||||||
@ -78,15 +74,13 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
|||||||
Call the apis only when the route is loaded.
|
Call the apis only when the route is loaded.
|
||||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||||
*/
|
*/
|
||||||
if (state.USAGE_EXPLORER.isLoaded) {
|
if (loading) {
|
||||||
props.getServicesList(props.globalTime);
|
props.getServicesList(props.globalTime);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [loading, props]);
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
labels: props.usageData.map((s) =>
|
labels: props.usageData.map((s) => new Date(s.timestamp / 1000000)),
|
||||||
moment(s.timestamp / 1000000).format('MMM Do h a'),
|
|
||||||
),
|
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Span Count',
|
label: 'Span Count',
|
||||||
@ -98,22 +92,6 @@ const _UsageExplorer = (props: UsageExplorerProps) => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
|
||||||
scales: {
|
|
||||||
yAxes: [
|
|
||||||
{
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: true,
|
|
||||||
fontSize: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{/* PNOTE - TODO - Keep it in reponsive row column tab */}
|
{/* PNOTE - TODO - Keep it in reponsive row column tab */}
|
||||||
|
79
frontend/src/pages/MetricApplication/index.tsx
Normal file
79
frontend/src/pages/MetricApplication/index.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
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 { useParams } from 'react-router-dom';
|
||||||
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
|
import {
|
||||||
|
GetInitialData,
|
||||||
|
GetInitialDataProps,
|
||||||
|
} from 'store/actions/metrics/getInitialData';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
import MetricReducer from 'types/reducer/metrics';
|
||||||
|
|
||||||
|
const MetricsApplication = ({ getInitialData }: MetricsProps): JSX.Element => {
|
||||||
|
const { loading, maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||||
|
(state) => state.globalTime,
|
||||||
|
);
|
||||||
|
const { error, errorMessage } = useSelector<AppState, MetricReducer>(
|
||||||
|
(state) => state.metrics,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { servicename } = useParams<ServiceProps>();
|
||||||
|
|
||||||
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (servicename !== undefined && loading == false) {
|
||||||
|
getInitialData({
|
||||||
|
end: maxTime,
|
||||||
|
service: servicename,
|
||||||
|
start: minTime,
|
||||||
|
step: 60,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, [servicename, maxTime, minTime, getInitialData, loading, dispatch]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <Typography>{errorMessage}</Typography>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spinner tip="Loading..." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <MetricsApplicationContainer />;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
getInitialData: (props: GetInitialDataProps) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ServiceProps {
|
||||||
|
servicename?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (
|
||||||
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
|
): DispatchProps => ({
|
||||||
|
getInitialData: bindActionCreators(GetInitialData, dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
type MetricsProps = DispatchProps;
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(MetricsApplication);
|
72
frontend/src/pages/Metrics/index.tsx
Normal file
72
frontend/src/pages/Metrics/index.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import Spinner from 'components/Spinner';
|
||||||
|
import { SKIP_ONBOARDING } from 'constants/onboarding';
|
||||||
|
import MetricTable from 'container/MetricsTable';
|
||||||
|
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 { 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 { services } = useSelector<AppState, MetricReducer>(
|
||||||
|
(state) => state.metrics,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSkipped = localStorage.getItem(SKIP_ONBOARDING) === 'true';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (loading === false) {
|
||||||
|
getService({
|
||||||
|
start: minTime,
|
||||||
|
end: maxTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [getService, maxTime, minTime, loading]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timeInterval: NodeJS.Timeout;
|
||||||
|
|
||||||
|
if (loading === false && !isSkipped && services.length === 0) {
|
||||||
|
timeInterval = setInterval(() => {
|
||||||
|
getService({
|
||||||
|
start: minTime,
|
||||||
|
end: maxTime,
|
||||||
|
});
|
||||||
|
}, 50000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (): void => {
|
||||||
|
clearInterval(timeInterval);
|
||||||
|
};
|
||||||
|
}, [getService, isSkipped, loading, maxTime, minTime, services]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Spinner tip="Loading..." />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <MetricTable />;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DispatchProps {
|
||||||
|
getService: ({
|
||||||
|
end,
|
||||||
|
start,
|
||||||
|
}: GetServiceProps) => (dispatch: Dispatch<AppActions>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (
|
||||||
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
|
): DispatchProps => ({
|
||||||
|
getService: bindActionCreators(GetService, dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
type MetricsProps = DispatchProps;
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(Metrics);
|
@ -13,26 +13,18 @@ const SettingsPage = (): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form
|
<Form name="basic" initialValues={{ remember: true }} form={form}>
|
||||||
labelCol={{
|
|
||||||
span: 3,
|
|
||||||
}}
|
|
||||||
wrapperCol={{ span: 6 }}
|
|
||||||
name="basic"
|
|
||||||
initialValues={{ remember: true }}
|
|
||||||
style={{ marginLeft: 20 }}
|
|
||||||
form={form}
|
|
||||||
>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Retention Period"
|
label="Retention Period"
|
||||||
name="retention_period"
|
name="retention_period"
|
||||||
rules={[{ required: false }]}
|
rules={[{ required: false }]}
|
||||||
|
style={{ maxWidth: '40%' }}
|
||||||
>
|
>
|
||||||
<Input style={{ marginLeft: 60 }} disabled={true} />
|
<Input disabled={true} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Space style={{ marginLeft: 60, marginTop: 48 }}>
|
<Space>
|
||||||
<Alert
|
<Alert
|
||||||
message="Mail us at support@signoz.io to get instructions on how to change your retention period"
|
message="Mail us at support@signoz.io to get instructions on how to change your retention period"
|
||||||
type="info"
|
type="info"
|
||||||
|
@ -4,10 +4,15 @@ import { IS_LOGGED_IN } from 'constants/auth';
|
|||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { bindActionCreators } from 'redux';
|
||||||
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
|
import { GlobalTimeLoading } from 'store/actions';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
|
||||||
import { ButtonContainer, Container, FormWrapper, Title } from './styles';
|
import { ButtonContainer, Container, FormWrapper, Title } from './styles';
|
||||||
|
|
||||||
const Signup = (): JSX.Element => {
|
const Signup = ({ globalLoading }: SignupProps): JSX.Element => {
|
||||||
const [state, setState] = useState({ submitted: false });
|
const [state, setState] = useState({ submitted: false });
|
||||||
const [formState, setFormState] = useState({
|
const [formState, setFormState] = useState({
|
||||||
firstName: { value: '' },
|
firstName: { value: '' },
|
||||||
@ -50,6 +55,7 @@ const Signup = (): JSX.Element => {
|
|||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
localStorage.setItem(IS_LOGGED_IN, 'yes');
|
localStorage.setItem(IS_LOGGED_IN, 'yes');
|
||||||
history.push(ROUTES.APPLICATION);
|
history.push(ROUTES.APPLICATION);
|
||||||
|
globalLoading();
|
||||||
} else {
|
} else {
|
||||||
// @TODO throw a error notification here
|
// @TODO throw a error notification here
|
||||||
}
|
}
|
||||||
@ -113,4 +119,16 @@ const Signup = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Signup;
|
interface DispatchProps {
|
||||||
|
globalLoading: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (
|
||||||
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
|
): DispatchProps => ({
|
||||||
|
globalLoading: bindActionCreators(GlobalTimeLoading, dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
type SignupProps = DispatchProps;
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(Signup);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import api from 'api';
|
import api from 'api';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { GlobalTime } from 'store/actions/global';
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
import { toUTCEpoch } from 'utils/timeUtils';
|
import { toUTCEpoch } from 'utils/timeUtils';
|
||||||
|
|
||||||
import { MetricsActionTypes } from './metricsActionTypes';
|
import { MetricsActionTypes } from './metricsActionTypes';
|
||||||
|
@ -35,7 +35,7 @@ export const GetQueryResults = (
|
|||||||
end,
|
end,
|
||||||
query: encodeURIComponent(query.query),
|
query: encodeURIComponent(query.query),
|
||||||
start: start,
|
start: start,
|
||||||
step: '30',
|
step: '60',
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
query: query.query,
|
query: query.query,
|
||||||
|
@ -1,81 +1,63 @@
|
|||||||
import { Moment } from 'moment';
|
import { Time } from 'container/Header/DateTimeSelection/config';
|
||||||
|
import getMinAgo from 'lib/getStartAndEndTime/getMinAgo';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
|
||||||
import { ActionTypes } from './types';
|
export const UpdateTimeInterval = (
|
||||||
|
interval: Time,
|
||||||
export type DateTimeRangeType = [Moment | null, Moment | null] | null;
|
dateTimeRange: [number, number] = [0, 0],
|
||||||
|
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||||
export interface GlobalTime {
|
return (dispatch: Dispatch<AppActions>): void => {
|
||||||
maxTime: number;
|
let maxTime = new Date().getTime();
|
||||||
minTime: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface updateTimeIntervalAction {
|
|
||||||
type: ActionTypes.updateTimeInterval;
|
|
||||||
payload: GlobalTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateTimeInterval = (
|
|
||||||
interval: string,
|
|
||||||
datetimeRange?: [number, number],
|
|
||||||
) => {
|
|
||||||
let maxTime = 0;
|
|
||||||
let minTime = 0;
|
let minTime = 0;
|
||||||
// if interval string is custom, then datetimRange should be present and max & min time should be
|
|
||||||
// set directly based on that. Assuming datetimeRange values are in ms, and minTime is 0th element
|
|
||||||
|
|
||||||
switch (interval) {
|
if (interval === '1min') {
|
||||||
case '1min':
|
const minTimeAgo = getMinAgo({ minutes: 1 }).getTime();
|
||||||
maxTime = Date.now() * 1000000; // in nano sec
|
minTime = minTimeAgo;
|
||||||
minTime = (Date.now() - 1 * 60 * 1000) * 1000000;
|
} else if (interval === '15min') {
|
||||||
break;
|
const minTimeAgo = getMinAgo({ minutes: 15 }).getTime();
|
||||||
case '5min':
|
minTime = minTimeAgo;
|
||||||
maxTime = Date.now() * 1000000; // in nano sec
|
} else if (interval === '1hr') {
|
||||||
minTime = (Date.now() - 5 * 60 * 1000) * 1000000;
|
const minTimeAgo = getMinAgo({ minutes: 60 }).getTime();
|
||||||
break;
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === '30min') {
|
||||||
case '15min':
|
const minTimeAgo = getMinAgo({ minutes: 30 }).getTime();
|
||||||
maxTime = Date.now() * 1000000; // in nano sec
|
minTime = minTimeAgo;
|
||||||
minTime = (Date.now() - 15 * 60 * 1000) * 1000000;
|
} else if (interval === '5min') {
|
||||||
break;
|
const minTimeAgo = getMinAgo({ minutes: 5 }).getTime();
|
||||||
|
minTime = minTimeAgo;
|
||||||
case '30min':
|
} else if (interval === '1day') {
|
||||||
maxTime = Date.now() * 1000000; // in nano sec
|
// one day = 24*60(min)
|
||||||
minTime = (Date.now() - 30 * 60 * 1000) * 1000000;
|
const minTimeAgo = getMinAgo({ minutes: 26 * 60 }).getTime();
|
||||||
break;
|
minTime = minTimeAgo;
|
||||||
|
} else if (interval === '1week') {
|
||||||
case '1hr':
|
// one week = one day * 7
|
||||||
maxTime = Date.now() * 1000000; // in nano sec
|
const minTimeAgo = getMinAgo({ minutes: 26 * 60 * 7 }).getTime();
|
||||||
minTime = (Date.now() - 1 * 60 * 60 * 1000) * 1000000;
|
minTime = minTimeAgo;
|
||||||
break;
|
} else if (interval === '6hr') {
|
||||||
|
const minTimeAgo = getMinAgo({ minutes: 6 * 60 }).getTime();
|
||||||
case '6hr':
|
minTime = minTimeAgo;
|
||||||
maxTime = Date.now() * 1000000; // in nano sec
|
} else if (interval === 'custom') {
|
||||||
minTime = (Date.now() - 6 * 60 * 60 * 1000) * 1000000;
|
maxTime = dateTimeRange[1];
|
||||||
break;
|
minTime = dateTimeRange[0];
|
||||||
|
|
||||||
case '1day':
|
|
||||||
maxTime = Date.now() * 1000000; // in nano sec
|
|
||||||
minTime = (Date.now() - 24 * 60 * 60 * 1000) * 1000000;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '1week':
|
|
||||||
maxTime = Date.now() * 1000000; // in nano sec
|
|
||||||
minTime = (Date.now() - 7 * 24 * 60 * 60 * 1000) * 1000000;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'custom':
|
|
||||||
if (datetimeRange !== undefined) {
|
|
||||||
maxTime = datetimeRange[1] * 1000000; // in nano sec
|
|
||||||
minTime = datetimeRange[0] * 1000000; // in nano sec
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// console.log('not found matching case');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
dispatch({
|
||||||
type: ActionTypes.updateTimeInterval,
|
type: 'UPDATE_TIME_INTERVAL',
|
||||||
payload: { maxTime: maxTime, minTime: minTime },
|
payload: {
|
||||||
|
maxTime: maxTime * 1000000, // in nano sec,
|
||||||
|
minTime: minTime * 1000000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GlobalTimeLoading = (): ((
|
||||||
|
dispatch: Dispatch<AppActions>,
|
||||||
|
) => void) => {
|
||||||
|
return (dispatch: Dispatch<AppActions>): void => {
|
||||||
|
dispatch({
|
||||||
|
type: 'GLOBAL_TIME_LOADING_START',
|
||||||
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export * from './app';
|
export * from './app';
|
||||||
export * from './dashboard';
|
export * from './dashboard';
|
||||||
export * from './global';
|
export * from './global';
|
||||||
|
export * from './metrics';
|
||||||
export * from './MetricsActions';
|
export * from './MetricsActions';
|
||||||
export * from './serviceMap';
|
export * from './serviceMap';
|
||||||
export * from './traceFilters';
|
export * from './traceFilters';
|
||||||
|
94
frontend/src/store/actions/metrics/getInitialData.ts
Normal file
94
frontend/src/store/actions/metrics/getInitialData.ts
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// import getDBOverView from 'api/metrics/getDBOverView';
|
||||||
|
// import getExternalAverageDuration from 'api/metrics/getExternalAverageDuration';
|
||||||
|
// import getExternalError from 'api/metrics/getExternalError';
|
||||||
|
// import getExternalService from 'api/metrics/getExternalService';
|
||||||
|
import getServiceOverview from 'api/metrics/getServiceOverview';
|
||||||
|
import getTopEndPoints from 'api/metrics/getTopEndPoints';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
import { Props } from 'types/api/metrics/getDBOverview';
|
||||||
|
|
||||||
|
export const GetInitialData = (
|
||||||
|
props: GetInitialDataProps,
|
||||||
|
): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||||
|
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
||||||
|
try {
|
||||||
|
dispatch({
|
||||||
|
type: 'GET_INITIAL_APPLICATION_LOADING',
|
||||||
|
});
|
||||||
|
|
||||||
|
const [
|
||||||
|
// getDBOverViewResponse,
|
||||||
|
// getExternalAverageDurationResponse,
|
||||||
|
// getExternalErrorResponse,
|
||||||
|
// getExternalServiceResponse,
|
||||||
|
getServiceOverviewResponse,
|
||||||
|
getTopEndPointsResponse,
|
||||||
|
] = await Promise.all([
|
||||||
|
// getDBOverView({
|
||||||
|
// ...props,
|
||||||
|
// }),
|
||||||
|
// getExternalAverageDuration({
|
||||||
|
// ...props,
|
||||||
|
// }),
|
||||||
|
// getExternalError({
|
||||||
|
// ...props,
|
||||||
|
// }),
|
||||||
|
// getExternalService({
|
||||||
|
// ...props,
|
||||||
|
// }),
|
||||||
|
getServiceOverview({
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
getTopEndPoints({
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
// getDBOverViewResponse.statusCode === 200 &&
|
||||||
|
// getExternalAverageDurationResponse.statusCode === 200 &&
|
||||||
|
// getExternalErrorResponse.statusCode === 200 &&
|
||||||
|
// getExternalServiceResponse.statusCode === 200 &&
|
||||||
|
getServiceOverviewResponse.statusCode === 200 &&
|
||||||
|
getTopEndPointsResponse.statusCode === 200
|
||||||
|
) {
|
||||||
|
dispatch({
|
||||||
|
type: 'GET_INTIAL_APPLICATION_DATA',
|
||||||
|
payload: {
|
||||||
|
// dbOverView: getDBOverViewResponse.payload,
|
||||||
|
// externalAverageDuration: getExternalAverageDurationResponse.payload,
|
||||||
|
// externalError: getExternalErrorResponse.payload,
|
||||||
|
// externalService: getExternalServiceResponse.payload,
|
||||||
|
serviceOverview: getServiceOverviewResponse.payload,
|
||||||
|
topEndPoints: getTopEndPointsResponse.payload,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatch({
|
||||||
|
type: 'GET_INITIAL_APPLICATION_ERROR',
|
||||||
|
payload: {
|
||||||
|
errorMessage:
|
||||||
|
getTopEndPointsResponse.error ||
|
||||||
|
getServiceOverviewResponse.error ||
|
||||||
|
// getExternalServiceResponse.error ||
|
||||||
|
// getExternalErrorResponse.error ||
|
||||||
|
// getExternalAverageDurationResponse.error ||
|
||||||
|
// getDBOverViewResponse.error ||
|
||||||
|
'Something went wrong',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: 'GET_INITIAL_APPLICATION_ERROR',
|
||||||
|
payload: {
|
||||||
|
errorMessage: (error as AxiosError).toString() || 'Something went wrong',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetInitialDataProps = Props;
|
43
frontend/src/store/actions/metrics/getService.ts
Normal file
43
frontend/src/store/actions/metrics/getService.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import getService from 'api/metrics/getService';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
import { Props } from 'types/api/metrics/getService';
|
||||||
|
|
||||||
|
export const GetService = ({
|
||||||
|
end,
|
||||||
|
start,
|
||||||
|
}: GetServiceProps): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||||
|
return async (dispatch: Dispatch<AppActions>): Promise<void> => {
|
||||||
|
try {
|
||||||
|
dispatch({
|
||||||
|
type: 'GET_SERVICE_LIST_LOADING_START',
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await getService({ end, start });
|
||||||
|
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
dispatch({
|
||||||
|
type: 'GET_SERVICE_LIST_SUCCESS',
|
||||||
|
payload: response.payload,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatch({
|
||||||
|
type: 'GET_SERVICE_LIST_ERROR',
|
||||||
|
payload: {
|
||||||
|
errorMessage: response.error || 'Something went wrong',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
dispatch({
|
||||||
|
type: 'GET_SERVICE_LIST_ERROR',
|
||||||
|
payload: {
|
||||||
|
errorMessage: (error as AxiosError).toString() || 'Something went wrong',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetServiceProps = Props;
|
1
frontend/src/store/actions/metrics/index.ts
Normal file
1
frontend/src/store/actions/metrics/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './getService';
|
@ -1,7 +1,7 @@
|
|||||||
import api from 'api';
|
import api from 'api';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
|
|
||||||
import { GlobalTime } from './global';
|
|
||||||
import { ActionTypes } from './types';
|
import { ActionTypes } from './types';
|
||||||
|
|
||||||
export interface serviceMapStore {
|
export interface serviceMapStore {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import api from 'api';
|
import api from 'api';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
import { toUTCEpoch } from 'utils/timeUtils';
|
import { toUTCEpoch } from 'utils/timeUtils';
|
||||||
|
|
||||||
import { GlobalTime } from './global';
|
|
||||||
import { ActionTypes } from './types';
|
import { ActionTypes } from './types';
|
||||||
|
|
||||||
// PNOTE
|
// PNOTE
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { updateTimeIntervalAction } from './global';
|
|
||||||
import { serviceMapItemAction, servicesAction } from './serviceMap';
|
import { serviceMapItemAction, servicesAction } from './serviceMap';
|
||||||
import { updateTraceFiltersAction } from './traceFilters';
|
import { updateTraceFiltersAction } from './traceFilters';
|
||||||
import { FetchTraceItemAction, FetchTracesAction } from './traces';
|
import { FetchTraceItemAction, FetchTracesAction } from './traces';
|
||||||
@ -19,6 +18,5 @@ export type Action =
|
|||||||
| FetchTracesAction
|
| FetchTracesAction
|
||||||
| updateTraceFiltersAction
|
| updateTraceFiltersAction
|
||||||
| getUsageDataAction
|
| getUsageDataAction
|
||||||
| updateTimeIntervalAction
|
|
||||||
| servicesAction
|
| servicesAction
|
||||||
| serviceMapItemAction;
|
| serviceMapItemAction;
|
||||||
|
@ -1,17 +1,39 @@
|
|||||||
import { Action, ActionTypes, GlobalTime } from 'store/actions';
|
import {
|
||||||
|
GLOBAL_TIME_LOADING_START,
|
||||||
|
GlobalTimeAction,
|
||||||
|
UPDATE_TIME_INTERVAL,
|
||||||
|
} from 'types/actions/globalTime';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
export const updateGlobalTimeReducer = (
|
const intitalState: GlobalReducer = {
|
||||||
state: GlobalTime = {
|
|
||||||
maxTime: Date.now() * 1000000,
|
maxTime: Date.now() * 1000000,
|
||||||
minTime: (Date.now() - 15 * 60 * 1000) * 1000000,
|
minTime: (Date.now() - 15 * 60 * 1000) * 1000000,
|
||||||
},
|
loading: true,
|
||||||
action: Action,
|
};
|
||||||
): GlobalTime => {
|
|
||||||
// Initial global state is time now and 15 minute interval
|
const globalTimeReducer = (
|
||||||
|
state = intitalState,
|
||||||
|
action: GlobalTimeAction,
|
||||||
|
): GlobalReducer => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.updateTimeInterval:
|
case UPDATE_TIME_INTERVAL: {
|
||||||
return action.payload;
|
return {
|
||||||
|
...state,
|
||||||
|
...action.payload,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case GLOBAL_TIME_LOADING_START: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default globalTimeReducer;
|
||||||
|
@ -2,7 +2,8 @@ import { combineReducers } from 'redux';
|
|||||||
|
|
||||||
import appReducer from './app';
|
import appReducer from './app';
|
||||||
import dashboardReducer from './dashboard';
|
import dashboardReducer from './dashboard';
|
||||||
import { updateGlobalTimeReducer } from './global';
|
import globalTimeReducer from './global';
|
||||||
|
import metricsReducers from './metric';
|
||||||
import { metricsReducer } from './metrics';
|
import { metricsReducer } from './metrics';
|
||||||
import { ServiceMapReducer } from './serviceMap';
|
import { ServiceMapReducer } from './serviceMap';
|
||||||
import TraceFilterReducer from './traceFilters';
|
import TraceFilterReducer from './traceFilters';
|
||||||
@ -14,11 +15,12 @@ const reducers = combineReducers({
|
|||||||
traces: tracesReducer,
|
traces: tracesReducer,
|
||||||
traceItem: traceItemReducer,
|
traceItem: traceItemReducer,
|
||||||
usageDate: usageDataReducer,
|
usageDate: usageDataReducer,
|
||||||
globalTime: updateGlobalTimeReducer,
|
globalTime: globalTimeReducer,
|
||||||
metricsData: metricsReducer,
|
metricsData: metricsReducer,
|
||||||
serviceMap: ServiceMapReducer,
|
serviceMap: ServiceMapReducer,
|
||||||
dashboards: dashboardReducer,
|
dashboards: dashboardReducer,
|
||||||
app: appReducer,
|
app: appReducer,
|
||||||
|
metrics: metricsReducers,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppState = ReturnType<typeof reducers>;
|
export type AppState = ReturnType<typeof reducers>;
|
||||||
|
97
frontend/src/store/reducers/metric.ts
Normal file
97
frontend/src/store/reducers/metric.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import {
|
||||||
|
GET_INITIAL_APPLICATION_ERROR,
|
||||||
|
GET_INITIAL_APPLICATION_LOADING,
|
||||||
|
GET_INTIAL_APPLICATION_DATA,
|
||||||
|
GET_SERVICE_LIST_ERROR,
|
||||||
|
GET_SERVICE_LIST_LOADING_START,
|
||||||
|
GET_SERVICE_LIST_SUCCESS,
|
||||||
|
MetricsActions,
|
||||||
|
} from 'types/actions/metrics';
|
||||||
|
import InitialValueTypes from 'types/reducer/metrics';
|
||||||
|
|
||||||
|
const InitialValue: InitialValueTypes = {
|
||||||
|
error: false,
|
||||||
|
errorMessage: '',
|
||||||
|
loading: false,
|
||||||
|
services: [],
|
||||||
|
dbOverView: [],
|
||||||
|
externalService: [],
|
||||||
|
topEndPoints: [],
|
||||||
|
externalAverageDuration: [],
|
||||||
|
externalError: [],
|
||||||
|
serviceOverview: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const metrics = (
|
||||||
|
state = InitialValue,
|
||||||
|
action: MetricsActions,
|
||||||
|
): InitialValueTypes => {
|
||||||
|
switch (action.type) {
|
||||||
|
case GET_SERVICE_LIST_ERROR: {
|
||||||
|
const { errorMessage } = action.payload;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: true,
|
||||||
|
errorMessage: errorMessage,
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case GET_SERVICE_LIST_LOADING_START: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case GET_SERVICE_LIST_SUCCESS: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
services: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case GET_INITIAL_APPLICATION_LOADING: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case GET_INITIAL_APPLICATION_ERROR: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
errorMessage: action.payload.errorMessage,
|
||||||
|
error: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case GET_INTIAL_APPLICATION_DATA: {
|
||||||
|
const {
|
||||||
|
// dbOverView,
|
||||||
|
topEndPoints,
|
||||||
|
serviceOverview,
|
||||||
|
// externalService,
|
||||||
|
// externalAverageDuration,
|
||||||
|
// externalError,
|
||||||
|
} = action.payload;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: false,
|
||||||
|
// dbOverView,
|
||||||
|
topEndPoints,
|
||||||
|
serviceOverview,
|
||||||
|
// externalService,
|
||||||
|
// externalAverageDuration,
|
||||||
|
// externalError,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default metrics;
|
18
frontend/src/types/actions/globalTime.ts
Normal file
18
frontend/src/types/actions/globalTime.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export const UPDATE_TIME_INTERVAL = 'UPDATE_TIME_INTERVAL';
|
||||||
|
export const GLOBAL_TIME_LOADING_START = 'GLOBAL_TIME_LOADING_START';
|
||||||
|
|
||||||
|
export type GlobalTime = {
|
||||||
|
maxTime: number;
|
||||||
|
minTime: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface UpdateTimeInterval {
|
||||||
|
type: typeof UPDATE_TIME_INTERVAL;
|
||||||
|
payload: GlobalTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GlobalTimeLoading {
|
||||||
|
type: typeof GLOBAL_TIME_LOADING_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GlobalTimeAction = UpdateTimeInterval | GlobalTimeLoading;
|
@ -1,6 +1,12 @@
|
|||||||
import { AppAction } from './app';
|
import { AppAction } from './app';
|
||||||
import { DashboardActions } from './dashboard';
|
import { DashboardActions } from './dashboard';
|
||||||
|
import { GlobalTimeAction } from './globalTime';
|
||||||
|
import { MetricsActions } from './metrics';
|
||||||
|
|
||||||
type AppActions = DashboardActions | AppAction;
|
type AppActions =
|
||||||
|
| DashboardActions
|
||||||
|
| AppAction
|
||||||
|
| GlobalTimeAction
|
||||||
|
| MetricsActions;
|
||||||
|
|
||||||
export default AppActions;
|
export default AppActions;
|
||||||
|
51
frontend/src/types/actions/metrics.ts
Normal file
51
frontend/src/types/actions/metrics.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// import { DBOverView } from 'types/api/metrics/getDBOverview';
|
||||||
|
// import { ExternalAverageDuration } from 'types/api/metrics/getExternalAverageDuration';
|
||||||
|
// import { ExternalError } from 'types/api/metrics/getExternalError';
|
||||||
|
// import { ExternalService } from 'types/api/metrics/getExternalService';
|
||||||
|
import { ServicesList } from 'types/api/metrics/getService';
|
||||||
|
import { ServiceOverview } from 'types/api/metrics/getServiceOverview';
|
||||||
|
import { TopEndPoints } from 'types/api/metrics/getTopEndPoints';
|
||||||
|
|
||||||
|
export const GET_SERVICE_LIST_SUCCESS = 'GET_SERVICE_LIST_SUCCESS';
|
||||||
|
export const GET_SERVICE_LIST_LOADING_START = 'GET_SERVICE_LIST_LOADING_START';
|
||||||
|
export const GET_SERVICE_LIST_ERROR = 'GET_SERVICE_LIST_ERROR';
|
||||||
|
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 interface GetServiceList {
|
||||||
|
type: typeof GET_SERVICE_LIST_SUCCESS;
|
||||||
|
payload: ServicesList[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetServiceListLoading {
|
||||||
|
type:
|
||||||
|
| typeof GET_SERVICE_LIST_LOADING_START
|
||||||
|
| typeof GET_INITIAL_APPLICATION_LOADING;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetServiceListError {
|
||||||
|
type: typeof GET_SERVICE_LIST_ERROR | typeof GET_INITIAL_APPLICATION_ERROR;
|
||||||
|
payload: {
|
||||||
|
errorMessage: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetInitialApplicationData {
|
||||||
|
type: typeof GET_INTIAL_APPLICATION_DATA;
|
||||||
|
payload: {
|
||||||
|
topEndPoints: TopEndPoints[];
|
||||||
|
// dbOverView: DBOverView[];
|
||||||
|
// externalService: ExternalService[];
|
||||||
|
// externalAverageDuration: ExternalAverageDuration[];
|
||||||
|
// externalError: ExternalError[];
|
||||||
|
serviceOverview: ServiceOverview[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MetricsActions =
|
||||||
|
| GetServiceListError
|
||||||
|
| GetServiceListLoading
|
||||||
|
| GetServiceList
|
||||||
|
| GetInitialApplicationData;
|
16
frontend/src/types/api/metrics/getDBOverview.ts
Normal file
16
frontend/src/types/api/metrics/getDBOverview.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export interface Props {
|
||||||
|
service: string;
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
step: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DBOverView {
|
||||||
|
avgDuration: number;
|
||||||
|
callRate: number;
|
||||||
|
externalHttpUrl: string;
|
||||||
|
numCalls: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = DBOverView[];
|
12
frontend/src/types/api/metrics/getExternalAverageDuration.ts
Normal file
12
frontend/src/types/api/metrics/getExternalAverageDuration.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Props as GetDBOverViewProps } from './getDBOverview';
|
||||||
|
|
||||||
|
export type Props = GetDBOverViewProps;
|
||||||
|
|
||||||
|
export interface ExternalAverageDuration {
|
||||||
|
avgDuration: number;
|
||||||
|
errorRate: number;
|
||||||
|
numErrors: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = ExternalAverageDuration[];
|
13
frontend/src/types/api/metrics/getExternalError.ts
Normal file
13
frontend/src/types/api/metrics/getExternalError.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Props as GetDBOverViewProps } from './getDBOverview';
|
||||||
|
|
||||||
|
export type Props = GetDBOverViewProps;
|
||||||
|
|
||||||
|
export interface ExternalError {
|
||||||
|
avgDuration: number;
|
||||||
|
errorRate: number;
|
||||||
|
externalHttpUrl: string;
|
||||||
|
numErrors: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = ExternalError[];
|
15
frontend/src/types/api/metrics/getExternalService.ts
Normal file
15
frontend/src/types/api/metrics/getExternalService.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Props as GetDBOverViewProps } from './getDBOverview';
|
||||||
|
|
||||||
|
export type Props = GetDBOverViewProps;
|
||||||
|
|
||||||
|
export interface ExternalService {
|
||||||
|
avgDuration: number;
|
||||||
|
callRate: number;
|
||||||
|
errorRate: number;
|
||||||
|
externalHttpUrl: string;
|
||||||
|
numCalls: number;
|
||||||
|
numErrors: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = ExternalService[];
|
16
frontend/src/types/api/metrics/getService.ts
Normal file
16
frontend/src/types/api/metrics/getService.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export interface Props {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServicesList {
|
||||||
|
serviceName: string;
|
||||||
|
p99: number;
|
||||||
|
avgDuration: number;
|
||||||
|
numCalls: number;
|
||||||
|
callRate: number;
|
||||||
|
numErrors: number;
|
||||||
|
errorRate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = ServicesList[];
|
16
frontend/src/types/api/metrics/getServiceOverview.ts
Normal file
16
frontend/src/types/api/metrics/getServiceOverview.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Props as GetDBOverViewProps } from './getDBOverview';
|
||||||
|
|
||||||
|
export type Props = GetDBOverViewProps;
|
||||||
|
|
||||||
|
export interface ServiceOverview {
|
||||||
|
callRate: number;
|
||||||
|
errorRate: number;
|
||||||
|
numCalls: number;
|
||||||
|
numErrors: number;
|
||||||
|
p50: number;
|
||||||
|
p95: number;
|
||||||
|
p99: number;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = ServiceOverview[];
|
15
frontend/src/types/api/metrics/getTopEndPoints.ts
Normal file
15
frontend/src/types/api/metrics/getTopEndPoints.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export interface TopEndPoints {
|
||||||
|
name: string;
|
||||||
|
numCalls: number;
|
||||||
|
p50: number;
|
||||||
|
p95: number;
|
||||||
|
p99: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
service: string;
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = TopEndPoints[];
|
7
frontend/src/types/reducer/globalTime.ts
Normal file
7
frontend/src/types/reducer/globalTime.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
|
|
||||||
|
export interface GlobalReducer {
|
||||||
|
maxTime: GlobalTime['maxTime'];
|
||||||
|
minTime: GlobalTime['minTime'];
|
||||||
|
loading: boolean;
|
||||||
|
}
|
22
frontend/src/types/reducer/metrics.ts
Normal file
22
frontend/src/types/reducer/metrics.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { DBOverView } from 'types/api/metrics/getDBOverview';
|
||||||
|
import { ExternalAverageDuration } from 'types/api/metrics/getExternalAverageDuration';
|
||||||
|
import { ExternalError } from 'types/api/metrics/getExternalError';
|
||||||
|
import { ExternalService } from 'types/api/metrics/getExternalService';
|
||||||
|
import { ServicesList } from 'types/api/metrics/getService';
|
||||||
|
import { ServiceOverview } from 'types/api/metrics/getServiceOverview';
|
||||||
|
import { TopEndPoints } from 'types/api/metrics/getTopEndPoints';
|
||||||
|
|
||||||
|
interface MetricReducer {
|
||||||
|
services: ServicesList[];
|
||||||
|
loading: boolean;
|
||||||
|
error: boolean;
|
||||||
|
errorMessage: string;
|
||||||
|
dbOverView: DBOverView[];
|
||||||
|
externalService: ExternalService[];
|
||||||
|
topEndPoints: TopEndPoints[];
|
||||||
|
externalAverageDuration: ExternalAverageDuration[];
|
||||||
|
externalError: ExternalError[];
|
||||||
|
serviceOverview: ServiceOverview[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MetricReducer;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user