From 351d1749e9b6c64e1ddea199371125ece61dc2e2 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 8 Apr 2022 16:43:55 +0200 Subject: [PATCH] feat(ui) - dashboard - wip --- frontend/app/api_client.js | 6 +- .../Alerts/AlertFormModal/AlertFormModal.tsx | 2 +- frontend/app/components/Alerts/Alerts.js | 2 +- .../Integrations/SlackAddForm/SlackAddForm.js | 2 +- .../CustomMetriLineChart.tsx | 2 +- .../CustomMetricOverviewChart.tsx | 84 ++++++++++ .../CustomMetricOverviewChart/index.ts | 1 + .../CustomMetricWidget/CustomMetricWidget.tsx | 44 ++--- .../CustomMetricWidgetPreview.tsx | 40 ++--- .../Dashboard/Widgets/ErrorsPerDomain/Bar.css | 2 +- .../Dashboard/Widgets/ErrorsPerDomain/Bar.js | 2 +- .../PredefinedWidgets/CPULoad/CPULoad.tsx | 56 +++++++ .../PredefinedWidgets/CPULoad/index.ts | 1 + .../CallsErrors4xx/CallsErrors4xx.tsx | 49 ++++++ .../PredefinedWidgets/CallsErrors4xx/index.ts | 1 + .../CallsErrors5xx/CallsErrors5xx.tsx | 49 ++++++ .../PredefinedWidgets/CallsErrors5xx/index.ts | 1 + .../PredefinedWidgets/Crashes/Crashes.tsx | 55 +++++++ .../PredefinedWidgets/Crashes/index.ts | 1 + .../DomBuildingTime/DomBuildingTime.tsx | 91 +++++++++++ .../DomBuildingTime/index.ts | 1 + .../ErrorsByOrigin/ErrorsByOrigin.tsx | 48 ++++++ .../PredefinedWidgets/ErrorsByOrigin/index.ts | 1 + .../ErrorsByType/ErrorsByType.tsx | 50 ++++++ .../PredefinedWidgets/ErrorsByType/index.ts | 1 + .../ErrorsPerDomain/ErrorsPerDomain.tsx | 36 +++++ .../ErrorsPerDomain/index.ts | 1 + .../Widgets/PredefinedWidgets/FPS/FPS.tsx | 60 +++++++ .../Widgets/PredefinedWidgets/FPS/index.ts | 1 + .../MemoryConsumption/MemoryConsumption.tsx | 61 +++++++ .../MemoryConsumption/index.ts | 1 + .../ResponseTime/ResponseTime.tsx | 91 +++++++++++ .../PredefinedWidgets/ResponseTime/index.ts | 1 + .../SessionsAffectedByJSErrors.tsx | 47 ++++++ .../SessionsAffectedByJSErrors/index.ts | 1 + .../SlowestDomains/SlowestDomains.tsx | 34 ++++ .../PredefinedWidgets/SlowestDomains/index.ts | 1 + .../TimeToRender/TimeToRender.tsx | 91 +++++++++++ .../PredefinedWidgets/TimeToRender/index.ts | 1 + .../DashboardMetricSelection.tsx | 16 +- .../DashboardSideMenu/DashboardSideMenu.tsx | 12 +- .../DashboardView/DashboardView.tsx | 24 ++- .../DashboardWidgetGrid.tsx | 3 +- .../MetricsSearch/MetricsSearch.tsx | 2 +- .../components/MetricsView/MetricsView.tsx | 6 +- .../components/WidgetChart/WidgetChart.tsx | 82 ++++------ .../components/WidgetForm/WidgetForm.tsx | 22 +-- .../WidgetPredefinedChart.tsx | 81 ++++++++++ .../components/WidgetPredefinedChart/index.ts | 1 + .../WidgetPreview/WidgetPreview.tsx | 2 +- .../WidgetWrapper/WidgetWrapper.tsx | 18 ++- .../SaveSearchModal/SaveSearchModal.tsx | 2 +- .../SavedSearchDropdown.tsx | 2 +- .../app/components/ui/ItemMenu/ItemMenu.js | 3 +- frontend/app/mstore/dashboardStore.ts | 151 +++++++++--------- frontend/app/mstore/metricStore.ts | 16 +- frontend/app/mstore/types/dashboard.ts | 21 ++- frontend/app/mstore/types/widget.ts | 33 +++- frontend/app/services/DashboardService.ts | 23 ++- frontend/app/services/MetricService.ts | 24 +-- 60 files changed, 1313 insertions(+), 250 deletions(-) create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx create mode 100644 frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/index.ts create mode 100644 frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetPredefinedChart/index.ts diff --git a/frontend/app/api_client.js b/frontend/app/api_client.js index 8fc753c08..626f033ea 100644 --- a/frontend/app/api_client.js +++ b/frontend/app/api_client.js @@ -69,12 +69,16 @@ export default class APIClient { this.siteId = siteId; } - fetch(path, params, options = { clean: true }) { + fetch(path, params, options = { clean: true }) { if (params !== undefined) { const cleanedParams = options.clean ? clean(params) : params; this.init.body = JSON.stringify(cleanedParams); } + if (this.init.method === 'GET') { + delete this.init.body; + } + let fetch = window.fetch; diff --git a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx index 74a2552f7..d1459eb6b 100644 --- a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx +++ b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx @@ -45,7 +45,7 @@ function AlertFormModal(props: Props) { const onDelete = async (instance) => { if (await confirm({ header: 'Confirm', - confirmButton: 'Yes, Delete', + confirmButton: 'Yes, delete', confirmation: `Are you sure you want to permanently delete this alert?` })) { props.remove(instance.alertId).then(() => { diff --git a/frontend/app/components/Alerts/Alerts.js b/frontend/app/components/Alerts/Alerts.js index aaa99b7a2..afe161aee 100644 --- a/frontend/app/components/Alerts/Alerts.js +++ b/frontend/app/components/Alerts/Alerts.js @@ -32,7 +32,7 @@ const Alerts = props => { const onDelete = async (instance) => { if (await confirm({ header: 'Confirm', - confirmButton: 'Yes, Delete', + confirmButton: 'Yes, delete', confirmation: `Are you sure you want to permanently delete this alert?` })) { props.remove(instance.alertId).then(() => { diff --git a/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js b/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js index 16586fd1d..4eb16f868 100644 --- a/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js +++ b/frontend/app/components/Client/Integrations/SlackAddForm/SlackAddForm.js @@ -22,7 +22,7 @@ class SlackAddForm extends React.PureComponent { remove = async (id) => { if (await confirm({ header: 'Confirm', - confirmButton: 'Yes, Delete', + confirmButton: 'Yes, delete', confirmation: `Are you sure you want to permanently delete this channel?` })) { this.props.remove(id); diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx index ffbbc6b88..59417bd49 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx @@ -11,7 +11,7 @@ interface Props { onClick?: (event, index) => void; } function CustomMetriLineChart(props: Props) { - const { data, params, seriesMap, colors, onClick = () => null } = props; + const { data, params, seriesMap = [], colors, onClick = () => null } = props; return ( void; +} +function CustomMetricOverviewChart(props: Props) { + const { data, params, seriesMap, colors, onClick = () => null } = props; + console.log('data', data) + const gradientDef = Styles.gradientDef(); + return ( +
+
+
+ {/*
{ 'test' }
*/} +
+
+ {/* {prefix} */} + {/*
+
+
*/} + +
+
+ + + {gradientDef} + + + + + + +
+ ) +} + +export default CustomMetricOverviewChart + + +const countView = (avg, unit) => { + if (unit === 'mb') { + if (!avg) return 0; + const count = Math.trunc(avg / 1024 / 1024); + return numberWithCommas(count); + } + if (unit === 'min') { + if (!avg) return 0; + const count = Math.trunc(avg); + return numberWithCommas(count > 1000 ? count +'k' : count); + } + return avg ? numberWithCommas(avg): 0; + } \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/index.ts new file mode 100644 index 000000000..2aa2ad492 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetricOverviewChart'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx index c5fd2ad3f..32f800e1f 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx @@ -56,29 +56,29 @@ function CustomMetricWidget(props: Props) { const isTable = metric.viewType === 'table'; const isPieChart = metric.viewType === 'pieChart'; - useEffect(() => { - new APIClient()['post'](`/custom_metrics/${metricParams.metricId}/chart`, { ...metricParams, q: metric.name }) - .then(response => response.json()) - .then(({ errors, data }) => { - if (errors) { - console.log('err', errors) - } else { - const namesMap = data - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce((unique: any, item: any) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []); + // useEffect(() => { + // new APIClient()['post'](`/custom_metrics/${metricParams.metricId}/chart`, { ...metricParams, q: metric.name }) + // .then(response => response.json()) + // .then(({ errors, data }) => { + // if (errors) { + // console.log('err', errors) + // } else { + // const namesMap = data + // .map(i => Object.keys(i)) + // .flat() + // .filter(i => i !== 'time' && i !== 'timestamp') + // .reduce((unique: any, item: any) => { + // if (!unique.includes(item)) { + // unique.push(item); + // } + // return unique; + // }, []); - setSeriesMap(namesMap); - setData(getChartFormatter(period)(data)); - } - }).finally(() => setLoading(false)); - }, [period]) + // setSeriesMap(namesMap); + // setData(getChartFormatter(period)(data)); + // } + // }).finally(() => setLoading(false)); + // }, [period]) const clickHandlerTable = (filters) => { const activeWidget = { diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx index 10936f1de..73321405c 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview/CustomMetricWidgetPreview.tsx @@ -61,27 +61,27 @@ function CustomMetricWidget(props: Props) { setLoading(true); // fetch new data for the widget preview - new APIClient()['post']('/custom_metrics/try', { ...metricParams, ...metric.toSaveData() }) - .then(response => response.json()) - .then(({ errors, data }) => { - if (errors) { - console.log('err', errors) - } else { - const namesMap = data - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce((unique: any, item: any) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []); + // new APIClient()['post']('/custom_metrics/try', { ...metricParams, ...metric.toSaveData() }) + // .then(response => response.json()) + // .then(({ errors, data }) => { + // if (errors) { + // console.log('err', errors) + // } else { + // const namesMap = data + // .map(i => Object.keys(i)) + // .flat() + // .filter(i => i !== 'time' && i !== 'timestamp') + // .reduce((unique: any, item: any) => { + // if (!unique.includes(item)) { + // unique.push(item); + // } + // return unique; + // }, []); - setSeriesMap(namesMap); - setData(getChartFormatter(period)(data)); - } - }).finally(() => setLoading(false)); + // setSeriesMap(namesMap); + // setData(getChartFormatter(period)(data)); + // } + // }).finally(() => setLoading(false)); }, [metric]) const onDateChange = (changedDates) => { diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.css b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.css index d3d399918..529aa15eb 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.css +++ b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.css @@ -1,5 +1,5 @@ .bar { - height: 10px; + height: 5px; background-color: red; width: 100%; border-radius: 3px; diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js index 99b37a032..8a09c13d4 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js +++ b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/Bar.js @@ -10,7 +10,7 @@ const Bar = ({ className = '', width = 0, avg, domain, color }) => { {`${avg}`}
-
{domain}
+
{domain}
) } diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx new file mode 100644 index 000000000..5e5853251 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles } from '../../common'; +import { + AreaChart, Area, + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; + +interface Props { + data: any +} +function CPULoad(props: Props) { + const { data } = props; + const gradientDef = Styles.gradientDef(); + const params = { density: 70 } + + return ( + + + + {gradientDef} + + + Styles.tickFormatter(val)} + label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} + /> + + + + + + ); +} + +export default CPULoad; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/index.ts new file mode 100644 index 000000000..37cec8b40 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/index.ts @@ -0,0 +1 @@ +export { default } from './CPULoad' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx new file mode 100644 index 000000000..bd54bc5e3 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles } from '../../common'; +import { + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; + +interface Props { + data: any +} +function CallsErrors4xx(props: Props) { + const { data } = props; + return ( + + + + + + + + + {/* { data.namesMap.map((key, index) => ( + + ))} */} + + + + ); +} + +export default CallsErrors4xx; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/index.ts new file mode 100644 index 000000000..a21e4a950 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/index.ts @@ -0,0 +1 @@ +export { default } from './CallsErrors4xx' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx new file mode 100644 index 000000000..e55bbb0cb --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles } from '../../common'; +import { + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; + +interface Props { + data: any +} +function CallsErrors5xx(props: Props) { + const { data } = props; + return ( + + + + + + + + + {/* { data.namesMap.map((key, index) => ( + + ))} */} + + + + ); +} + +export default CallsErrors5xx; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/index.ts new file mode 100644 index 000000000..661204c0d --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/index.ts @@ -0,0 +1 @@ +export { default } from './CallsErrors5xx' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx new file mode 100644 index 000000000..a73537d69 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles } from '../../common'; +import { + AreaChart, Area, + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; + +interface Props { + data: any +} +function Crashes(props: Props) { + const { data } = props; + const gradientDef = Styles.gradientDef(); + const params = { density: 70 } + return ( + + + + {gradientDef} + + + Styles.tickFormatter(val)} + label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} + /> + + + + + + ); +} + +export default Crashes; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/index.ts new file mode 100644 index 000000000..ba5ce0764 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/index.ts @@ -0,0 +1 @@ +export { default } from './Crashes' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx new file mode 100644 index 000000000..4bda65afa --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles, AvgLabel } from '../../common'; +import { withRequest } from 'HOCs' +import { + AreaChart, Area, + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; +import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; +import { toUnderscore } from 'App/utils'; + +const WIDGET_KEY = 'pagesDomBuildtime'; + +interface Props { + data: any + optionsLoading: any + fetchOptions: any + options: any +} +function DomBuildingTime(props: Props) { + const { data, optionsLoading } = props; + const gradientDef = Styles.gradientDef(); + const params = { density: 70 } + + + const onSelect = (params) => { + const _params = { density: 70 } + console.log('params', params) // TODO reload the data with new params; + // this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value }) + } + + return ( + + <> +
+ + +
+ + + {gradientDef} + + + Styles.tickFormatter(val)} + label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} + /> + + + + + +
+ ); +} + +export default withRequest({ + dataName: "options", + initialData: [], + dataWrapper: data => data, + loadingName: 'optionsLoading', + requestName: "fetchOptions", + endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', + method: 'GET' +})(DomBuildingTime) \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/index.ts new file mode 100644 index 000000000..a3191aaf7 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/index.ts @@ -0,0 +1 @@ +export { default } from './DomBuildingTime' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx new file mode 100644 index 000000000..2eb5aa1dd --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles } from '../../common'; +import { + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; + +interface Props { + data: any +} +function ErrorsByOrigin(props: Props) { + const { data } = props; + return ( + + + + + + + + + 1st Party} dataKey="firstParty" stackId="a" fill={Styles.colors[0]} /> + 3rd Party} dataKey="thirdParty" stackId="a" fill={Styles.colors[2]} /> + + + + ); +} + +export default ErrorsByOrigin; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/index.ts new file mode 100644 index 000000000..18a8b9ec3 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/index.ts @@ -0,0 +1 @@ +export { default } from './ErrorsByOrigin' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx new file mode 100644 index 000000000..993ff8837 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles } from '../../common'; +import { + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; + +interface Props { + data: any +} +function ErrorsByType(props: Props) { + const { data } = props; + return ( + + + + + + + + + + + + + + + + ); +} + +export default ErrorsByType; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/index.ts new file mode 100644 index 000000000..f889ccec7 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/index.ts @@ -0,0 +1 @@ +export { default } from './ErrorsByType' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx new file mode 100644 index 000000000..d51e7539b --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles } from '../../common'; +import { numberWithCommas } from 'App/utils'; +import Bar from 'App/components/Dashboard/Widgets/ErrorsPerDomain/Bar'; + +interface Props { + data: any +} +function ErrorsPerDomain(props: Props) { + const { data } = props; + console.log('ErrorsPerDomain', data); + // const firstAvg = 10; + const firstAvg = data.chart[0] && data.chart[0].errorsCount; + return ( + +
+ {data.chart.map((item, i) => + + )} +
+
+ ); +} + +export default ErrorsPerDomain; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/index.ts new file mode 100644 index 000000000..d08e3867b --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/index.ts @@ -0,0 +1 @@ +export { default } from './ErrorsPerDomain' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx new file mode 100644 index 000000000..13f0c17b9 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles, AvgLabel } from '../../common'; +import { + AreaChart, Area, + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; + +interface Props { + data: any +} +function FPS(props: Props) { + const { data } = props; + const gradientDef = Styles.gradientDef(); + const params = { density: 70 } + + return ( + + <> +
+ +
+ + + {gradientDef} + + + Styles.tickFormatter(val)} + label={{ ...Styles.axisLabelLeft, value: "CPU Load (%)" }} + /> + + + + + +
+ ); +} + +export default FPS; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/index.ts new file mode 100644 index 000000000..85a43ba5e --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/index.ts @@ -0,0 +1 @@ +export { default } from './FPS' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx new file mode 100644 index 000000000..121cf598c --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles, AvgLabel } from '../../common'; +import { + AreaChart, Area, + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; + +interface Props { + data: any +} +function MemoryConsumption(props: Props) { + const { data } = props; + const gradientDef = Styles.gradientDef(); + const params = { density: 70 } + + return ( + + <> +
+ +
+ + + {gradientDef} + + + Styles.tickFormatter(val)} + label={{ ...Styles.axisLabelLeft, value: "JS Heap Size (mb)" }} + /> + + + + + +
+ ); +} + +export default MemoryConsumption; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/index.ts new file mode 100644 index 000000000..7d426259c --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/index.ts @@ -0,0 +1 @@ +export { default } from './MemoryConsumption' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx new file mode 100644 index 000000000..a6c2a025f --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles, AvgLabel } from '../../common'; +import { withRequest } from 'HOCs' +import { + AreaChart, Area, + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; +import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; +import { toUnderscore } from 'App/utils'; + +const WIDGET_KEY = 'pagesResponseTime'; + +interface Props { + data: any + optionsLoading: any + fetchOptions: any + options: any +} +function ResponseTime(props: Props) { + const { data, optionsLoading } = props; + const gradientDef = Styles.gradientDef(); + const params = { density: 70 } + + + const onSelect = (params) => { + const _params = { density: 70 } + console.log('params', params) // TODO reload the data with new params; + // this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value }) + } + + return ( + + <> +
+ + +
+ + + {gradientDef} + + + Styles.tickFormatter(val)} + label={{ ...Styles.axisLabelLeft, value: "Page Response Time (ms)" }} + /> + + + + + +
+ ); +} + +export default withRequest({ + dataName: "options", + initialData: [], + dataWrapper: data => data, + loadingName: 'optionsLoading', + requestName: "fetchOptions", + endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', + method: 'GET' +})(ResponseTime) \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/index.ts new file mode 100644 index 000000000..95effcb83 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/index.ts @@ -0,0 +1 @@ +export { default } from './ResponseTime' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx new file mode 100644 index 000000000..df55d3811 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles } from '../../common'; +import { + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; + +interface Props { + data: any +} +function SessionsAffectedByJSErrors(props: Props) { + const { data } = props; + return ( + + + + + + + + + + + + + ); +} + +export default SessionsAffectedByJSErrors; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/index.ts new file mode 100644 index 000000000..b160b1af1 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/index.ts @@ -0,0 +1 @@ +export { default } from './SessionsAffectedByJSErrors' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx new file mode 100644 index 000000000..a7334e650 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles } from '../../common'; +import { numberWithCommas } from 'App/utils'; +import Bar from 'App/components/Dashboard/Widgets/SlowestDomains/Bar'; + +interface Props { + data: any +} +function SlowestDomains(props: Props) { + const { data } = props; + const firstAvg = data.chart[0] && data.chart[0].errorsCount; + return ( + +
+ {data.chart.map((item, i) => + + )} +
+
+ ); +} + +export default SlowestDomains; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/index.ts new file mode 100644 index 000000000..311262347 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/index.ts @@ -0,0 +1 @@ +export { default } from './SlowestDomains' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx new file mode 100644 index 000000000..5332f71c5 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import { Styles, AvgLabel } from '../../common'; +import { withRequest } from 'HOCs' +import { + AreaChart, Area, + BarChart, Bar, CartesianGrid, Tooltip, + LineChart, Line, Legend, ResponsiveContainer, + XAxis, YAxis + } from 'recharts'; +import WidgetAutoComplete from 'Shared/WidgetAutoComplete'; +import { toUnderscore } from 'App/utils'; + +const WIDGET_KEY = 'timeToRender'; + +interface Props { + data: any + optionsLoading: any + fetchOptions: any + options: any +} +function TimeToRender(props: Props) { + const { data, optionsLoading } = props; + const gradientDef = Styles.gradientDef(); + const params = { density: 70 } + + + const onSelect = (params) => { + const _params = { density: 70 } + console.log('params', params) // TODO reload the data with new params; + // this.props.fetchWidget(WIDGET_KEY, dashbaordStore.period, props.platform, { ..._params, url: params.value }) + } + + return ( + + <> +
+ + +
+ + + {gradientDef} + + + Styles.tickFormatter(val)} + label={{ ...Styles.axisLabelLeft, value: "Time to Render (ms)" }} + /> + + + + + +
+ ); +} + +export default withRequest({ + dataName: "options", + initialData: [], + dataWrapper: data => data, + loadingName: 'optionsLoading', + requestName: "fetchOptions", + endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', + method: 'GET' +})(TimeToRender) \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/index.ts b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/index.ts new file mode 100644 index 000000000..0e806bf6d --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/index.ts @@ -0,0 +1 @@ +export { default } from './TimeToRender' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx index 475aeaa6d..8c0930398 100644 --- a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx +++ b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx @@ -87,15 +87,19 @@ function DashboardMetricSelection(props) {
-
+
{activeCategory && activeCategory.widgets.map((widget: any) => ( -
dashboardStore.toggleWidgetSelection(widget)} - > - -
+ /> ))}
diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 087ae7e07..3d1901be2 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -1,4 +1,4 @@ -import { useObserver, observer, useLocalObservable } from 'mobx-react-lite'; +import { useObserver } from 'mobx-react-lite'; import React from 'react'; import { SideMenuitem, SideMenuHeader, Icon, Button } from 'UI'; import { useStore } from 'App/mstore'; @@ -7,6 +7,7 @@ import { withSiteId, dashboardSelected, metrics } from 'App/routes'; import { useModal } from 'App/components/Modal'; import DashbaordListModal from '../DashbaordListModal'; import DashboardModal from '../DashboardModal'; +import cn from 'classnames'; const SHOW_COUNT = 5; interface Props { @@ -36,20 +37,25 @@ function DashboardSideMenu(props: Props) { showModal(, {}) } + const togglePinned = (dashboard) => { + dashboardStore.updatePinned(dashboard.dashboardId); + } + return useObserver(() => (
- {dashboardsPicked.map((item: any) => ( + {dashboardsPicked.sort((a: any, b: any) => a.isPinned === b.isPinned ? 0 : a.isPinned ? -1 : 1 ).map((item: any) => ( onItemClick(item)} + className="group" leading = {(
{item.isPublic &&
} - {item.isPinned &&
} + {
togglePinned(item)}>
}
)} /> diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index f3dfdabcc..5d698f704 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -10,6 +10,7 @@ import { withRouter } from 'react-router-dom'; import { useModal } from 'App/components/Modal'; import DashboardModal from '../DashboardModal'; import DashboardEditModal from '../DashboardEditModal'; +import DateRange from 'Shared/DateRange'; interface Props { siteId: number; @@ -23,6 +24,7 @@ function DashboardView(props: Props) { const { hideModal, showModal } = useModal(); const loading = useObserver(() => dashboardStore.fetchingDashboard); const dashboard: any = dashboardStore.selectedDashboard + const period = useObserver(() => dashboardStore.period); const [showEditModal, setShowEditModal] = React.useState(false); useEffect(() => { @@ -42,7 +44,7 @@ function DashboardView(props: Props) { const onDelete = async () => { if (await confirm({ header: 'Confirm', - confirmButton: 'Yes, Delete', + confirmButton: 'Yes, delete', confirmation: `Are you sure you want to permanently delete this Dashboard?` })) { dashboardStore.deleteDashboard(dashboard).then(() => { @@ -53,7 +55,7 @@ function DashboardView(props: Props) { } } - return ( + return useObserver(() => ( setShowEditModal(false)} />
- {/* */}
-
+
+
+ Time Range + dashboardStore.setPeriod(period)} + customRangeRight + direction="left" + /> +
+
- ) + )); } export default withRouter(withModal(observer(DashboardView))); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx index 77356a279..28638f5de 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx @@ -29,7 +29,7 @@ function DashboardWidgetGrid(props) {
} > -
+
{list && list.map((item, index) => ( dashbaord.swapWidgetPosition(dragIndex, hoverIndex)} dashboardId={dashboardId} siteId={siteId} + isWidget={true} /> ))}
diff --git a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx index 6941b6ec0..066598c8e 100644 --- a/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx +++ b/frontend/app/components/Dashboard/components/MetricsSearch/MetricsSearch.tsx @@ -19,7 +19,7 @@ function MetricsSearch(props) { return useObserver(() => (
- + metricStore.metrics.length); React.useEffect(() => { metricStore.fetchList(); @@ -19,7 +20,10 @@ function MetricsView(props: Props) { return useObserver(() => (
- +
+ + {metricsCount} +
diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index b20c3a1b4..86bffb9e9 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -1,72 +1,58 @@ import React, { useState, useRef, useEffect } from 'react'; -import Period, { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period'; -import CustomMetriLineChart from '../../Widgets/CustomMetricsWidgets/CustomMetriLineChart'; -import CustomMetricPercentage from '../../Widgets/CustomMetricsWidgets/CustomMetricPercentage'; -import CustomMetricTable from '../../Widgets/CustomMetricsWidgets/CustomMetricTable'; -import CustomMetricPieChart from '../../Widgets/CustomMetricsWidgets/CustomMetricPieChart'; -import APIClient from 'App/api_client'; -import { Styles } from '../../Widgets/common'; -import { getChartFormatter } from 'Types/dashboard/helper'; -import { observer, useObserver } from 'mobx-react-lite'; +import CustomMetriLineChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart'; +import CustomMetricPercentage from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPercentage'; +import CustomMetricTable from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable'; +import CustomMetricPieChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart'; +import { Styles } from 'App/components/Dashboard/Widgets/common'; +import { observer, useObserver, useLocalObservable } from 'mobx-react-lite'; import { Loader } from 'UI'; import { useStore } from 'App/mstore'; +import WidgetPredefinedChart from '../WidgetPredefinedChart'; interface Props { metric: any; + isWidget?: boolean } function WidgetChart(props: Props) { - const metric = useObserver(() => props.metric); - const { metricStore } = useStore(); - // const metric: any = useObserver(() => metricStore.instance); - const series = useObserver(() => metric.series); + const { isWidget = false, metric } = props; + // const metric = useObserver(() => props.metric); + const { dashboardStore } = useStore(); + const period = useObserver(() => dashboardStore.period); const colors = Styles.customMetricColors; const [loading, setLoading] = useState(false) - const [data, setData] = useState({ chart: [{}] }) const [seriesMap, setSeriesMap] = useState([]); - const [period, setPeriod] = useState(Period({ rangeName: metric.rangeName, startDate: metric.startDate, endDate: metric.endDate })); const params = { density: 70 } const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart' } const prevMetricRef = useRef(); + const [data, setData] = useState(metric.data); useEffect(() => { - // Check for title change if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) { prevMetricRef.current = metric; return }; prevMetricRef.current = metric; - setLoading(true); - // fetch new data for the widget preview - new APIClient()['post']('/custom_metrics/try', { ...metricParams, ...metric.toJson() }) - .then(response => response.json()) - .then(({ errors, data }) => { - if (errors) { - console.log('err', errors) - } else { - const namesMap = data - .map(i => Object.keys(i)) - .flat() - .filter(i => i !== 'time' && i !== 'timestamp') - .reduce((unique: any, item: any) => { - if (!unique.includes(item)) { - unique.push(item); - } - return unique; - }, []); - - setSeriesMap(namesMap); - setData(getChartFormatter(period)(data)); - } - }).finally(() => setLoading(false)); - }, [metric.data]); + setLoading(true); + const data = isWidget ? {} : { ...metricParams, ...metric.toJson() }; + dashboardStore.fetchMetricChartData(metric, data, isWidget).then((res: any) => { + setData(res); + }).finally(() => { + setLoading(false); + }); + }, [period]); const renderChart = () => { - const { metricType, viewType } = metric; + const { metricType, viewType, predefinedKey } = metric; + + if (metricType === 'predefined') { + return + } + if (metricType === 'timeseries') { if (viewType === 'lineChart') { return ( @@ -85,12 +71,12 @@ function WidgetChart(props: Props) { if (metricType === 'table') { if (viewType === 'table') { - return ; + return ; } else if (viewType === 'pieChart') { return ( @@ -100,11 +86,11 @@ function WidgetChart(props: Props) { return
Unknown
; } - return ( + return useObserver(() => ( {renderChart()} - ); + )); } -export default observer(WidgetChart); \ No newline at end of file +export default WidgetChart; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index bc7803c85..6813a07aa 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -70,7 +70,7 @@ function WidgetForm(props: Props) { const onDelete = async () => { if (await confirm({ header: 'Confirm', - confirmButton: 'Yes, Delete', + confirmButton: 'Yes, delete', confirmation: `Are you sure you want to permanently delete this metric?` })) { metricStore.delete(metric).then(props.onDelete); @@ -122,10 +122,10 @@ function WidgetForm(props: Props) { <> issue type )} @@ -134,12 +134,12 @@ function WidgetForm(props: Props) { <> showing )} diff --git a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx new file mode 100644 index 000000000..da6c49280 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Styles } from 'App/components/Dashboard/Widgets/common'; +import CustomMetricOverviewChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart'; +import ErrorsByType from 'App/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType'; +import ErrorsByOrigin from 'App/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin'; +import ErrorsPerDomain from 'App/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain'; +import { useObserver } from 'mobx-react-lite'; +import SessionsAffectedByJSErrors from '../../Widgets/PredefinedWidgets/SessionsAffectedByJSErrors'; +import CallsErrors4xx from '../../Widgets/PredefinedWidgets/CallsErrors4xx'; +import CallsErrors5xx from '../../Widgets/PredefinedWidgets/CallsErrors5xx'; +import CPULoad from '../../Widgets/PredefinedWidgets/CPULoad'; +import Crashes from '../../Widgets/PredefinedWidgets/Crashes'; +import DomBuildingTime from '../../Widgets/PredefinedWidgets/DomBuildingTime'; +import FPS from '../../Widgets/PredefinedWidgets/FPS'; +import MemoryConsumption from '../../Widgets/PredefinedWidgets/MemoryConsumption'; +import ResponseTime from '../../Widgets/PredefinedWidgets/ResponseTime'; +import TimeToRender from '../../Widgets/PredefinedWidgets/TimeToRender'; +import SlowestDomains from '../../Widgets/PredefinedWidgets/SlowestDomains'; + +interface Props { + data: any; + predefinedKey: string +} +function WidgetPredefinedChart(props: Props) { + const { data, predefinedKey } = props; + // const { viewType } = data; + const params = { density: 70 } + + const renderWidget = () => { + switch (predefinedKey) { + // ERRORS + case 'errors_per_type': + return + case 'errors_per_domains': + return + case 'resources_by_party': + return + case 'impacted_sessions_by_js_errors': + return + case 'domains_errors_4xx': + return + case 'domains_errors_5xx': + return + + // PERFORMANCE + case 'cpu': + return + case 'crashes': + return + case 'pages_dom_buildtime': + return + case 'fps': + return + case 'memory_consumption': + return + case 'pages_response_time': + return + // case 'pages_response_time_distribution': + // case 'resources_vs_visually_complete': + // case 'impacted_sessions_by_slow_pages': + // case 'sessions_per_browser': + case 'slowest_domains': + return + // case 'speed_location': + case 'time_to_render': + return + + + default: + return
No widget found
+ } + } + + return useObserver(() => ( + <> + {renderWidget()} + + )); +} + +export default WidgetPredefinedChart; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/index.ts b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/index.ts new file mode 100644 index 000000000..e54ae37cd --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/index.ts @@ -0,0 +1 @@ +export { default } from './WidgetPredefinedChart' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index cf2e9fe21..4ec8a63e2 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -78,7 +78,7 @@ function WidgetPreview(props: Props) {
- +
)); diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index c667ae171..9c723129e 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -21,10 +21,13 @@ interface Props { siteId?: string, active?: boolean; history?: any + onClick?: () => void; + isWidget?: boolean; } function WidgetWrapper(props: Props) { const { dashboardStore } = useStore(); - const { active = false, widget, index = 0, moveListItem = null, isPreview = false, isTemplate = false, dashboardId, siteId } = props; + const { isWidget = false, active = false, index = 0, moveListItem = null, isPreview = false, isTemplate = false, dashboardId, siteId } = props; + const widget = useObserver(() => props.widget); const [{ opacity, isDragging }, dragRef] = useDrag({ type: 'item', @@ -51,8 +54,8 @@ function WidgetWrapper(props: Props) { const onDelete = async () => { if (await confirm({ header: 'Confirm', - confirmButton: 'Yes, Delete', - confirmation: `Are you sure you want to permanently delete this Dashboard?` + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to permanently delete the widget from this dashboard?` })) { dashboardStore.deleteDashboardWidget(dashboardId!, widget.widgetId); } @@ -63,7 +66,7 @@ function WidgetWrapper(props: Props) { } const onChartClick = () => { - if (isPreview || isTemplate) return; + if (!isWidget || widget.metricType === 'predefined') return; props.history.push(withSiteId(dashboardMetricDetails(dashboardId, widget.metricId),siteId)); } @@ -80,13 +83,14 @@ function WidgetWrapper(props: Props) { borderColor: (canDrop && isOver) || active ? '#394EFF' : (isPreview ? 'transparent' : '#EEEEEE'), }} ref={dragDropRef} + onClick={props.onClick ? props.onClick : () => {}} >

{widget.name}

- {!isPreview && !isTemplate && ( + {isWidget && (
-
- +
+
diff --git a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx index 1ba6b3d56..668b657b0 100644 --- a/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx +++ b/frontend/app/components/shared/SaveSearchModal/SaveSearchModal.tsx @@ -38,7 +38,7 @@ function SaveSearchModal(props: Props) { const onDelete = async () => { if (await confirm({ header: 'Confirm', - confirmButton: 'Yes, Delete', + confirmButton: 'Yes, delete', confirmation: `Are you sure you want to permanently delete this Saved search?`, })) { props.remove(savedSearch.searchId).then(() => { diff --git a/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx b/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx index a141dbb5b..61f566680 100644 --- a/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx +++ b/frontend/app/components/shared/SavedSearch/components/SavedSearchDropdown/SavedSearchDropdown.tsx @@ -42,7 +42,7 @@ function SavedSearchDropdown(props: Props) { const onDelete = async (instance) => { if (await confirm({ header: 'Confirm', - confirmButton: 'Yes, Delete', + confirmButton: 'Yes, delete', confirmation: `Are you sure you want to permanently delete this search?` })) { props.remove(instance.alertId).then(() => { diff --git a/frontend/app/components/ui/ItemMenu/ItemMenu.js b/frontend/app/components/ui/ItemMenu/ItemMenu.js index 1938f5a3c..2663a7b2a 100644 --- a/frontend/app/components/ui/ItemMenu/ItemMenu.js +++ b/frontend/app/components/ui/ItemMenu/ItemMenu.js @@ -1,6 +1,7 @@ import { Icon } from 'UI'; import styles from './itemMenu.css'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; +import cn from 'classnames'; export default class ItemMenu extends React.PureComponent { state = { @@ -29,7 +30,7 @@ export default class ItemMenu extends React.PureComponent { >
{ this.menuBtnRef = ref; } } - className="w-10 h-10 cursor-pointer bg-white rounded-full flex items-center justify-center hover:bg-gray-lightest" + className={cn("w-10 h-10 cursor-pointer rounded-full flex items-center justify-center hover:bg-gray-light", { 'bg-gray-light' : displayed })} onClick={ this.toggleMenu } role="button" > diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts index 85832699e..6f2e35da2 100644 --- a/frontend/app/mstore/dashboardStore.ts +++ b/frontend/app/mstore/dashboardStore.ts @@ -3,12 +3,17 @@ import Dashboard, { IDashboard } from "./types/dashboard" import Widget, { IWidget } from "./types/widget"; import { dashboardService, metricService } from "App/services"; import { toast } from 'react-toastify'; +import Period, { LAST_24_HOURS, LAST_7_DAYS } from 'Types/app/period'; +import { getChartFormatter } from 'Types/dashboard/helper'; export interface IDashboardSotre { dashboards: IDashboard[] selectedDashboard: IDashboard | null dashboardInstance: IDashboard selectedWidgets: IWidget[] + startTimestamp: number + endTimestamp: number + period: Period siteId: any currentWidget: Widget @@ -52,6 +57,10 @@ export interface IDashboardSotre { fetchTemplates(): Promise deleteDashboardWidget(dashboardId: string, widgetId: string): Promise addWidgetToDashboard(dashboard: IDashboard, metricIds: any): Promise + + updatePinned(dashboardId: string): Promise + fetchMetricChartData(metric: IWidget, data: any, isWidget: boolean): Promise + setPeriod(period: any): void } export default class DashboardStore implements IDashboardSotre { siteId: any = null @@ -63,6 +72,9 @@ export default class DashboardStore implements IDashboardSotre { currentWidget: Widget = new Widget() widgetCategories: any[] = [] widgets: Widget[] = [] + period: Period = Period({ rangeName: LAST_7_DAYS }) + startTimestamp: number = 0 + endTimestamp: number = 0 // Metrics metricsPage: number = 1 @@ -78,8 +90,6 @@ export default class DashboardStore implements IDashboardSotre { constructor() { makeAutoObservable(this, { widgetCategories: observable.ref, - // dashboardInstance: observable.ref, - resetCurrentWidget: action, addDashboard: action, removeDashboard: action, @@ -99,32 +109,11 @@ export default class DashboardStore implements IDashboardSotre { removeSelectedWidgetByCategory: action, toggleWidgetSelection: action, fetchTemplates: action, + updatePinned: action, + setPeriod: action, + + fetchMetricChartData: action }) - - - // TODO remove this sample data - - // for (let i = 0; i < 4; i++) { - // const cat: any = { - // name: `Category ${i + 1}`, - // categoryId: i, - // description: `Category ${i + 1} description`, - // widgets: [] - // } - - // const randomNumber = Math.floor(Math.random() * (5 - 2 + 1)) + 2 - // for (let j = 0; j < randomNumber; j++) { - // const widget: any= new Widget(); - // widget.widgetId = `${i}-${j}` - // widget.viewType = 'lineChart' - // widget.name = `Widget ${i}-${j}`; - // // widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)]; - // widget.metricType = 'timeseries'; - // cat.widgets.push(widget); - // } - - // this.widgetCategories.push(cat) - // } } toggleAllSelectedWidgets(isSelected: boolean) { @@ -180,7 +169,7 @@ export default class DashboardStore implements IDashboardSotre { return dashboardService.getDashboards() .then((list: any) => { runInAction(() => { - this.dashboards = list.map(d => new Dashboard().fromJson(d)) + this.dashboards = list.map(d => new Dashboard().fromJson(d)).sort((a, b) => a.position - b.position) }) }).finally(() => { runInAction(() => { @@ -383,53 +372,65 @@ export default class DashboardStore implements IDashboardSotre { }) } -} -function getRandomWidget() { - const widget = new Widget(); - widget.widgetId = Math.floor(Math.random() * 100); - widget.name = randomMetricName(); - // widget.type = "random"; - widget.colSpan = Math.floor(Math.random() * 2) + 1; - return widget; -} - -function generateRandomPlaceName() { - const placeNames = [ - "New York", - "Los Angeles", - "Chicago", - "Houston", - "Philadelphia", - "Phoenix", - "San Antonio", - "San Diego", - ] - return placeNames[Math.floor(Math.random() * placeNames.length)] -} - - -function randomMetricName () { - const metrics = ["Revenue", "Profit", "Expenses", "Sales", "Orders", "Revenue", "Profit", "Expenses", "Sales", "Orders", "Revenue", "Profit", "Expenses", "Sales", "Orders", "Revenue", "Profit", "Expenses", "Sales", "Orders"]; - return metrics[Math.floor(Math.random() * metrics.length)]; -} - -function getRandomDashboard(id: any = null, isPinned = false) { - const dashboard = new Dashboard(); - dashboard.name = generateRandomPlaceName(); - dashboard.dashboardId = id ? id : Math.floor(Math.random() * 10); - dashboard.isPinned = isPinned; - for (let i = 0; i < 8; i++) { - const widget = getRandomWidget(); - widget.position = i; - dashboard.addWidget(widget); + updatePinned(dashboardId: string): Promise { + // this.isSaving = true + return dashboardService.updatePinned(dashboardId).then(() => { + toast.success('Dashboard pinned successfully') + this.dashboards.forEach(d => { + if (d.dashboardId === dashboardId) { + d.isPinned = true + } else { + d.isPinned = false + } + }) + }).catch(() => { + toast.error('Dashboard could not be pinned') + }).finally(() => { + // this.isSaving = false + }) + } + + setPeriod(period: any) { + this.period = Period({ start: period.startDate, end: period.endDate, rangeName: period.rangeValue }) } - return dashboard; -} -const sampleDashboards = [ - getRandomDashboard(1, true), - getRandomDashboard(2), - getRandomDashboard(3), - getRandomDashboard(4), -] \ No newline at end of file + fetchMetricChartData(metric: IWidget, data: any, isWidget: boolean = false): Promise { + const period = this.period.toTimestamps() + return new Promise((resolve, reject) => { + // this.isLoading = true + return metricService.getMetricChartData(metric, { ...period, ...data, key: metric.predefinedKey }, isWidget) + .then(data => { + if (metric.metricType === 'predefined' && metric.viewType === 'overview') { + const _data = { ...data, chart: getChartFormatter(this.period)(data.chart) } + metric.setData(_data) + resolve(_data); + } else { + if (metric.predefinedKey === 'errors_per_domains') { + console.log('errors_per_domains', data) + data.chart = data + } else { + data.chart = getChartFormatter(this.period)(Array.isArray(data) ? data : data.chart) + } + data.namesMap = Array.isArray(data) ? data + .map(i => Object.keys(i)) + .flat() + .filter(i => i !== 'time' && i !== 'timestamp') + .reduce((unique: any, item: any) => { + if (!unique.includes(item)) { + unique.push(item); + } + return unique; + }, []) : data.chart; + console.log('map', data.namesMap) + const _data = { namesMap: data.namesMap, chart: data.chart } + metric.setData(_data) + resolve(_data); + } + }).catch((err) => { + console.log('err', err) + reject(err) + }) + }) + } +} \ No newline at end of file diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index bb4815389..48c796179 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -32,7 +32,6 @@ export interface IMetricStore { fetchList(): void fetch(metricId: string) delete(metric: IWidget) - // fetchMetricChartData(metric: IWidget) } export default class MetricStore implements IMetricStore { @@ -131,7 +130,7 @@ export default class MetricStore implements IMetricStore { // API Communication save(metric: IWidget, dashboardId?: string): Promise { - const wasCreating = !metric[Widget.ID_KEY] + const wasCreating = !metric.exists() this.isSaving = true return metricService.saveMetric(metric, dashboardId) .then((metric) => { @@ -177,20 +176,9 @@ export default class MetricStore implements IMetricStore { return metricService.deleteMetric(metric[Widget.ID_KEY]) .then(() => { this.removeById(metric[Widget.ID_KEY]) + toast.success('Metric deleted successfully') }).finally(() => { this.isSaving = false }) } - - fetchMetricChartData(metric: IWidget) { - this.isLoading = true - return metricService.getMetricChartData(metric) - .then(data => { - // runInAction(() => { - // metric.data = data - // }) - }).finally(() => { - this.isLoading = false - }) - } } \ No newline at end of file diff --git a/frontend/app/mstore/types/dashboard.ts b/frontend/app/mstore/types/dashboard.ts index aefe7541f..e3db0a146 100644 --- a/frontend/app/mstore/types/dashboard.ts +++ b/frontend/app/mstore/types/dashboard.ts @@ -1,5 +1,7 @@ import { makeAutoObservable, observable, action, runInAction } from "mobx" import Widget, { IWidget } from "./widget" +import { dashboardService } from "App/services" +import { toast } from 'react-toastify'; export interface IDashboard { dashboardId: any @@ -24,7 +26,7 @@ export interface IDashboard { getWidgetByIndex(index: number): void getWidgetCount(): void getWidgetIndexByWidgetId(widgetId: string): void - swapWidgetPosition(positionA: number, positionB: number): void + swapWidgetPosition(positionA: number, positionB: number): Promise sortWidgets(): void exists(): boolean toggleMetrics(metricId: string): void @@ -93,7 +95,7 @@ export default class Dashboard implements IDashboard { this.name = json.name this.isPublic = json.isPublic this.isPinned = json.isPinned - this.config = json.config + // this.config = json.config this.widgets = json.widgets ? json.widgets.map(w => new Widget().fromJson(w)) : [] }) return this @@ -138,7 +140,7 @@ export default class Dashboard implements IDashboard { return this.widgets.findIndex(w => w.widgetId === widgetId) } - swapWidgetPosition(positionA, positionB) { + swapWidgetPosition(positionA, positionB): Promise { const widgetA = this.widgets[positionA] const widgetB = this.widgets[positionB] this.widgets[positionA] = widgetB @@ -146,6 +148,19 @@ export default class Dashboard implements IDashboard { widgetA.position = positionB widgetB.position = positionA + + return new Promise((resolve, reject) => { + Promise.all([ + dashboardService.saveWidget(this.dashboardId, widgetA), + dashboardService.saveWidget(this.dashboardId, widgetB) + ]).then(() => { + toast.success("Widget position updated") + resolve() + }).catch(() => { + toast.error("Error updating widget position") + reject() + }) + }) } sortWidgets() { diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts index 672c84659..09d96d699 100644 --- a/frontend/app/mstore/types/widget.ts +++ b/frontend/app/mstore/types/widget.ts @@ -9,6 +9,7 @@ export interface IWidget { metricType: string metricOf: string metricValue: string + metricFormat: string viewType: string series: FilterSeries[] sessions: [] @@ -25,6 +26,7 @@ export interface IWidget { isValid: boolean dashboardId: any colSpan: number + predefinedKey: string udpateKey(key: string, value: any): void removeSeries(index: number): void @@ -34,6 +36,8 @@ export interface IWidget { validate(): void update(data: any): void exists(): boolean + toWidget(): any + setData(data: any): void } export default class Widget implements IWidget { public static get ID_KEY():string { return "metricId" } @@ -44,6 +48,7 @@ export default class Widget implements IWidget { metricOf: string = "sessionCount" metricValue: string = "" viewType: string = "lineChart" + metricFormat: string = "sessionCount" series: FilterSeries[] = [] sessions: [] = [] isPublic: boolean = true @@ -54,15 +59,19 @@ export default class Widget implements IWidget { config: any = {} position: number = 0 - data: any = {} + data: any = { + chart: [], + namesMap: {} + } isLoading: boolean = false isValid: boolean = false dashboardId: any = undefined colSpan: number = 2 + predefinedKey: string = '' constructor() { makeAutoObservable(this, { - // data: observable, + data: observable.ref, widgetId: observable, name: observable, metricType: observable, @@ -108,6 +117,8 @@ export default class Widget implements IWidget { this.metricValue = json.metricValue this.metricOf = json.metricOf this.metricType = json.metricType + this.metricFormat = json.metricFormat + this.viewType = json.viewType this.name = json.name this.series = json.series ? json.series.map((series: any) => new FilterSeries().fromJson(series)) : [], this.dashboards = json.dashboards @@ -116,10 +127,21 @@ export default class Widget implements IWidget { this.lastModified = DateTime.fromMillis(1649319074) this.config = json.config this.position = json.config.position + this.predefinedKey = json.predefinedKey }) return this } + toWidget(): any { + return { + config: { + position: this.position, + col: this.config.col, + row: this.config.row, + } + } + } + toJson() { return { metricId: this.metricId, @@ -127,6 +149,7 @@ export default class Widget implements IWidget { metricOf: this.metricOf, metricValue: this.metricValue, metricType: this.metricType, + metricFormat: this.metricFormat, viewType: this.viewType, name: this.name, series: this.series.map((series: any) => series.toJson()), @@ -146,4 +169,10 @@ export default class Widget implements IWidget { exists() { return this.metricId !== undefined } + + setData(data: any) { + runInAction(() => { + Object.assign(this.data, data) + }) + } } \ No newline at end of file diff --git a/frontend/app/services/DashboardService.ts b/frontend/app/services/DashboardService.ts index 3bb99b9b4..b75a059fa 100644 --- a/frontend/app/services/DashboardService.ts +++ b/frontend/app/services/DashboardService.ts @@ -17,6 +17,8 @@ export interface IDashboardService { addWidget(dashboard: IDashboard, metricIds: []): Promise saveWidget(dashboardId: string, widget: IWidget): Promise deleteWidget(dashboardId: string, widgetId: string): Promise + + updatePinned(dashboardId: string): Promise } @@ -115,8 +117,6 @@ export default class DashboardService implements IDashboardService { */ saveMetric(metric: IWidget, dashboardId?: string): Promise { const data = metric.toJson(); - - // const path = dashboardId ? `/metrics` : '/metrics'; // TODO change to /dashboards/:dashboardId/widgets const path = dashboardId ? `/dashboards/${dashboardId}/metrics` : '/metrics'; if (metric.widgetId) { return this.client.put(path + '/' + metric.widgetId, data) @@ -142,6 +142,23 @@ export default class DashboardService implements IDashboardService { * @returns {Promise} */ saveWidget(dashboardId: string, widget: IWidget): Promise { - return this.client.post(`/dashboards/${dashboardId}/widgets`, widget.toJson()) + if (widget.widgetId) { + return this.client.put(`/dashboards/${dashboardId}/widgets/${widget.widgetId}`, widget.toWidget()) + .then(response => response.json()) + .then(response => response.data || {}); + } + return this.client.post(`/dashboards/${dashboardId}/widgets`, widget.toWidget()) + .then(response => response.json()) + .then(response => response.data || {}); + } + + /** + * Update the pinned status of a dashboard. + * @param dashboardId + * @returns + */ + updatePinned(dashboardId: string): Promise { + return this.client.get(`/dashboards/${dashboardId}/pin`, {}) + .then(response => response.json()) } } \ No newline at end of file diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts index f2fab0a83..a62520d52 100644 --- a/frontend/app/services/MetricService.ts +++ b/frontend/app/services/MetricService.ts @@ -10,7 +10,7 @@ export interface IMetricService { deleteMetric(metricId: string): Promise; getTemplates(): Promise; - getMetricChartData(metric: IWidget): Promise; + getMetricChartData(metric: IWidget, data: any, isWidget: boolean): Promise; } export default class MetricService implements IMetricService { @@ -54,18 +54,10 @@ export default class MetricService implements IMetricService { const data = metric.toJson() const isCreating = !data[Widget.ID_KEY]; const method = isCreating ? 'post' : 'put'; - - if(dashboardId) { - const url = `/dashboards/${dashboardId}/metrics`; - return this.client[method](url, data) - .then(response => response.json()) - .then(response => response.data || {}); - } else { - const url = isCreating ? '/metrics' : '/metrics/' + data[Widget.ID_KEY]; - return this.client[method](url, data) - .then(response => response.json()) - .then(response => response.data || {}); - } + const url = isCreating ? '/metrics' : '/metrics/' + data[Widget.ID_KEY]; + return this.client[method](url, data) + .then(response => response.json()) + .then(response => response.data || {}); } /** @@ -90,9 +82,9 @@ export default class MetricService implements IMetricService { .then(response => response.data || []); } - getMetricChartData(metric: IWidget): Promise { - const path = metric.metricId ? `/metrics/${metric.metricId}/chart` : `/custom_metrics/try`; - return this.client.get(path) + getMetricChartData(metric: IWidget, data: any, isWidget: boolean = false): Promise { + const path = isWidget ? `/metrics/${metric.metricId}/chart` : `/metrics/try`; + return this.client.post(path, data) .then(response => response.json()) .then(response => response.data || {}); }