From d2b107ec7f4cd64f5597f0b48538c23b5fca6073 Mon Sep 17 00:00:00 2001 From: Palash <88981777+pal-sig@users.noreply.github.com> Date: Wed, 20 Oct 2021 09:24:55 +0530 Subject: [PATCH] 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 --- frontend/src/AppRoutes/index.tsx | 29 +- frontend/src/AppRoutes/pageComponents.ts | 13 +- frontend/src/api/browser/localstorage/get.ts | 5 + .../src/api/browser/localstorage/remove.ts | 5 + frontend/src/api/browser/localstorage/set.ts | 5 + frontend/src/api/metrics/getDBOverView.ts | 26 ++ .../api/metrics/getExternalAverageDuration.ts | 29 ++ frontend/src/api/metrics/getExternalError.ts | 26 ++ .../src/api/metrics/getExternalService.ts | 26 ++ frontend/src/api/metrics/getService.ts | 26 ++ .../src/api/metrics/getServiceOverview.ts | 26 ++ frontend/src/api/metrics/getTopEndPoints.ts | 26 ++ frontend/src/components/Graph/index.tsx | 107 ++---- .../AppLayout/index.tsx} | 29 +- frontend/src/container/AppLayout/styles.ts | 26 ++ .../container/GridGraphComponent/index.tsx | 7 +- .../Graph/FullView/EmptyGraph.tsx | 90 +++++ .../GridGraphLayout/Graph/FullView/index.tsx | 104 ++++-- .../GridGraphLayout/Graph/FullView/styles.ts | 1 + .../container/GridGraphLayout/Graph/index.tsx | 4 +- .../container/Header/Breadcrumbs/index.tsx | 48 +++ .../Header/CustomDateTimeModal/index.tsx} | 20 +- .../Header/DateTimeSelection/config.ts | 60 +++ .../Header/DateTimeSelection/index.tsx | 328 ++++++++++++++++ .../Header/DateTimeSelection/styles.ts | 28 ++ .../Nav/TopNav => container/Header}/index.tsx | 12 +- frontend/src/container/Header/styles.ts | 8 + .../Nav/TopNav => container/Header}/utils.ts | 0 .../MetricsApplication/Tabs/Application.tsx | 255 +++++++++++++ .../MetricsApplication/Tabs/DBCall.tsx | 59 +++ .../MetricsApplication/Tabs/External.tsx | 97 +++++ .../MetricsApplication/Tabs/styles.ts | 10 + .../MetricsApplication/TopEndpointsTable.tsx | 122 ++++++ .../container/MetricsApplication/index.tsx | 50 +++ .../container/MetricsApplication/styles.ts | 46 +++ .../MetricsTable/SkipOnBoardModal/index.tsx | 48 +++ frontend/src/container/MetricsTable/index.tsx | 105 ++++++ frontend/src/container/MetricsTable/styles.ts | 15 + frontend/src/container/NewWidget/index.tsx | 7 +- .../SideNav/index.tsx | 20 +- .../SideNav/menuItems.ts | 10 +- .../SideNav/styles.ts | 0 frontend/src/lib/getChartData.ts | 6 +- frontend/src/lib/getLabelName.ts | 4 +- frontend/src/lib/getMaxMinTime.ts | 2 +- frontend/src/lib/getTimeString.ts | 15 + .../src/modules/Metrics/ErrorRateChart.tsx | 118 ------ .../Metrics/ExternalApi/ExternalApiGraph.tsx | 105 ------ .../Metrics/ExternalApi/graphConfig.ts | 105 ------ .../src/modules/Metrics/ExternalApi/index.tsx | 1 - .../Metrics/LatencyLineChart/LatencyLine.tsx | 28 -- .../Metrics/LatencyLineChart/index.tsx | 142 ------- .../src/modules/Metrics/RequestRateChart.tsx | 127 ------- .../modules/Metrics/ServiceMetrics/index.tsx | 260 ------------- .../modules/Metrics/ServiceMetrics/styles.ts | 18 - .../src/modules/Metrics/ServiceMetricsDef.tsx | 1 - .../modules/Metrics/ServiceTable/index.tsx | 186 ---------- .../modules/Metrics/ServiceTable/styles.ts | 5 - .../src/modules/Metrics/ServicesTableDef.tsx | 1 - .../src/modules/Metrics/TopEndpointsTable.css | 12 - .../src/modules/Metrics/TopEndpointsTable.tsx | 133 ------- frontend/src/modules/Metrics/styles.ts | 12 - .../modules/Nav/TopNav/DateTimeSelector.tsx | 349 ------------------ .../modules/Nav/TopNav/ShowBreadcrumbs.tsx | 55 --- frontend/src/modules/Nav/TopNav/config.ts | 24 -- frontend/src/modules/RouteProvider.tsx | 83 ----- .../src/modules/Servicemap/ServiceMap.tsx | 10 +- .../Traces/TraceCustomVisualizations.tsx | 17 +- frontend/src/modules/Traces/TraceDetail.tsx | 29 +- frontend/src/modules/Traces/TraceFilter.tsx | 61 +-- frontend/src/modules/Usage/UsageExplorer.tsx | 44 +-- .../src/pages/MetricApplication/index.tsx | 79 ++++ frontend/src/pages/Metrics/index.tsx | 72 ++++ frontend/src/pages/Settings/index.tsx | 16 +- frontend/src/pages/SignUp/index.tsx | 22 +- .../actions/MetricsActions/metricsActions.ts | 2 +- .../actions/dashboard/getQueryResults.ts | 2 +- frontend/src/store/actions/global.ts | 134 +++---- frontend/src/store/actions/index.ts | 1 + .../store/actions/metrics/getInitialData.ts | 94 +++++ .../src/store/actions/metrics/getService.ts | 43 +++ frontend/src/store/actions/metrics/index.ts | 1 + frontend/src/store/actions/serviceMap.ts | 2 +- frontend/src/store/actions/traces.ts | 2 +- frontend/src/store/actions/types.ts | 2 - frontend/src/store/reducers/global.ts | 44 ++- frontend/src/store/reducers/index.ts | 6 +- frontend/src/store/reducers/metric.ts | 97 +++++ frontend/src/types/actions/globalTime.ts | 18 + frontend/src/types/actions/index.ts | 8 +- frontend/src/types/actions/metrics.ts | 51 +++ .../src/types/api/metrics/getDBOverview.ts | 16 + .../api/metrics/getExternalAverageDuration.ts | 12 + .../src/types/api/metrics/getExternalError.ts | 13 + .../types/api/metrics/getExternalService.ts | 15 + frontend/src/types/api/metrics/getService.ts | 16 + .../types/api/metrics/getServiceOverview.ts | 16 + .../src/types/api/metrics/getTopEndPoints.ts | 15 + frontend/src/types/reducer/globalTime.ts | 7 + frontend/src/types/reducer/metrics.ts | 22 ++ .../src/typings/chartjs-adapter-date-fns.d.ts | 1 + 101 files changed, 2601 insertions(+), 2139 deletions(-) create mode 100644 frontend/src/api/browser/localstorage/get.ts create mode 100644 frontend/src/api/browser/localstorage/remove.ts create mode 100644 frontend/src/api/browser/localstorage/set.ts create mode 100644 frontend/src/api/metrics/getDBOverView.ts create mode 100644 frontend/src/api/metrics/getExternalAverageDuration.ts create mode 100644 frontend/src/api/metrics/getExternalError.ts create mode 100644 frontend/src/api/metrics/getExternalService.ts create mode 100644 frontend/src/api/metrics/getService.ts create mode 100644 frontend/src/api/metrics/getServiceOverview.ts create mode 100644 frontend/src/api/metrics/getTopEndPoints.ts rename frontend/src/{modules/AppLayout.tsx => container/AppLayout/index.tsx} (68%) create mode 100644 frontend/src/container/AppLayout/styles.ts create mode 100644 frontend/src/container/GridGraphLayout/Graph/FullView/EmptyGraph.tsx create mode 100644 frontend/src/container/Header/Breadcrumbs/index.tsx rename frontend/src/{modules/Nav/TopNav/CustomDateTimeModal.tsx => container/Header/CustomDateTimeModal/index.tsx} (81%) create mode 100644 frontend/src/container/Header/DateTimeSelection/config.ts create mode 100644 frontend/src/container/Header/DateTimeSelection/index.tsx create mode 100644 frontend/src/container/Header/DateTimeSelection/styles.ts rename frontend/src/{modules/Nav/TopNav => container/Header}/index.tsx (65%) create mode 100644 frontend/src/container/Header/styles.ts rename frontend/src/{modules/Nav/TopNav => container/Header}/utils.ts (100%) create mode 100644 frontend/src/container/MetricsApplication/Tabs/Application.tsx create mode 100644 frontend/src/container/MetricsApplication/Tabs/DBCall.tsx create mode 100644 frontend/src/container/MetricsApplication/Tabs/External.tsx create mode 100644 frontend/src/container/MetricsApplication/Tabs/styles.ts create mode 100644 frontend/src/container/MetricsApplication/TopEndpointsTable.tsx create mode 100644 frontend/src/container/MetricsApplication/index.tsx create mode 100644 frontend/src/container/MetricsApplication/styles.ts create mode 100644 frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx create mode 100644 frontend/src/container/MetricsTable/index.tsx create mode 100644 frontend/src/container/MetricsTable/styles.ts rename frontend/src/{components => container}/SideNav/index.tsx (84%) rename frontend/src/{components => container}/SideNav/menuItems.ts (100%) rename frontend/src/{components => container}/SideNav/styles.ts (100%) create mode 100644 frontend/src/lib/getTimeString.ts delete mode 100644 frontend/src/modules/Metrics/ErrorRateChart.tsx delete mode 100644 frontend/src/modules/Metrics/ExternalApi/ExternalApiGraph.tsx delete mode 100644 frontend/src/modules/Metrics/ExternalApi/graphConfig.ts delete mode 100644 frontend/src/modules/Metrics/ExternalApi/index.tsx delete mode 100644 frontend/src/modules/Metrics/LatencyLineChart/LatencyLine.tsx delete mode 100644 frontend/src/modules/Metrics/LatencyLineChart/index.tsx delete mode 100644 frontend/src/modules/Metrics/RequestRateChart.tsx delete mode 100644 frontend/src/modules/Metrics/ServiceMetrics/index.tsx delete mode 100644 frontend/src/modules/Metrics/ServiceMetrics/styles.ts delete mode 100644 frontend/src/modules/Metrics/ServiceMetricsDef.tsx delete mode 100644 frontend/src/modules/Metrics/ServiceTable/index.tsx delete mode 100644 frontend/src/modules/Metrics/ServiceTable/styles.ts delete mode 100644 frontend/src/modules/Metrics/ServicesTableDef.tsx delete mode 100644 frontend/src/modules/Metrics/TopEndpointsTable.css delete mode 100644 frontend/src/modules/Metrics/TopEndpointsTable.tsx delete mode 100644 frontend/src/modules/Metrics/styles.ts delete mode 100644 frontend/src/modules/Nav/TopNav/DateTimeSelector.tsx delete mode 100644 frontend/src/modules/Nav/TopNav/ShowBreadcrumbs.tsx delete mode 100644 frontend/src/modules/Nav/TopNav/config.ts delete mode 100644 frontend/src/modules/RouteProvider.tsx create mode 100644 frontend/src/pages/MetricApplication/index.tsx create mode 100644 frontend/src/pages/Metrics/index.tsx create mode 100644 frontend/src/store/actions/metrics/getInitialData.ts create mode 100644 frontend/src/store/actions/metrics/getService.ts create mode 100644 frontend/src/store/actions/metrics/index.ts create mode 100644 frontend/src/store/reducers/metric.ts create mode 100644 frontend/src/types/actions/globalTime.ts create mode 100644 frontend/src/types/actions/metrics.ts create mode 100644 frontend/src/types/api/metrics/getDBOverview.ts create mode 100644 frontend/src/types/api/metrics/getExternalAverageDuration.ts create mode 100644 frontend/src/types/api/metrics/getExternalError.ts create mode 100644 frontend/src/types/api/metrics/getExternalService.ts create mode 100644 frontend/src/types/api/metrics/getService.ts create mode 100644 frontend/src/types/api/metrics/getServiceOverview.ts create mode 100644 frontend/src/types/api/metrics/getTopEndPoints.ts create mode 100644 frontend/src/types/reducer/globalTime.ts create mode 100644 frontend/src/types/reducer/metrics.ts create mode 100644 frontend/src/typings/chartjs-adapter-date-fns.d.ts diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx index 78ce774500..777098951a 100644 --- a/frontend/src/AppRoutes/index.tsx +++ b/frontend/src/AppRoutes/index.tsx @@ -1,8 +1,7 @@ import NotFound from 'components/NotFound'; import Spinner from 'components/Spinner'; +import AppLayout from 'container/AppLayout'; import history from 'lib/history'; -import AppLayout from 'modules/AppLayout'; -import { RouteProvider } from 'modules/RouteProvider'; import React, { Suspense } from 'react'; import { Route, Router, Switch } from 'react-router-dom'; @@ -10,20 +9,18 @@ import routes from './routes'; const App = (): JSX.Element => ( - - - }> - - {routes.map(({ path, component, exact }, index) => { - return ( - - ); - })} - - - - - + + }> + + {routes.map(({ path, component, exact }, index) => { + return ( + + ); + })} + + + + ); diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index 7a351c744b..84b92d6f15 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -1,9 +1,13 @@ import Loadable from 'components/Loadable'; +export const ServicesTablePage = Loadable( + () => import(/* webpackChunkName: "ServicesTablePage" */ 'pages/Metrics'), +); + export const ServiceMetricsPage = Loadable( () => 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( () => import(/* webpackChunkName: "SignupPage" */ 'pages/SignUp'), ); diff --git a/frontend/src/api/browser/localstorage/get.ts b/frontend/src/api/browser/localstorage/get.ts new file mode 100644 index 0000000000..675d6995c4 --- /dev/null +++ b/frontend/src/api/browser/localstorage/get.ts @@ -0,0 +1,5 @@ +const get = (key: string): string | null => { + return localStorage.getItem(key); +}; + +export default get; diff --git a/frontend/src/api/browser/localstorage/remove.ts b/frontend/src/api/browser/localstorage/remove.ts new file mode 100644 index 0000000000..6e0546ff1e --- /dev/null +++ b/frontend/src/api/browser/localstorage/remove.ts @@ -0,0 +1,5 @@ +const remove = (key: string): void => { + window.localStorage.removeItem(key); +}; + +export default remove; diff --git a/frontend/src/api/browser/localstorage/set.ts b/frontend/src/api/browser/localstorage/set.ts new file mode 100644 index 0000000000..b0910a0c96 --- /dev/null +++ b/frontend/src/api/browser/localstorage/set.ts @@ -0,0 +1,5 @@ +const set = (key: string, value: string): void => { + localStorage.setItem(key, value); +}; + +export default set; diff --git a/frontend/src/api/metrics/getDBOverView.ts b/frontend/src/api/metrics/getDBOverView.ts new file mode 100644 index 0000000000..7afd56d75d --- /dev/null +++ b/frontend/src/api/metrics/getDBOverView.ts @@ -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 | 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; diff --git a/frontend/src/api/metrics/getExternalAverageDuration.ts b/frontend/src/api/metrics/getExternalAverageDuration.ts new file mode 100644 index 0000000000..51be375d94 --- /dev/null +++ b/frontend/src/api/metrics/getExternalAverageDuration.ts @@ -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 | 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; diff --git a/frontend/src/api/metrics/getExternalError.ts b/frontend/src/api/metrics/getExternalError.ts new file mode 100644 index 0000000000..3587639bb9 --- /dev/null +++ b/frontend/src/api/metrics/getExternalError.ts @@ -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 | 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; diff --git a/frontend/src/api/metrics/getExternalService.ts b/frontend/src/api/metrics/getExternalService.ts new file mode 100644 index 0000000000..de9bf65173 --- /dev/null +++ b/frontend/src/api/metrics/getExternalService.ts @@ -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 | 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; diff --git a/frontend/src/api/metrics/getService.ts b/frontend/src/api/metrics/getService.ts new file mode 100644 index 0000000000..29909b2905 --- /dev/null +++ b/frontend/src/api/metrics/getService.ts @@ -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 | 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; diff --git a/frontend/src/api/metrics/getServiceOverview.ts b/frontend/src/api/metrics/getServiceOverview.ts new file mode 100644 index 0000000000..3ceb794e0e --- /dev/null +++ b/frontend/src/api/metrics/getServiceOverview.ts @@ -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 | 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; diff --git a/frontend/src/api/metrics/getTopEndPoints.ts b/frontend/src/api/metrics/getTopEndPoints.ts new file mode 100644 index 0000000000..a2973d1866 --- /dev/null +++ b/frontend/src/api/metrics/getTopEndPoints.ts @@ -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 | 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; diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index ee1fcce187..a7a901bc06 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -1,8 +1,11 @@ import { + ActiveElement, BarController, BarElement, CategoryScale, Chart, + ChartData, + ChartEvent, ChartOptions, ChartType, Decimation, @@ -13,7 +16,6 @@ import { LineController, LineElement, PointElement, - ScaleOptions, SubTitle, TimeScale, TimeSeriesScale, @@ -53,8 +55,6 @@ const Graph = ({ type, title, isStacked, - label, - xAxisType, onClickHandler, }: GraphProps): JSX.Element => { const { isDarkMode } = useSelector((state) => state.app); @@ -97,25 +97,18 @@ const Graph = ({ legend: { // just making sure that label is present 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: { usePointStyle: true, pointStyle: 'circle', }, position: 'bottom', - // labels: { - // generateLabels: (chart: Chart): LegendItem[] => { - // return (data.datasets || []).map((e, index) => { - // return { - // text: e.label || '', - // datasetIndex: index, - // }; - // }); - // }, - // pointStyle: 'circle', - // usePointStyle: true, - // }, }, }, layout: { @@ -123,16 +116,17 @@ const Graph = ({ }, scales: { x: { - animate: false, grid: { display: true, color: getGridColor(), }, - labels: label, adapters: { date: chartjsAdapter, }, - type: xAxisType, + time: { + unit: 'minute', + }, + type: 'timeseries', }, y: { display: true, @@ -151,77 +145,26 @@ const Graph = ({ cubicInterpolationMode: 'monotone', }, }, - onClick: onClickHandler, + onClick: (event, element, chart) => { + if (onClickHandler) { + onClickHandler(event, element, chart, data); + } + }, }; lineChartRef.current = new Chart(chartRef.current, { type: type, data: data, 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(() => { buildChart(); }, [buildChart]); - return ( - <> - - {/* */} - - ); + return ; }; interface GraphProps { @@ -230,8 +173,14 @@ interface GraphProps { title?: string; isStacked?: boolean; label?: string[]; - xAxisType?: ScaleOptions['type']; - onClickHandler?: ChartOptions['onClick']; + onClickHandler?: graphOnClickHandler; } +export type graphOnClickHandler = ( + event: ChartEvent, + elements: ActiveElement[], + chart: Chart, + data: ChartData, +) => void; + export default Graph; diff --git a/frontend/src/modules/AppLayout.tsx b/frontend/src/container/AppLayout/index.tsx similarity index 68% rename from frontend/src/modules/AppLayout.tsx rename to frontend/src/container/AppLayout/index.tsx index f2f707cf31..66cc5f1fc4 100644 --- a/frontend/src/modules/AppLayout.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -1,39 +1,38 @@ import { Layout } from 'antd'; -import SideNav from 'components/SideNav'; +import get from 'api/browser/localstorage/get'; import ROUTES from 'constants/routes'; +import TopNav from 'container/Header'; +import SideNav from 'container/SideNav'; import history from 'lib/history'; import React, { ReactNode, useEffect } from 'react'; import { useSelector } from 'react-redux'; -import { useLocation } from 'react-router-dom'; import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; -import TopNav from './Nav/TopNav'; -import { useRoute } from './RouteProvider'; - const { Content, Footer } = Layout; - interface BaseLayoutProps { children: ReactNode; } const BaseLayout: React.FC = ({ children }) => { - const location = useLocation(); - const { dispatch } = useRoute(); - const currentYear = new Date().getFullYear(); const { isLoggedIn } = useSelector((state) => state.app); + const isLoggedInLocalStorage = get('isLoggedIn'); useEffect(() => { - dispatch({ type: 'ROUTE_IS_LOADED', payload: location.pathname }); - }, [location, dispatch]); + if (isLoggedIn && history.location.pathname === '/') { + history.push(ROUTES.APPLICATION); + } - useEffect(() => { - if (isLoggedIn) { + if (!isLoggedIn && isLoggedInLocalStorage !== null) { history.push(ROUTES.APPLICATION); } else { - history.push(ROUTES.SIGN_UP); + if (isLoggedInLocalStorage === null) { + history.push(ROUTES.SIGN_UP); + } } - }, [isLoggedIn]); + }, [isLoggedIn, isLoggedInLocalStorage]); + + const currentYear = new Date().getFullYear(); return ( diff --git a/frontend/src/container/AppLayout/styles.ts b/frontend/src/container/AppLayout/styles.ts new file mode 100644 index 0000000000..5a3a409479 --- /dev/null +++ b/frontend/src/container/AppLayout/styles.ts @@ -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; +`; diff --git a/frontend/src/container/GridGraphComponent/index.tsx b/frontend/src/container/GridGraphComponent/index.tsx index 4831bf7794..94da26c2f2 100644 --- a/frontend/src/container/GridGraphComponent/index.tsx +++ b/frontend/src/container/GridGraphComponent/index.tsx @@ -1,6 +1,6 @@ import { Typography } from 'antd'; -import { ChartData } from 'chart.js'; -import Graph from 'components/Graph'; +import { ChartData, ChartOptions } from 'chart.js'; +import Graph, { graphOnClickHandler } from 'components/Graph'; import ValueGraph from 'components/ValueGraph'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import history from 'lib/history'; @@ -14,6 +14,7 @@ const GridGraphComponent = ({ title, opacity, isStacked, + onClickHandler, }: GridGraphComponentProps): JSX.Element | null => { const location = history.location.pathname; @@ -29,6 +30,7 @@ const GridGraphComponent = ({ isStacked, opacity, xAxisType: 'time', + onClickHandler: onClickHandler, }} /> ); @@ -66,6 +68,7 @@ export interface GridGraphComponentProps { title?: string; opacity?: string; isStacked?: boolean; + onClickHandler?: graphOnClickHandler; } export default GridGraphComponent; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/EmptyGraph.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/EmptyGraph.tsx new file mode 100644 index 0000000000..3a6144ac9f --- /dev/null +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/EmptyGraph.tsx @@ -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( + (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 ( + + ); +}; + +interface EmptyGraphProps { + selectedTime: timePreferance; + widget: Widgets; + onClickHandler: graphOnClickHandler | undefined; +} + +export default EmptyGraph; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index 5f9310d312..d053e621d4 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -2,6 +2,7 @@ import { Button, Typography } from 'antd'; import getQueryResult from 'api/widgets/getQuery'; import { AxiosError } from 'axios'; import { ChartData } from 'chart.js'; +import { graphOnClickHandler } from 'components/Graph'; import Spinner from 'components/Spinner'; import TimePreference from 'components/TimePreferenceDropDown'; import GridGraphComponent from 'container/GridGraphComponent'; @@ -12,15 +13,21 @@ import { import getChartData from 'lib/getChartData'; import GetMaxMinTime from 'lib/getMaxMinTime'; 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 { GlobalTime } from 'store/actions'; import { AppState } from 'store/reducers'; +import { GlobalTime } from 'types/actions/globalTime'; import { Widgets } from 'types/api/dashboard/getAll'; +import EmptyGraph from './EmptyGraph'; 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( (state) => state.globalTime, ); @@ -65,7 +72,7 @@ const FullView = ({ widget }: FullViewProps): JSX.Element => { end, query: query.query, start: start, - step: '30', + step: '60', }); return { query: query.query, @@ -118,39 +125,69 @@ const FullView = ({ widget }: FullViewProps): JSX.Element => { onFetchDataHandler(); }, [onFetchDataHandler]); + if (state.error && !state.loading) { + return ( + + {state.errorMessage} + + ); + } + if (state.loading || state.payload === undefined) { - return ; + return ( +
+ +
+ ); } if (state.loading === false && state.payload.datasets.length === 0) { return ( <> - - - No Data - + {fullViewOptions && ( + + + + + )} + + {noDataGraph ? ( + + ) : ( + + No Data + + )} ); } return ( <> - - - - + {fullViewOptions && ( + + + + + )} { isStacked: widget.isStacked, opacity: widget.opacity, title: widget.title, + onClickHandler: onClickHandler, }} /> @@ -176,6 +214,20 @@ interface FullViewState { interface FullViewProps { 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; +}); diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts b/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts index 10d21cd4da..7b85723a29 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts @@ -6,6 +6,7 @@ export const GraphContainer = styled.div` justify-content: center; display: flex; flex-direction: column; + width: 100%; `; export const NotFoundContainer = styled.div` diff --git a/frontend/src/container/GridGraphLayout/Graph/index.tsx b/frontend/src/container/GridGraphLayout/Graph/index.tsx index 05adbd4654..e1a21c383c 100644 --- a/frontend/src/container/GridGraphLayout/Graph/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/index.tsx @@ -12,13 +12,13 @@ import { useSelector } from 'react-redux'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; -import { GlobalTime } from 'store/actions'; import { DeleteWidget, DeleteWidgetProps, } from 'store/actions/dashboard/deleteWidget'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; +import { GlobalTime } from 'types/actions/globalTime'; import { Widgets } from 'types/api/dashboard/getAll'; import Bar from './Bar'; @@ -65,7 +65,7 @@ const GridCardGraph = ({ end, query: query.query, start: start, - step: '30', + step: '60', }); return { diff --git a/frontend/src/container/Header/Breadcrumbs/index.tsx b/frontend/src/container/Header/Breadcrumbs/index.tsx new file mode 100644 index 0000000000..b28a933af4 --- /dev/null +++ b/frontend/src/container/Header/Breadcrumbs/index.tsx @@ -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 ( + + {url.split('/').slice(-1)[0]} + + ); + } else { + return ( + + {breadcrumbNameMap[url]} + + ); + } + }); + + const breadcrumbItems = [ + + Home + , + ].concat(extraBreadcrumbItems); + + return {breadcrumbItems}; +}; + +export default withRouter(ShowBreadcrumbs); diff --git a/frontend/src/modules/Nav/TopNav/CustomDateTimeModal.tsx b/frontend/src/container/Header/CustomDateTimeModal/index.tsx similarity index 81% rename from frontend/src/modules/Nav/TopNav/CustomDateTimeModal.tsx rename to frontend/src/container/Header/CustomDateTimeModal/index.tsx index f1ce19078a..8ebb25bd32 100644 --- a/frontend/src/modules/Nav/TopNav/CustomDateTimeModal.tsx +++ b/frontend/src/container/Header/CustomDateTimeModal/index.tsx @@ -2,22 +2,15 @@ import { DatePicker, Modal } from 'antd'; import { Moment } from 'moment'; import moment from 'moment'; import React, { useState } from 'react'; -import { DateTimeRangeType } from 'store/actions'; +export type DateTimeRangeType = [Moment | null, Moment | null] | null; const { RangePicker } = DatePicker; -interface CustomDateTimeModalProps { - visible: boolean; - onCreate: (dateTimeRange: DateTimeRangeType) => void; //Store is defined in antd forms library - onCancel: () => void; -} - -const CustomDateTimeModal: React.FC = ({ - //destructuring props +const CustomDateTimeModal = ({ visible, onCreate, onCancel, -}) => { +}: CustomDateTimeModalProps): JSX.Element => { const [ customDateTimeRange, setCustomDateTimeRange, @@ -26,6 +19,7 @@ const CustomDateTimeModal: React.FC = ({ function handleRangePickerOk(date_time: DateTimeRangeType): void { setCustomDateTimeRange(date_time); } + function disabledDate(current: Moment): boolean { if (current > moment()) { return true; @@ -53,4 +47,10 @@ const CustomDateTimeModal: React.FC = ({ ); }; +interface CustomDateTimeModalProps { + visible: boolean; + onCreate: (dateTimeRange: DateTimeRangeType) => void; + onCancel: () => void; +} + export default CustomDateTimeModal; diff --git a/frontend/src/container/Header/DateTimeSelection/config.ts b/frontend/src/container/Header/DateTimeSelection/config.ts new file mode 100644 index 0000000000..500267ac1c --- /dev/null +++ b/frontend/src/container/Header/DateTimeSelection/config.ts @@ -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; +}; diff --git a/frontend/src/container/Header/DateTimeSelection/index.tsx b/frontend/src/container/Header/DateTimeSelection/index.tsx new file mode 100644 index 0000000000..2cfd86d758 --- /dev/null +++ b/frontend/src/container/Header/DateTimeSelection/index.tsx @@ -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(); + const [endTime, setEndTime] = useState(); + const [options, setOptions] = useState(getOptions(location.pathname)); + const [refreshButtonHidden, setRefreshButtonHidden] = useState(false); + const [refreshText, setRefreshText] = useState(''); + const [customDateTimeVisible, setCustomDTPickerVisible] = useState( + false, + ); + const isOnSelectHandler = useRef(false); + + const { maxTime, loading, minTime } = useSelector( + (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