diff --git a/frontend/.prettierrc b/frontend/.prettierrc index 761a3e639..4c38cc4c4 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -1,6 +1,6 @@ { - "tabWidth": 4, + "tabWidth": 2, "useTabs": false, - "printWidth": 150, + "printWidth": 100, "singleQuote": true } diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index 90afa772a..0e24e10f9 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -91,7 +91,6 @@ function AssistActions({ React.useEffect(() => { if (!onCall && isCallActive && agentIds) { - logger.log('joinig the party', agentIds) setPrestart(true); call(agentIds) } diff --git a/frontend/app/components/Dashboard/NewDashboard.tsx b/frontend/app/components/Dashboard/NewDashboard.tsx index 89af30897..2f9496591 100644 --- a/frontend/app/components/Dashboard/NewDashboard.tsx +++ b/frontend/app/components/Dashboard/NewDashboard.tsx @@ -9,40 +9,41 @@ import cn from 'classnames'; import { withSiteId } from 'App/routes'; import withPermissions from 'HOCs/withPermissions' -function NewDashboard(props: RouteComponentProps<{}>) { - const { history, match: { params: { siteId, dashboardId, metricId } } } = props; +interface RouterProps { + siteId: string; + dashboardId: string; + metricId: string; +} + +function NewDashboard(props: RouteComponentProps) { + const { history, match: { params: { siteId, dashboardId } } } = props; const { dashboardStore } = useStore(); const loading = useObserver(() => dashboardStore.isLoading); const isMetricDetails = history.location.pathname.includes('/metrics/') || history.location.pathname.includes('/metric/'); + const isDashboardDetails = history.location.pathname.includes('/dashboard/') + const shouldHideMenu = isMetricDetails || isDashboardDetails; useEffect(() => { dashboardStore.fetchList().then((resp) => { if (parseInt(dashboardId) > 0) { dashboardStore.selectDashboardById(dashboardId); } }); - if (!dashboardId && location.pathname.includes('dashboard')) { - dashboardStore.selectDefaultDashboard().then(({ dashboardId }) => { - props.history.push(withSiteId(`/dashboard/${dashboardId}`, siteId)); - }, () => { - props.history.push(withSiteId('/dashboard', siteId)); - }) - } }, [siteId]); return useObserver(() => (
-
+
- +
diff --git a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js b/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js index 10ab766a7..99ffc931b 100644 --- a/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js +++ b/frontend/app/components/Dashboard/Widgets/BreakdownOfLoadedResources/BreakdownOfLoadedResources.js @@ -30,6 +30,7 @@ export default class BreakdownOfLoadedResources extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js b/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js index 8bf8b90c2..4b3cadef6 100644 --- a/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js +++ b/frontend/app/components/Dashboard/Widgets/CallWithErrors/CallWithErrors.js @@ -64,6 +64,7 @@ export default class CallWithErrors extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js b/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js index a0e0d05a0..3c655da5f 100644 --- a/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js +++ b/frontend/app/components/Dashboard/Widgets/CallsErrors5xx/CallsErrors5xx.js @@ -37,6 +37,7 @@ export default class CallsErrors5xx extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js b/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js index 0579480fb..ee448dac2 100644 --- a/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js +++ b/frontend/app/components/Dashboard/Widgets/CpuLoad/CpuLoad.js @@ -27,6 +27,7 @@ export default class CpuLoad extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js b/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js index 16f96a07c..576c9c13f 100644 --- a/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js +++ b/frontend/app/components/Dashboard/Widgets/Crashes/Crashes.js @@ -30,6 +30,7 @@ export default class Crashes extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx index 76b8697c1..0765d7940 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricPieChart/CustomMetricPieChart.tsx @@ -34,7 +34,7 @@ function CustomMetricPieChart(props: Props) { } } return ( - + - +
+ No data for the selected time period
}>
{ e.stopPropagation(); @@ -46,9 +44,9 @@ function CustomMetricTableErrors(props: RouteComponentProps & Props) { return ( No data for the selected time period} + show={!data.errors || data.errors.length === 0} + size="small" >
{data.errors && diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx index c5aa85e0f..ffb489b11 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTableSessions/CustomMetricTableSessions.tsx @@ -3,7 +3,7 @@ import React from "react"; import SessionItem from "Shared/SessionItem"; import { Pagination, NoContent } from "UI"; import { useStore } from "App/mstore"; -import { overPastString } from "App/dateRange"; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; interface Props { metric: any; @@ -26,7 +26,13 @@ function CustomMetricTableSessions(props: Props) { data.sessions.length === 0 } size="small" - title={`No sessions found ${overPastString(period)}`} + title={ +
+ +
+
No relevant sessions found for the selected time period.
+
+ } >
{data.sessions && diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx index fe83c04b8..30dff93db 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidget/CustomMetricWidget.tsx @@ -104,6 +104,7 @@ function CustomMetricWidget(props: Props) { diff --git a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js b/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js index 27cc682ff..970bfdbad 100644 --- a/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js +++ b/frontend/app/components/Dashboard/Widgets/DomBuildingTime/DomBuildingTime.js @@ -44,6 +44,7 @@ export default class DomBuildingTime extends React.PureComponent { return ( @@ -60,6 +61,7 @@ export default class DomBuildingTime extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js b/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js index 399908f74..d77bac5f4 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js +++ b/frontend/app/components/Dashboard/Widgets/ErrorsByOrigin/ErrorsByOrigin.js @@ -29,6 +29,7 @@ export default class ErrorsByOrigin extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js b/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js index 3bca2406c..4421a3fbb 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js +++ b/frontend/app/components/Dashboard/Widgets/ErrorsByType/ErrorsByType.js @@ -31,6 +31,7 @@ export default class ErrorsByType extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js index 68752c46b..11af3f7d7 100644 --- a/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js +++ b/frontend/app/components/Dashboard/Widgets/ErrorsPerDomain/ErrorsPerDomain.js @@ -15,6 +15,7 @@ export default class ErrorsPerDomain extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/FPS/FPS.js b/frontend/app/components/Dashboard/Widgets/FPS/FPS.js index d91379188..843cea3db 100644 --- a/frontend/app/components/Dashboard/Widgets/FPS/FPS.js +++ b/frontend/app/components/Dashboard/Widgets/FPS/FPS.js @@ -26,6 +26,7 @@ export default class FPS extends React.PureComponent { return ( diff --git a/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js b/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js index 23f5731d9..fcd36d98e 100644 --- a/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js +++ b/frontend/app/components/Dashboard/Widgets/LastFrustrations/LastFrustrations.js @@ -12,6 +12,7 @@ export default class LastFeedbacks extends React.PureComponent { { sessions.map(({ diff --git a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js b/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js index 839db02bc..14ed08d93 100644 --- a/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js +++ b/frontend/app/components/Dashboard/Widgets/MemoryConsumption/MemoryConsumption.js @@ -26,6 +26,7 @@ export default class MemoryConsumption extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js index 6f2d300a1..a86e23220 100644 --- a/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js +++ b/frontend/app/components/Dashboard/Widgets/MostImpactfulErrors/MostImpactfulErrors.js @@ -48,6 +48,7 @@ export default class MostImpactfulErrors extends React.PureComponent {
@@ -46,4 +47,4 @@ function BreakdownOfLoadedResources(props: Props) { ); } -export default BreakdownOfLoadedResources; \ No newline at end of file +export default BreakdownOfLoadedResources; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx index 53356bf0d..311b481c6 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CPULoad/CPULoad.tsx @@ -19,6 +19,7 @@ function CPULoad(props: Props) { return ( @@ -54,4 +55,4 @@ function CPULoad(props: Props) { ); } -export default CPULoad; \ No newline at end of file +export default CPULoad; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx index 45673614f..d85512786 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx @@ -61,6 +61,7 @@ function CallWithErrors(props: Props) { diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx index afaaeb37d..ce62bedff 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors4xx/CallsErrors4xx.tsx @@ -16,6 +16,7 @@ function CallsErrors4xx(props: Props) { return ( @@ -46,4 +47,4 @@ function CallsErrors4xx(props: Props) { ); } -export default CallsErrors4xx; \ No newline at end of file +export default CallsErrors4xx; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx index cc87d5c26..9792e9fce 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallsErrors5xx/CallsErrors5xx.tsx @@ -16,6 +16,7 @@ function CallsErrors5xx(props: Props) { return ( @@ -46,4 +47,4 @@ function CallsErrors5xx(props: Props) { ); } -export default CallsErrors5xx; \ No newline at end of file +export default CallsErrors5xx; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx index 0fa472db9..69a7c2f6b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/Crashes/Crashes.tsx @@ -18,6 +18,7 @@ function Crashes(props: Props) { return ( @@ -52,4 +53,4 @@ function Crashes(props: Props) { ); } -export default Crashes; \ No newline at end of file +export default Crashes; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx index f14dc5cd7..7eea40a8a 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/DomBuildingTime/DomBuildingTime.tsx @@ -33,6 +33,7 @@ function DomBuildingTime(props: Props) { return ( <> @@ -87,4 +88,4 @@ export default withRequest({ requestName: "fetchOptions", endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', method: 'GET' -})(DomBuildingTime) \ No newline at end of file +})(DomBuildingTime) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx index f50859051..dded3aef0 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByOrigin/ErrorsByOrigin.tsx @@ -17,6 +17,7 @@ function ErrorsByOrigin(props: Props) { return ( @@ -49,4 +50,4 @@ function ErrorsByOrigin(props: Props) { ); } -export default ErrorsByOrigin; \ No newline at end of file +export default ErrorsByOrigin; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx index 8d01941c8..f3d4a6f0b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsByType/ErrorsByType.tsx @@ -16,6 +16,7 @@ function ErrorsByType(props: Props) { return ( @@ -48,4 +49,4 @@ function ErrorsByType(props: Props) { ); } -export default ErrorsByType; \ No newline at end of file +export default ErrorsByType; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx index fab8ced65..d58effb2b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx @@ -17,6 +17,7 @@ function ErrorsPerDomain(props: Props) { size="small" show={ metric.data.chart.length === 0 } style={{ height: '240px'}} + title="No recordings found" >
{metric.data.chart.map((item, i) => @@ -34,4 +35,4 @@ function ErrorsPerDomain(props: Props) { ); } -export default ErrorsPerDomain; \ No newline at end of file +export default ErrorsPerDomain; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx index e246d3c3f..baea7156d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/FPS/FPS.tsx @@ -19,6 +19,7 @@ function FPS(props: Props) { return ( <> @@ -57,4 +58,4 @@ function FPS(props: Props) { ); } -export default FPS; \ No newline at end of file +export default FPS; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx index 80e1f4d9c..4889f05f7 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/MemoryConsumption/MemoryConsumption.tsx @@ -22,6 +22,7 @@ function MemoryConsumption(props: Props) { <>
@@ -60,4 +61,4 @@ function MemoryConsumption(props: Props) { ); } -export default MemoryConsumption; \ No newline at end of file +export default MemoryConsumption; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx index 4bd0c2b52..74ae4c997 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.tsx @@ -17,6 +17,7 @@ function ResourceLoadedVsResponseEnd(props: Props) { <>
@@ -119,4 +120,4 @@ export default withRequest({ requestName: "fetchOptions", endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', method: 'GET' -})(ResourceLoadingTime) \ No newline at end of file +})(ResourceLoadingTime) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx index 0d6587386..6ed001ff1 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTime/ResponseTime.tsx @@ -34,6 +34,7 @@ function ResponseTime(props: Props) { return ( <> @@ -88,4 +89,4 @@ export default withRequest({ requestName: "fetchOptions", endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', method: 'GET' -})(ResponseTime) \ No newline at end of file +})(ResponseTime) diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx index 5190157ae..8021b9f7d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ResponseTimeDistribution/ResponseTimeDistribution.tsx @@ -49,6 +49,7 @@ function ResponseTimeDistribution(props: Props) { return ( @@ -125,4 +126,4 @@ function ResponseTimeDistribution(props: Props) { ); } -export default ResponseTimeDistribution; \ No newline at end of file +export default ResponseTimeDistribution; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx index 55434e2a9..495ac5126 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.tsx @@ -15,6 +15,7 @@ function SessionsAffectedByJSErrors(props: Props) { const { data, metric } = props; return ( @@ -52,4 +53,4 @@ function SessionsImpactedBySlowRequests(props: Props) { ); } -export default SessionsImpactedBySlowRequests; \ No newline at end of file +export default SessionsImpactedBySlowRequests; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx index 6b155364d..48db4f67c 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx @@ -19,6 +19,7 @@ function SessionsPerBrowser(props: Props) { return (
@@ -38,4 +39,4 @@ function SessionsPerBrowser(props: Props) { ); } -export default SessionsPerBrowser; \ No newline at end of file +export default SessionsPerBrowser; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx index c6adbeff6..1291305ed 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx @@ -16,6 +16,7 @@ function SlowestDomains(props: Props) { size="small" show={ metric.data.chart.length === 0 } style={{ maxHeight: '240px' }} + title="No recordings found" >
{metric.data.chart.map((item, i) => diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx index e87a66b24..6661623c2 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx @@ -63,7 +63,7 @@ function SpeedIndexByLocation(props: Props) { }; return ( - +
diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx index 7fceb853d..94334ee5d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/TimeToRender/TimeToRender.tsx @@ -35,6 +35,7 @@ function TimeToRender(props: Props) { <>
@@ -88,4 +89,4 @@ export default withRequest({ requestName: "fetchOptions", endpoint: '/dashboard/' + toUnderscore(WIDGET_KEY) + '/search', method: 'GET' -})(TimeToRender) \ No newline at end of file +})(TimeToRender) diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js index c30375aa7..d04a5cef5 100644 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js +++ b/frontend/app/components/Dashboard/Widgets/ResourceLoadedVsResponseEnd/ResourceLoadedVsResponseEnd.js @@ -28,6 +28,7 @@ export default class ResourceLoadedVsResponseEnd extends React.PureComponent { diff --git a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js b/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js index 262312f1b..8f95a3479 100644 --- a/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js +++ b/frontend/app/components/Dashboard/Widgets/ResourceLoadingTime/ResourceLoadingTime.js @@ -66,6 +66,7 @@ export default class ResourceLoadingTime extends React.PureComponent {
@@ -96,6 +97,7 @@ export default class ResourceLoadingTime extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js b/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js index 747247872..057122195 100644 --- a/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js +++ b/frontend/app/components/Dashboard/Widgets/SessionsAffectedByJSErrors/SessionsAffectedByJSErrors.js @@ -36,6 +36,7 @@ export default class SessionsAffectedByJSErrors extends React.PureComponent {
{data.chart.map((item, i) => @@ -40,4 +41,4 @@ export default class SessionsPerBrowser extends React.PureComponent { ); } -} \ No newline at end of file +} diff --git a/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js b/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js index b31b93891..9f85ae412 100644 --- a/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js +++ b/frontend/app/components/Dashboard/Widgets/SlowestDomains/SlowestDomains.js @@ -16,6 +16,7 @@ export default class ResponseTime extends React.PureComponent {
{data.partition && data.partition.map((item, i) => diff --git a/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js b/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js index 87cf5478f..7bfc0cfd9 100644 --- a/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js +++ b/frontend/app/components/Dashboard/Widgets/SlowestImages/SlowestImages.js @@ -41,6 +41,7 @@ export default class SlowestImages extends React.PureComponent {
diff --git a/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js b/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js index 956174cba..1ac489588 100644 --- a/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js +++ b/frontend/app/components/Dashboard/Widgets/TimeToRender/TimeToRender.js @@ -59,6 +59,7 @@ export default class TimeToRender extends React.PureComponent { { + setDashboards(filterList(dashboards, dashboardsSearch, ['name', 'owner', 'description'])) + }, [dashboardsSearch]) + + const list = dashboardsSearch !== '' ? shownDashboards : dashboards; + const lenth = list.length; + + return ( + + +
You haven't created any dashboards yet
+ + } + > +
+
+
Title
+
Visibility
+
Created
+
+ + {sliceListPerPage(list, dashboardStore.page - 1, dashboardStore.pageSize).map((dashboard: any) => ( + + + + ))} +
+ +
+
+ Showing {Math.min(list.length, dashboardStore.pageSize)} out of {list.length} Dashboards +
+ dashboardStore.updateKey('page', page)} + limit={dashboardStore.pageSize} + debounceRequest={100} + /> +
+
+ ); +} + +export default observer(DashboardList); diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx new file mode 100644 index 000000000..61aeb15b9 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { connect } from 'react-redux'; +import { IDashboard } from 'App/mstore/types/dashboard'; +import { checkForRecent } from 'App/date'; +import { withSiteId, dashboardSelected } from 'App/routes'; +import { useStore } from 'App/mstore'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +interface Props extends RouteComponentProps { + dashboard: IDashboard; + siteId: string; +} + +function DashboardListItem(props: Props) { + const { dashboard, siteId, history } = props; + const { dashboardStore } = useStore(); + + const onItemClick = () => { + dashboardStore.selectDashboardById(dashboard.dashboardId); + const path = withSiteId(dashboardSelected(dashboard.dashboardId), siteId); + history.push(path); + }; + return ( +
+
+
+
+
+ +
+
{dashboard.name}
+
+
+ {/*
*/} +
+
+ + {dashboard.isPublic ? 'Team' : 'Private'} +
+
+
{checkForRecent(dashboard.createdAt, 'LLL dd, yyyy, hh:mm a')}
+
+ {dashboard.description ?
{dashboard.description}
: null} +
+ ); +} +// @ts-ignore +export default connect((state) => ({ siteId: state.getIn(['site', 'siteId']) }))(withRouter(DashboardListItem)); diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx new file mode 100644 index 000000000..a3b13f1d3 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx @@ -0,0 +1,36 @@ +import React, { useEffect, useState } from 'react'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; +import { Icon } from 'UI'; +import { debounce } from 'App/utils'; + +let debounceUpdate: any = () => {} + +function DashboardSearch() { + const { dashboardStore } = useStore(); + const [query, setQuery] = useState(dashboardStore.dashboardsSearch); + useEffect(() => { + debounceUpdate = debounce((key: string, value: any) => dashboardStore.updateKey(key, value), 500); + }, []) + + // @ts-ignore + const write = ({ target: { value } }) => { + setQuery(value); + debounceUpdate('dashboardsSearch', value); + } + + return ( +
+ + +
+ ); +} + +export default observer(DashboardSearch); diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx new file mode 100644 index 000000000..c9b98a745 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Button, PageTitle, Icon } from 'UI'; +import withPageTitle from 'HOCs/withPageTitle'; +import { useStore } from 'App/mstore'; +import { withSiteId } from 'App/routes'; + +import DashboardList from './DashboardList'; +import DashboardSearch from './DashboardSearch'; + +function DashboardsView({ history, siteId }: { history: any, siteId: string }) { + const { dashboardStore } = useStore(); + + const onAddDashboardClick = () => { + dashboardStore.initDashboard(); + dashboardStore + .save(dashboardStore.dashboardInstance) + .then(async (syncedDashboard) => { + dashboardStore.selectDashboardById(syncedDashboard.dashboardId); + history.push(withSiteId(`/dashboard/${syncedDashboard.dashboardId}`, siteId)) + }) + } + + return ( +
+
+
+ +
+ +
+ +
+
+
+ + A dashboard is a custom visualization using your OpenReplay data. +
+ +
+ ); +} + +export default withPageTitle('Dashboards - OpenReplay')(DashboardsView); diff --git a/frontend/app/components/Dashboard/components/DashboardList/index.ts b/frontend/app/components/Dashboard/components/DashboardList/index.ts new file mode 100644 index 000000000..61e485dc9 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/index.ts @@ -0,0 +1 @@ +export { default } from './DashboardsView'; diff --git a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx index d612efe0b..cca7c51c1 100644 --- a/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx +++ b/frontend/app/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection.tsx @@ -6,9 +6,16 @@ import cn from 'classnames'; import { useStore } from 'App/mstore'; import { Loader } from 'UI'; -function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds }) { +interface IWiProps { + category: Record + onClick: (category: Record) => void + isSelected: boolean + selectedWidgetIds: string[] +} + +export function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds }: IWiProps) { const selectedCategoryWidgetsCount = useObserver(() => { - return category.widgets.filter(widget => selectedWidgetIds.includes(widget.metricId)).length; + return category.widgets.filter((widget: any) => selectedWidgetIds.includes(widget.metricId)).length; }); return (
void; } -function DashboardModal(props) { +function DashboardModal(props: Props) { const { history, siteId, dashboardId } = props; const { dashboardStore } = useStore(); const selectedWidgetsCount = useObserver(() => dashboardStore.selectedWidgets.length); const { hideModal } = useModal(); - const loadingTemplates = useObserver(() => dashboardStore.loadingTemplates); const dashboard = useObserver(() => dashboardStore.dashboardInstance); const loading = useObserver(() => dashboardStore.isSaving); diff --git a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx index 4df856619..f18415f27 100644 --- a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx +++ b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Switch, Route } from 'react-router'; -import { withRouter } from 'react-router-dom'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import { metrics, @@ -16,19 +16,20 @@ import DashboardView from '../DashboardView'; import MetricsView from '../MetricsView'; import WidgetView from '../WidgetView'; import WidgetSubDetailsView from '../WidgetSubDetailsView'; +import DashboardsView from '../DashboardList'; -function DashboardViewSelected({ siteId, dashboardId }) { +function DashboardViewSelected({ siteId, dashboardId }: { siteId: string, dashboardId: string }) { return ( ) } -interface Props { - history: any +interface Props extends RouteComponentProps { match: any } function DashboardRouter(props: Props) { - const { match: { params: { siteId, dashboardId, metricId } } } = props; + const { match: { params: { siteId, dashboardId } }, history } = props; + return (
@@ -44,8 +45,8 @@ function DashboardRouter(props: Props) { - - + + diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 00b462bbd..2c4a4091e 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -1,113 +1,40 @@ -//@ts-nocheck -import { useObserver } from 'mobx-react-lite'; import React from 'react'; -import { SideMenuitem, SideMenuHeader, Icon, Popup, Button } from 'UI'; -import { useStore } from 'App/mstore'; -import { withRouter } from 'react-router-dom'; -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'; +import { SideMenuitem, SideMenuHeader } from 'UI'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { withSiteId, metrics, dashboard } from 'App/routes'; import { connect } from 'react-redux'; import { compose } from 'redux' import { setShowAlerts } from 'Duck/dashboard'; -// import stl from 'Shared/MainSearchBar/mainSearchBar.module.css'; -const SHOW_COUNT = 8; - -interface Props { +interface Props extends RouteComponentProps { siteId: string history: any setShowAlerts: (show: boolean) => void } -function DashboardSideMenu(props: RouteComponentProps) { +function DashboardSideMenu(props: Props) { const { history, siteId, setShowAlerts } = props; - const { hideModal, showModal } = useModal(); - const { dashboardStore } = useStore(); - const dashboardId = useObserver(() => dashboardStore.selectedDashboard?.dashboardId); - const dashboardsPicked = useObserver(() => dashboardStore.dashboards.slice(0, SHOW_COUNT)); - const remainingDashboardsCount = dashboardStore.dashboards.length - SHOW_COUNT; const isMetric = history.location.pathname.includes('metrics'); + const isDashboards = history.location.pathname.includes('dashboard'); - const redirect = (path) => { + const redirect = (path: string) => { history.push(path); } - const onItemClick = (dashboard) => { - dashboardStore.selectDashboardById(dashboard.dashboardId); - const path = withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(siteId)); - history.push(path); - }; - - const onAddDashboardClick = (e) => { - dashboardStore.initDashboard(); - showModal(, { right: true }) - } - - const togglePinned = (dashboard, e) => { - e.stopPropagation(); - dashboardStore.updatePinned(dashboard.dashboardId); - } - - return useObserver(() => ( + return (
- <> - - Create - - - } + text="Preferences" /> - {dashboardsPicked.map((item: any) => ( - onItemClick(item)} - className="group" - leading = {( -
- {item.isPublic && ( - -
-
- )} - {item.isPinned &&
} - {!item.isPinned && ( - -
togglePinned(item, e)} - > - -
-
- )} -
- )} - /> - ))} -
- {remainingDashboardsCount > 0 && ( -
showModal(, {})} - > - {remainingDashboardsCount} More -
- )} -
+
+ redirect(withSiteId(dashboard(), siteId))} + /> +
) { />
- )); + ); } export default compose( withRouter, connect(null, { setShowAlerts }), -)(DashboardSideMenu) as React.FunctionComponent> +)(DashboardSideMenu) diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css new file mode 100644 index 000000000..42045607f --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css @@ -0,0 +1,5 @@ +.tooltipContainer { + & > tippy-popper > tippy-tooltip { + padding: 0!important; + } +} diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index 108d961a5..df8e198d8 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -1,22 +1,23 @@ -import React, { useEffect } from "react"; -import { observer } from "mobx-react-lite"; -import { useStore } from "App/mstore"; -import { Button, PageTitle, Loader, NoContent } from "UI"; -import { withSiteId } from "App/routes"; -import withModal from "App/components/Modal/withModal"; -import DashboardWidgetGrid from "../DashboardWidgetGrid"; -import { confirm } from "UI"; -import { withRouter, RouteComponentProps } from "react-router-dom"; -import { useModal } from "App/components/Modal"; -import DashboardModal from "../DashboardModal"; -import DashboardEditModal from "../DashboardEditModal"; -import AlertFormModal from "App/components/Alerts/AlertFormModal"; -import withPageTitle from "HOCs/withPageTitle"; -import withReport from "App/components/hocs/withReport"; -import DashboardOptions from "../DashboardOptions"; -import SelectDateRange from "Shared/SelectDateRange"; -import DashboardIcon from "../../../../svg/dashboard-icn.svg"; -import { Tooltip } from "react-tippy"; +import React, { useEffect } from 'react'; +import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; +import { Button, PageTitle, Loader } from 'UI'; +import { withSiteId } from 'App/routes'; +import withModal from 'App/components/Modal/withModal'; +import DashboardWidgetGrid from '../DashboardWidgetGrid'; +import { confirm } from 'UI'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { useModal } from 'App/components/Modal'; +import DashboardModal from '../DashboardModal'; +import DashboardEditModal from '../DashboardEditModal'; +import AlertFormModal from 'App/components/Alerts/AlertFormModal'; +import withPageTitle from 'HOCs/withPageTitle'; +import withReport from 'App/components/hocs/withReport'; +import DashboardOptions from '../DashboardOptions'; +import SelectDateRange from 'Shared/SelectDateRange'; +import { Tooltip } from 'react-tippy'; +import Breadcrumb from 'Shared/Breadcrumb'; +import AddMetricContainer from '../DashboardWidgetGrid/AddMetricContainer'; interface IProps { siteId: string; @@ -29,63 +30,53 @@ type Props = IProps & RouteComponentProps; function DashboardView(props: Props) { const { siteId, dashboardId } = props; const { dashboardStore } = useStore(); + const { showModal } = useModal(); + const [focusTitle, setFocusedInput] = React.useState(true); const [showEditModal, setShowEditModal] = React.useState(false); - const { showModal } = useModal(); const showAlertModal = dashboardStore.showAlertModal; const loading = dashboardStore.fetchingDashboard; - const dashboards = dashboardStore.dashboards; const dashboard: any = dashboardStore.selectedDashboard; const period = dashboardStore.period; const queryParams = new URLSearchParams(props.location.search); + const trimQuery = () => { + if (!queryParams.has('modal')) return; + queryParams.delete('modal'); + props.history.replace({ + search: queryParams.toString(), + }); + }; + const pushQuery = () => { + if (!queryParams.has('modal')) props.history.push('?modal=addMetric'); + }; + + useEffect(() => { + if (queryParams.has('modal')) { + onAddWidgets(); + trimQuery(); + } + }, []); + + useEffect(() => { + const isExists = dashboardStore.getDashboardById(dashboardId); + if (!isExists) { + props.history.push(withSiteId(`/dashboard`, siteId)); + } + }, [dashboardId]); + useEffect(() => { if (!dashboard || !dashboard.dashboardId) return; dashboardStore.fetch(dashboard.dashboardId); }, [dashboard]); - const trimQuery = () => { - if (!queryParams.has("modal")) return; - queryParams.delete("modal"); - props.history.replace({ - search: queryParams.toString(), - }); - }; - const pushQuery = () => { - if (!queryParams.has("modal")) props.history.push("?modal=addMetric"); - }; - - useEffect(() => { - if (!dashboardId || (!dashboard && dashboardStore.dashboards.length > 0)) dashboardStore.selectDefaultDashboard(); - - if (queryParams.has("modal")) { - onAddWidgets(); - trimQuery(); - } - }, []); - useEffect(() => { - dashboardStore.selectDefaultDashboard(); - }, [siteId]) - const onAddWidgets = () => { dashboardStore.initDashboard(dashboard); - showModal( - , - { right: true } - ); + showModal(, { right: true }); }; - const onAddDashboardClick = () => { - dashboardStore.initDashboard(); - showModal(, { right: true }) - } - const onEdit = (isTitle: boolean) => { dashboardStore.initDashboard(dashboard); setFocusedInput(isTitle); @@ -95,141 +86,99 @@ function DashboardView(props: Props) { const onDelete = async () => { if ( await confirm({ - header: "Confirm", - confirmButton: "Yes, delete", + header: 'Confirm', + confirmButton: 'Yes, delete', confirmation: `Are you sure you want to permanently delete this Dashboard?`, }) ) { dashboardStore.deleteDashboard(dashboard).then(() => { - dashboardStore.selectDefaultDashboard().then( - ({ dashboardId }) => { - props.history.push( - withSiteId(`/dashboard/${dashboardId}`, siteId) - ); - }, - () => { - props.history.push(withSiteId("/dashboard", siteId)); - } - ); + props.history.push(withSiteId(`/dashboard`, siteId)); }); } }; + if (!dashboard) return null; + return ( - - - - Gather and analyze
important metrics in one - place. -
- - } - size="small" - subtext={ - - } - > -
- setShowEditModal(false)} - focusTitle={focusTitle} - /> -
-
- + setShowEditModal(false)} focusTitle={focusTitle} /> + +
+
+ + {dashboard?.name} + + } + onDoubleClick={() => onEdit(true)} + className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer" + actionButton={ + /* @ts-ignore */ +
} > - {dashboard?.name} + - } - onDoubleClick={() => onEdit(true)} - className="mr-3 select-none hover:border-dotted hover:border-b border-gray-medium cursor-pointer" - actionButton={ - - } + } + /> +
+
+
+ dashboardStore.setPeriod(period)} + right={true} />
-
-
- - dashboardStore.setPeriod(period) - } - right={true} - /> -
-
-
- -
+
+
+
-
-

- {dashboard?.description} -

-
- - - dashboardStore.updateKey("showAlertModal", false) - } - />
- +
+ {/* @ts-ignore */} + +

onEdit(false)} + > + {dashboard?.description || 'Describe the purpose of this dashboard'} +

+
+
+ + dashboardStore.updateKey('showAlertModal', false)} /> +
); } -export default withPageTitle("Dashboards - OpenReplay")( - withReport(withRouter(withModal(observer(DashboardView)))) -); +export default withPageTitle('Dashboards - OpenReplay')(withReport(withRouter(withModal(observer(DashboardView))))); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx new file mode 100644 index 000000000..481ce70c2 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetric.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { Button } from 'UI'; +import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper'; +import { useStore } from 'App/mstore'; +import { useModal } from 'App/components/Modal'; +import { dashboardMetricCreate, withSiteId } from 'App/routes'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +interface IProps extends RouteComponentProps { + metrics: any[]; + siteId: string; + title: string; + description: string; +} + +function AddMetric({ metrics, history, siteId, title, description }: IProps) { + const { dashboardStore } = useStore(); + const { hideModal } = useModal(); + + const dashboard = dashboardStore.selectedDashboard; + const selectedWidgetIds = dashboardStore.selectedWidgets.map((widget: any) => widget.metricId); + const queryParams = new URLSearchParams(location.search); + + const onSave = () => { + if (selectedWidgetIds.length === 0) return; + dashboardStore + .save(dashboard) + .then(async (syncedDashboard) => { + if (dashboard.exists()) { + await dashboardStore.fetch(dashboard.dashboardId); + } + dashboardStore.selectDashboardById(syncedDashboard.dashboardId); + }) + .then(hideModal); + }; + + const onCreateNew = () => { + const path = withSiteId(dashboardMetricCreate(dashboard.dashboardId), siteId); + if (!queryParams.has('modal')) history.push('?modal=addMetric'); + history.push(path); + hideModal(); + }; + + return ( +
+
+
+
+

{title}

+
{description}
+
+ + + +
+ +
+ {metrics ? metrics.map((metric: any) => ( + dashboardStore.toggleWidgetSelection(metric)} + /> + )) : ( +
No custom metrics created.
+ )} +
+ +
+
+ {'Selected '} + {selectedWidgetIds.length} + {' out of '} + {metrics.length} +
+ +
+
+
+ ); +} + +export default withRouter(observer(AddMetric)); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx new file mode 100644 index 000000000..c13c27763 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddMetricContainer.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { Icon } from 'UI'; +import { useModal } from 'App/components/Modal'; +import { useStore } from 'App/mstore'; +import AddMetric from './AddMetric'; +import AddPredefinedMetric from './AddPredefinedMetric'; +import cn from 'classnames'; + +interface AddMetricButtonProps { + iconName: string; + title: string; + description: string; + isPremade?: boolean; + isPopup?: boolean; + onClick: () => void; +} + +function AddMetricButton({ iconName, title, description, onClick, isPremade, isPopup }: AddMetricButtonProps) { + return ( +
+
+ +
+
+
{title}
+
+ {description} +
+
+
+ ); +} + +function AddMetricContainer({ siteId, isPopup }: any) { + const { showModal } = useModal(); + const [categories, setCategories] = React.useState[]>([]); + const { dashboardStore } = useStore(); + + React.useEffect(() => { + dashboardStore?.fetchTemplates(true).then((cats) => setCategories(cats)); + }, []); + + const onAddCustomMetrics = () => { + dashboardStore.initDashboard(dashboardStore.selectedDashboard); + showModal( + category.name === 'custom')?.widgets} + />, + { right: true } + ); + }; + + const onAddPredefinedMetrics = () => { + dashboardStore.initDashboard(dashboardStore.selectedDashboard); + showModal( + category.name !== 'custom')} + />, + { right: true } + ); + }; + + const classes = isPopup + ? 'bg-white border rounded p-4 grid grid-rows-2 gap-4' + : 'bg-white border border-dashed hover:!border-gray-medium rounded p-8 grid grid-cols-2 gap-8'; + return ( +
+ + +
+ ); +} + +export default observer(AddMetricContainer); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx new file mode 100644 index 000000000..48bf85079 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/AddPredefinedMetric.tsx @@ -0,0 +1,145 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { Button } from 'UI'; +import WidgetWrapper from 'App/components/Dashboard/components/WidgetWrapper'; +import { useStore } from 'App/mstore'; +import { useModal } from 'App/components/Modal'; +import { dashboardMetricCreate, withSiteId } from 'App/routes'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { WidgetCategoryItem } from 'App/components/Dashboard/components/DashboardMetricSelection/DashboardMetricSelection'; + +interface IProps extends RouteComponentProps { + categories: Record[]; + siteId: string; + title: string; + description: string; +} + +function AddPredefinedMetric({ categories, history, siteId, title, description }: IProps) { + const { dashboardStore } = useStore(); + const { hideModal } = useModal(); + const [allCheck, setAllCheck] = React.useState(false); + const [activeCategory, setActiveCategory] = React.useState>(); + + const scrollContainer = React.useRef(null); + + const dashboard = dashboardStore.selectedDashboard; + const selectedWidgetIds = dashboardStore.selectedWidgets.map((widget: any) => widget.metricId); + const queryParams = new URLSearchParams(location.search); + const totalMetricCount = categories.reduce((acc, category) => acc + category.widgets.length, 0); + + React.useEffect(() => { + dashboardStore?.fetchTemplates(true).then((categories) => { + const defaultCategory = categories.filter((category: any) => category.name !== 'custom')[0]; + setActiveCategory(defaultCategory); + }); + }, []); + + React.useEffect(() => { + if (scrollContainer.current) { + scrollContainer.current.scrollTop = 0; + } + }, [activeCategory, scrollContainer.current]); + + const handleWidgetCategoryClick = (category: any) => { + setActiveCategory(category); + setAllCheck(false); + }; + + const onSave = () => { + if (selectedWidgetIds.length === 0) return; + dashboardStore + .save(dashboard) + .then(async (syncedDashboard) => { + if (dashboard.exists()) { + await dashboardStore.fetch(dashboard.dashboardId); + } + dashboardStore.selectDashboardById(syncedDashboard.dashboardId); + }) + .then(hideModal); + }; + + const onCreateNew = () => { + const path = withSiteId(dashboardMetricCreate(dashboard.dashboardId), siteId); + if (!queryParams.has('modal')) history.push('?modal=addMetric'); + history.push(path); + hideModal(); + }; + + const toggleAllMetrics = ({ target: { checked } }: any) => { + setAllCheck(checked); + if (checked) { + dashboardStore.selectWidgetsByCategory(activeCategory.name); + } else { + dashboardStore.removeSelectedWidgetByCategory(activeCategory); + } + }; + + return ( +
+
+
+
+

{title}

+
{description}
+
+ + +
+ +
+
+
+ {activeCategory && + categories.map((category) => ( + + ))} +
+
+ +
+ {activeCategory && + activeCategory.widgets.map((metric: any) => ( + dashboardStore.toggleWidgetSelection(metric)} + /> + ))} +
+
+ +
+
+ {'Selected '} + {selectedWidgetIds.length} + {' out of '} + {totalMetricCount} +
+ +
+
+
+ ); +} + +export default withRouter(observer(AddPredefinedMetric)); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx index 442ee46e6..74c1d5c3d 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { useStore } from 'App/mstore'; import WidgetWrapper from '../WidgetWrapper'; -import { NoContent, Button, Loader } from 'UI'; +import { NoContent, Loader } from 'UI'; import { useObserver } from 'mobx-react-lite'; +import AddMetricContainer from './AddMetricContainer' interface Props { siteId: string, @@ -18,16 +19,14 @@ function DashboardWidgetGrid(props: Props) { const list: any = useObserver(() => dashboard?.widgets); return useObserver(() => ( + // @ts-ignore Build your dashboard} subtext={ -
-

Metrics helps you visualize trends from sessions captured by OpenReplay

- -
+
} >
@@ -42,6 +41,7 @@ function DashboardWidgetGrid(props: Props) { isWidget={true} /> ))} +
diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx index 492a41bd5..23bcedb2e 100644 --- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx +++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx @@ -1,43 +1,16 @@ import React from 'react'; -import { Icon, NoContent, Label, Link, Pagination, Popup } from 'UI'; -import { checkForRecent, formatDateTimeDefault, convertTimestampToUtcTimestamp } from 'App/date'; -import { getIcon } from 'react-toastify/dist/components'; +import { Icon, Link } from 'UI'; +import { checkForRecent } from 'App/date'; +import { Tooltip } from 'react-tippy' +import { withRouter, RouteComponentProps } from 'react-router-dom'; +import { withSiteId } from 'App/routes'; -interface Props { +interface Props extends RouteComponentProps { metric: any; -} - -function DashboardLink({ dashboards}: any) { - return ( - dashboards.map((dashboard: any) => ( - - -
-
- -
- {dashboard.name} -
- -
- )) - ); + siteId: string; } function MetricTypeIcon({ type }: any) { - const PopupWrapper = (props: any) => { - return ( - {type}
} - position="top center" - on="hover" - hideOnScroll={true} - > - {props.children} - - ); - } - const getIcon = () => { switch (type) { case 'funnel': @@ -50,45 +23,47 @@ function MetricTypeIcon({ type }: any) { } return ( - -
- + {type}
} + position="top" + arrow + > +
+
-
+ ) } -function MetricListItem(props: Props) { - const { metric } = props; - + +function MetricListItem(props: Props) { + const { metric, history, siteId } = props; + + const onItemClick = () => { + const path = withSiteId(`/metrics/${metric.metricId}`, siteId); + history.push(path); + }; return ( -
+
- {/*
- -
*/} - +
{metric.name} - +
- {/*
*/} -
- -
{metric.owner}
-
+
{metric.isPublic ? 'Team' : 'Private'}
-
{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}
+
{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}
); } -export default MetricListItem; +export default withRouter(MetricListItem); diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx index 3cc6dff40..e64026822 100644 --- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx +++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx @@ -1,26 +1,24 @@ import { useObserver } from 'mobx-react-lite'; import React, { useEffect } from 'react'; -import { NoContent, Pagination } from 'UI'; +import { NoContent, Pagination, Icon } from 'UI'; import { useStore } from 'App/mstore'; -import { getRE } from 'App/utils'; +import { filterList } from 'App/utils'; import MetricListItem from '../MetricListItem'; import { sliceListPerPage } from 'App/utils'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import { IWidget } from 'App/mstore/types/widget'; -interface Props { } -function MetricsList(props: Props) { +function MetricsList({ siteId }: { siteId: string }) { const { metricStore } = useStore(); const metrics = useObserver(() => metricStore.metrics); const metricsSearch = useObserver(() => metricStore.metricsSearch); - const filterList = (list) => { - const filterRE = getRE(metricsSearch, 'i'); - let _list = list.filter(w => { - const dashbaordNames = w.dashboards.map(d => d.name).join(' '); - return filterRE.test(w.name) || filterRE.test(w.metricType) || filterRE.test(w.owner) || filterRE.test(dashbaordNames); - }); - return _list + + const filterByDashboard = (item: IWidget, searchRE: RegExp) => { + const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' ') + return searchRE.test(dashboardsStr) } - const list: any = metricsSearch !== '' ? filterList(metrics) : metrics; + const list = metricsSearch !== '' + ? filterList(metrics, metricsSearch, ['name', 'metricType', 'owner'], filterByDashboard) + : metrics; const lenth = list.length; useEffect(() => { @@ -32,29 +30,30 @@ function MetricsList(props: Props) { show={lenth === 0} title={
- -
No data available.
+ +
You haven't created any metrics yet
} > -
-
-
Metric
- {/*
Type
*/} -
Dashboards
+
+
+
Title
Owner
-
Visibility
-
Last Modified
+
Visibility
+
Last Modified
{sliceListPerPage(list, metricStore.page - 1, metricStore.pageSize).map((metric: any) => ( - + ))}
-
+
+
+ Showing {Math.min(list.length, metricStore.pageSize)} out of {list.length} metrics +
metricStore.updateKey(key, value), 500); }, []) - const write = ({ target: { name, value } }) => { + const write = ({ target: { value } }) => { setQuery(value); debounceUpdate('metricsSearch', value); } @@ -23,7 +23,7 @@ function MetricsSearch(props) { @@ -31,4 +31,4 @@ function MetricsSearch(props) { )); } -export default MetricsSearch; \ No newline at end of file +export default MetricsSearch; diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx index a8c1d96c4..85cefe70d 100644 --- a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx @@ -9,27 +9,28 @@ import { useObserver } from 'mobx-react-lite'; interface Props{ siteId: number; } -function MetricsView(props: Props) { - const { siteId } = props; +function MetricsView({ siteId }: Props) { const { metricStore } = useStore(); - const metricsCount = useObserver(() => metricStore.metrics.length); React.useEffect(() => { metricStore.fetchList(); }, []); return useObserver(() => ( -
+
- {metricsCount}
-
+
- +
+ + Create custom Metrics to capture key interactions and track KPIs. +
+
)); } diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 31e73fda3..7a6df145f 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -179,7 +179,7 @@ function WidgetChart(props: Props) { } return ( - {renderChart()} +
{renderChart()}
); } diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index 88e0a59b4..685da85a5 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -1,14 +1,13 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions'; import { FilterKey } from 'Types/filter/filterType'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; -import { Button, Icon } from 'UI' +import { Button, Icon, SegmentSelection } from 'UI' import FilterSeries from '../FilterSeries'; import { confirm, Popup } from 'UI'; import Select from 'Shared/Select' import { withSiteId, dashboardMetricDetails, metricDetails } from 'App/routes' -import DashboardSelectionModal from '../DashboardSelectionModal/DashboardSelectionModal'; interface Props { history: any; @@ -16,9 +15,15 @@ interface Props { onDelete: () => void; } +const metricIcons = { + timeseries: 'graph-up', + table: 'table', + funnel: 'funnel', +} + function WidgetForm(props: Props) { - const [showDashboardSelectionModal, setShowDashboardSelectionModal] = useState(false); - const { history, match: { params: { siteId, dashboardId, metricId } } } = props; + + const { history, match: { params: { siteId, dashboardId } } } = props; const { metricStore, dashboardStore } = useStore(); const dashboards = dashboardStore.dashboards; const isSaving = useObserver(() => metricStore.isSaving); @@ -65,13 +70,15 @@ function WidgetForm(props: Props) { metricStore.merge(obj); }; + const onSelect = (_: any, option: Record) => writeOption({ value: { value: option.value }, name: option.name}) + const onSave = () => { const wasCreating = !metric.exists() metricStore.save(metric, dashboardId) .then((metric: any) => { if (wasCreating) { if (parseInt(dashboardId) > 0) { - history.replace(withSiteId(dashboardMetricDetails(parseInt(dashboardId), metric.metricId), siteId)); + history.replace(withSiteId(dashboardMetricDetails(dashboardId, metric.metricId), siteId)); } else { history.replace(withSiteId(metricDetails(metric.metricId), siteId)); } @@ -94,11 +101,15 @@ function WidgetForm(props: Props) {
-