From d604f9920b5055cd8bf3819bdaead399e8d1b9bf Mon Sep 17 00:00:00 2001 From: Delirium Date: Thu, 27 Jun 2024 19:47:34 +0200 Subject: [PATCH] feat ui: dashboards redesign (#2230) * feat ui: dashboards redesign start * more cards * fix ui: more different cards... * feat ui: finish cards, all trigger, all icons * change(ui): added missin const * feature(ui): new dashboard modal * feature(ui): new dashboard modal * change(ui): new cards * change(ui): dashboard redesign * change(ui): dashboard redesign * change(ui): dashboard redesign * change(ui): modal context and alert form * change(ui): table card show more with modal * change(ui): examples * change(ui): example categorize and other improvements * change(ui): example categorize and other improvements * change(ui): performance cards * change(ui): insights card * Various style updates in dashboards and other pages. (#2308) * Various minor style updates * Various style improvements * Update ExampleCards.tsx * change(ui): fixed an issue with card create * change(ui): fixed an issue with card create * change(ui): default filters and events order * change(ui): random data * Dashboards redesign - improvments (#2313) * Various minor style updates * Various style improvements * Update ExampleCards.tsx * various minor improvements in dashbaords. * revised dashboard widget header * change(ui): sessions by user * change(ui): funnel example * change(ui): modal height and scroll * change(ui): example cards with data * change(ui): example cards with data * change(ui): funnel bar text color * change(ui): example cards overlay click * change(ui): path analysis filter card --------- Co-authored-by: Shekar Siri Co-authored-by: Sudheer Salavadi --- frontend/app/Router.tsx | 331 ++++---- frontend/app/components/Alerts/AlertForm.js | 698 ++++++++-------- .../Alerts/AlertFormModal/AlertFormModal.tsx | 19 +- .../Assist/RecordingsList/Recordings.tsx | 2 +- .../Client/Audit/AuditView/AuditView.tsx | 2 +- .../Client/CustomFields/CustomFields.js | 2 +- .../Client/Integrations/Integrations.tsx | 12 +- .../app/components/Client/Modules/Modules.tsx | 4 +- .../Client/ProfileSettings/ProfileSettings.js | 2 +- .../app/components/Client/Roles/Roles.tsx | 2 +- .../Client/SessionsListingSettings.tsx | 2 +- .../app/components/Client/Sites/Sites.tsx | 2 +- .../app/components/Client/Users/UsersView.tsx | 2 +- .../components/Client/Webhooks/Webhooks.tsx | 2 +- .../Dashboard/Widgets/CardSessionsByList.tsx | 60 ++ .../CustomMetriLineChart/index.ts | 1 - .../CustomMetricLineChart.tsx} | 48 +- .../CustomMetricLineChart/index.ts | 1 + .../CustomMetricOverviewChart.tsx | 55 +- .../CustomMetricTable/CustomMetricTable.tsx | 78 +- .../CustomMetricsWidgets/SessionsBy.tsx | 88 ++ .../CallWithErrors/CallWithErrors.tsx | 10 +- .../ErrorsPerDomain/ErrorsPerDomain.tsx | 9 +- .../Dashboard/Widgets/common/Styles.js | 147 ++-- .../components/AddCardSelectionModal.tsx | 62 ++ .../components/AddToDashboardButton.tsx | 64 ++ .../components/Alerts/AlertsView.tsx | 2 +- .../Dashboard/components/CreateCardButton.tsx | 32 + .../components/CreateDashboardButton.tsx | 25 + .../DashboardEditModal/DashboardEditModal.tsx | 4 +- .../DashboardHeader/DashboardHeader.tsx | 218 +++-- .../DashboardList/DashboardList.tsx | 175 ++-- .../DashboardList/DashboardListItem.tsx | 48 -- .../DashboardList/DashboardSearch.tsx | 21 +- .../DashboardList/DashboardsView.tsx | 14 +- .../components/DashboardList/Header.tsx | 82 +- .../NewDashModal/CardsLibrary.tsx | 88 ++ .../DashboardList/NewDashModal/CreateCard.tsx | 102 +++ .../NewDashModal/ExampleCards.tsx | 761 ++++++++++++++++++ .../NewDashModal/Examples/AreaChartCard.tsx | 82 ++ .../NewDashModal/Examples/BarChart.tsx | 70 ++ .../NewDashModal/Examples/Bars.tsx | 61 ++ .../Examples/CallsWithErrorsExample.tsx | 23 + .../NewDashModal/Examples/Count.tsx | 266 ++++++ .../NewDashModal/Examples/ExCard.tsx | 30 + .../NewDashModal/Examples/Funnel.tsx | 67 ++ .../NewDashModal/Examples/InsightsExample.tsx | 56 ++ .../NewDashModal/Examples/Path.tsx | 59 ++ .../NewDashModal/Examples/PerfBreakdown.tsx | 105 +++ .../Examples/SessionsBy/ByBrowser.tsx | 52 ++ .../Examples/SessionsBy/ByCountry.tsx | 50 ++ .../Examples/SessionsBy/ByIssues.tsx | 46 ++ .../Examples/SessionsBy/BySystem.tsx | 51 ++ .../Examples/SessionsBy/ByUrl.tsx | 104 +++ .../Examples/SessionsBy/ByUser.tsx | 53 ++ .../Examples/SessionsBy/Component.tsx | 67 ++ .../Examples/SessionsByErrors.tsx | 15 + .../Examples/SessionsByIssues.tsx | 15 + .../NewDashModal/Examples/SlowestDomain.tsx | 86 ++ .../Examples/Tabs/CoreWebVitals.tsx | 20 + .../Examples/Tabs/PerformanceMonitoring.tsx | 20 + .../Examples/Tabs/ProductAnalytics.tsx | 20 + .../Examples/Tabs/WebAnalytics.tsx | 20 + .../NewDashModal/Examples/Trend.tsx | 43 + .../NewDashModal/Examples/WebVital.tsx | 56 ++ .../NewDashModal/NewDashboardModal.tsx | 60 ++ .../DashboardList/NewDashModal/Option.tsx | 16 + .../DashboardList/NewDashModal/SelectCard.tsx | 137 ++++ .../DashboardList/NewDashModal/index.ts | 1 + .../DashboardOptions/DashboardOptions.tsx | 3 +- .../DashboardSelectionModal.tsx | 30 +- .../DashboardView/DashboardView.module.css | 2 +- .../DashboardView/DashboardView.tsx | 169 ++-- .../DashboardWidgetGrid.tsx | 193 ++--- .../components/FilterSeries/AddStepButton.tsx | 33 + .../components/FilterSeries/FilterSeries.tsx | 257 +++--- .../Funnels/FunnelIssues/FunnelIssues.tsx | 2 +- .../MetricViewHeader/MetricViewHeader.tsx | 249 +++--- .../components/MetricsView/MetricsView.tsx | 2 +- .../Sessions/SessionList/SessionList.tsx | 13 - .../components/Sessions/SessionList/index.ts | 1 - .../SessionListItem/SessionListItem.tsx | 14 - .../Sessions/SessionListItem/index.ts | 1 - .../Sessions/SessionWidget/SessionWidget.tsx | 12 - .../Sessions/SessionWidget/index.ts | 1 - .../components/WidgetChart/WidgetChart.tsx | 17 +- .../WidgetDateRange/WidgetDateRange.tsx | 25 +- .../components/WidgetForm/CardBuilder.tsx | 330 ++++++++ .../components/WidgetForm/WidgetForm.tsx | 661 +++++++-------- .../components/WidgetForm/WidgetFormNew.tsx | 179 ++++ .../WidgetPredefinedChart.tsx | 4 +- .../WidgetPreview/WidgetPreview.tsx | 239 +++--- .../components/WidgetView/CardViewMenu.tsx | 99 +++ .../components/WidgetView/WidgetView.tsx | 134 +-- .../WidgetView/WidgetViewHeader.tsx | 48 ++ .../components/WidgetWrapper/AlertButton.tsx | 35 +- .../components/WidgetWrapper/CardMenu.tsx | 48 ++ .../WidgetWrapper/WidgetWrapper.tsx | 2 +- .../WidgetWrapper/WidgetWrapperNew.tsx | 159 ++++ .../WidgetWrapper/widgetWrapper.module.css | 3 +- .../ForgotPassword/ForgotPassword.tsx | 2 +- .../Funnels/FunnelWidget/FunnelBar.tsx | 240 +++--- .../Funnels/FunnelWidget/FunnelWidget.tsx | 2 +- frontend/app/components/Login/Login.tsx | 2 +- frontend/app/components/ModalContext.tsx | 64 ++ .../UsabilityTesting/UsabilityTesting.tsx | 6 +- .../FilterAutoComplete/FilterAutoComplete.tsx | 2 +- .../shared/Filters/FilterList/EventsOrder.tsx | 55 ++ .../shared/Filters/FilterList/FilterList.tsx | 428 +++++----- .../Filters/FilterModal/FilterModal.tsx | 4 +- .../FilterSelection/FilterSelection.tsx | 2 +- .../FilterValueDropdown.tsx | 2 +- .../Insights/SankeyChart/CustomLink.tsx | 8 +- .../Insights/SankeyChart/CustomNode.tsx | 48 +- .../LiveSessionList/LiveSessionList.tsx | 2 +- .../LiveSessionSearchField.tsx | 2 +- .../shared/MainSearchBar/MainSearchBar.tsx | 9 +- .../MainSearchBar/components/TagList.tsx | 14 +- .../shared/SavedSearch/SavedSearch.tsx | 10 +- .../app/components/shared/Select/Select.tsx | 2 +- .../SelectDateRange/SelectDateRange.tsx | 67 +- .../AiSessionSearchField.tsx | 2 +- .../SessionSearchField/SessionSearchField.tsx | 2 +- .../app/components/ui/Icons/color_apple.tsx | 23 + .../components/ui/Icons/color_browser_Tor.tsx | 31 + .../ui/Icons/color_browser_applebot.tsx | 29 + .../color_browser_browser_icons_color_Tor.tsx | 31 + ...r_browser_browser_icons_color_applebot.tsx | 29 + ...lor_browser_browser_icons_color_chrome.tsx | 34 + ...wser_browser_icons_color_chrome_mobile.tsx | 34 + ..._browser_icons_color_chrome_mobile_ios.tsx | 34 + ...owser_browser_icons_color_duck_duck_go.tsx | 59 ++ ..._browser_icons_color_duckduckgo_mobile.tsx | 59 ++ ...color_browser_browser_icons_color_edge.tsx | 47 ++ ...rowser_browser_icons_color_edge_mobile.tsx | 47 ++ ...r_browser_browser_icons_color_facebook.tsx | 28 + ...or_browser_browser_icons_color_firefox.tsx | 124 +++ ...rowser_browser_icons_color_firefox_ios.tsx | 124 +++ ...ser_browser_icons_color_firefox_mobile.tsx | 124 +++ ...lor_browser_browser_icons_color_google.tsx | 24 + ..._browser_browser_icons_color_googlebot.tsx | 24 + ...ser_browser_icons_color_huawei_browser.tsx | 22 + ..._browser_icons_color_internet_explorer.tsx | 21 + ...owser_browser_icons_color_miui_browser.tsx | 28 + ...wser_browser_icons_color_mobile_safari.tsx | 171 ++++ ...r_browser_icons_color_mobile_safari_ui.tsx | 171 ++++ ...olor_browser_browser_icons_color_opera.tsx | 36 + ...lor_browser_browser_icons_color_safari.tsx | 171 ++++ ...r_browser_icons_color_samsung_internet.tsx | 22 + ...browser_browser_icons_color_uc_browser.tsx | 52 ++ ...or_browser_browser_icons_color_unknown.tsx | 23 + ...olor_browser_browser_icons_color_whale.tsx | 43 + ...ser_browser_icons_color_yandex_browser.tsx | 22 + .../ui/Icons/color_browser_chrome.tsx | 34 + .../ui/Icons/color_browser_chrome_mobile.tsx | 34 + .../Icons/color_browser_chrome_mobile_ios.tsx | 34 + .../ui/Icons/color_browser_duck_duck_go.tsx | 59 ++ .../Icons/color_browser_duckduckgo_mobile.tsx | 59 ++ .../ui/Icons/color_browser_edge.tsx | 47 ++ .../ui/Icons/color_browser_edge_mobile.tsx | 47 ++ .../ui/Icons/color_browser_facebook.tsx | 28 + .../ui/Icons/color_browser_firefox.tsx | 124 +++ .../ui/Icons/color_browser_firefox_ios.tsx | 124 +++ .../ui/Icons/color_browser_firefox_mobile.tsx | 124 +++ .../ui/Icons/color_browser_google.tsx | 24 + .../ui/Icons/color_browser_googlebot.tsx | 24 + .../ui/Icons/color_browser_huawei_browser.tsx | 22 + .../Icons/color_browser_internet_explorer.tsx | 21 + .../ui/Icons/color_browser_miui_browser.tsx | 28 + .../ui/Icons/color_browser_mobile_safari.tsx | 171 ++++ .../Icons/color_browser_mobile_safari_ui.tsx | 171 ++++ .../ui/Icons/color_browser_opera.tsx | 36 + .../ui/Icons/color_browser_safari.tsx | 171 ++++ .../Icons/color_browser_samsung_internet.tsx | 22 + .../ui/Icons/color_browser_uc_browser.tsx | 52 ++ .../ui/Icons/color_browser_unknown.tsx | 23 + .../ui/Icons/color_browser_whale.tsx | 43 + .../ui/Icons/color_browser_yandex_browser.tsx | 22 + .../app/components/ui/Icons/color_chrome.tsx | 35 + .../components/ui/Icons/color_country_de.tsx | 32 + .../components/ui/Icons/color_country_fr.tsx | 32 + .../components/ui/Icons/color_country_gb.tsx | 44 + .../components/ui/Icons/color_country_in.tsx | 39 + .../components/ui/Icons/color_country_us.tsx | 39 + frontend/app/components/ui/Icons/color_de.tsx | 32 + .../ui/Icons/color_device_desktop.tsx | 23 + .../ui/Icons/color_device_mobile.tsx | 35 + .../ui/Icons/color_device_other_phone.tsx | 30 + .../ui/Icons/color_device_tablet.tsx | 23 + .../ui/Icons/color_device_unkown.tsx | 25 + .../app/components/ui/Icons/color_edge.tsx | 48 ++ .../app/components/ui/Icons/color_fedora.tsx | 24 + .../app/components/ui/Icons/color_firefox.tsx | 125 +++ frontend/app/components/ui/Icons/color_fr.tsx | 32 + frontend/app/components/ui/Icons/color_gb.tsx | 44 + frontend/app/components/ui/Icons/color_in.tsx | 39 + .../ui/Icons/color_issues_bad_request.tsx | 28 + .../ui/Icons/color_issues_click_rage.tsx | 25 + .../components/ui/Icons/color_issues_cpu.tsx | 31 + .../ui/Icons/color_issues_crash.tsx | 26 + .../ui/Icons/color_issues_custom.tsx | 25 + .../ui/Icons/color_issues_dead_click.tsx | 31 + .../ui/Icons/color_issues_errors.tsx | 24 + .../color_issues_excessive_scrolling.tsx | 24 + .../ui/Icons/color_issues_js_exception.tsx | 24 + .../ui/Icons/color_issues_memory.tsx | 30 + .../Icons/color_issues_missing_resource.tsx | 27 + .../ui/Icons/color_issues_mouse_thrashing.tsx | 23 + .../ui/Icons/color_issues_mouse_trashing.tsx | 23 + .../ui/Icons/color_issues_slow_page_load.tsx | 38 + .../components/ui/Icons/color_microsoft.tsx | 25 + .../app/components/ui/Icons/color_opera.tsx | 37 + .../components/ui/Icons/color_os_android.tsx | 24 + .../components/ui/Icons/color_os_apple.tsx | 22 + .../ui/Icons/color_os_blackberry.tsx | 28 + .../ui/Icons/color_os_chrome_os.tsx | 34 + .../ui/Icons/color_os_elementary.tsx | 21 + .../components/ui/Icons/color_os_fedora.tsx | 23 + .../components/ui/Icons/color_os_freebsd.tsx | 24 + .../components/ui/Icons/color_os_gnome.tsx | 21 + .../app/components/ui/Icons/color_os_ios.tsx | 24 + .../components/ui/Icons/color_os_linux.tsx | 109 +++ .../ui/Icons/color_os_linux_mint.tsx | 36 + .../components/ui/Icons/color_os_macos.tsx | 30 + .../ui/Icons/color_os_microsoft.tsx | 24 + .../components/ui/Icons/color_os_ubuntu.tsx | 22 + .../components/ui/Icons/color_os_unkown.tsx | 25 + .../app/components/ui/Icons/color_safari.tsx | 172 ++++ .../app/components/ui/Icons/color_ubuntu.tsx | 23 + frontend/app/components/ui/Icons/color_us.tsx | 39 + frontend/app/components/ui/Icons/index.ts | 79 ++ .../ui/Icons/integrations_apple.tsx | 19 + .../ui/Icons/integrations_chrome.tsx | 19 + .../components/ui/Icons/integrations_edge.tsx | 19 + .../ui/Icons/integrations_fedora.tsx | 19 + .../ui/Icons/integrations_firefox.tsx | 19 + .../ui/Icons/integrations_microsoft.tsx | 19 + .../ui/Icons/integrations_opera.tsx | 19 + .../ui/Icons/integrations_safari.tsx | 19 + .../ui/Icons/integrations_ubuntu.tsx | 19 + .../app/components/ui/Icons/pdf_download.tsx | 2 +- frontend/app/components/ui/Icons/pencil.tsx | 2 +- frontend/app/components/ui/Icons/trash.tsx | 2 +- frontend/app/components/ui/Icons/users.tsx | 3 +- .../app/components/ui/ItemMenu/ItemMenu.tsx | 99 ++- .../ui/ItemMenu/itemMenu.module.css | 5 +- frontend/app/components/ui/Modal/Modal.tsx | 26 +- .../app/components/ui/Popover/Popover.tsx | 6 +- frontend/app/components/ui/SVG.tsx | 318 +++++++- frontend/app/constants/countries.js | 4 +- frontend/app/mstore/dashboardStore.ts | 60 +- frontend/app/mstore/types/IconProvider.tsx | 126 +++ frontend/app/mstore/types/dashboard.ts | 10 + frontend/app/mstore/types/filter.ts | 20 +- frontend/app/mstore/types/sessionsCardData.ts | 98 +++ frontend/app/mstore/types/widget.ts | 50 +- frontend/app/styles/general.css | 34 +- frontend/app/styles/global.scss | 2 +- frontend/app/styles/reset.css | 2 +- frontend/app/svg/ca-no-dashboards.svg | 343 ++++++-- frontend/app/svg/icons/color/apple.svg | 4 + frontend/app/svg/icons/color/browser/Tor.svg | 13 + .../app/svg/icons/color/browser/applebot.svg | 11 + .../app/svg/icons/color/browser/chrome.svg | 16 + .../svg/icons/color/browser/chrome_mobile.svg | 16 + .../icons/color/browser/chrome_mobile_ios.svg | 16 + .../svg/icons/color/browser/duck_duck_go.svg | 41 + .../icons/color/browser/duckduckgo_mobile.svg | 41 + frontend/app/svg/icons/color/browser/edge.svg | 29 + .../svg/icons/color/browser/edge_mobile.svg | 29 + .../app/svg/icons/color/browser/facebook.svg | 10 + .../app/svg/icons/color/browser/firefox.svg | 106 +++ .../svg/icons/color/browser/firefox_ios.svg | 106 +++ .../icons/color/browser/firefox_mobile.svg | 106 +++ .../app/svg/icons/color/browser/google.svg | 6 + .../app/svg/icons/color/browser/googlebot.svg | 6 + .../icons/color/browser/huawei_browser.svg | 4 + .../icons/color/browser/internet_explorer.svg | 3 + .../svg/icons/color/browser/miui_browser.svg | 10 + .../svg/icons/color/browser/mobile_safari.svg | 153 ++++ .../icons/color/browser/mobile_safari_ui.svg | 153 ++++ .../app/svg/icons/color/browser/opera.svg | 18 + .../app/svg/icons/color/browser/safari.svg | 153 ++++ .../icons/color/browser/samsung_internet.svg | 4 + .../svg/icons/color/browser/uc_browser.svg | 34 + .../app/svg/icons/color/browser/unknown.svg | 5 + .../app/svg/icons/color/browser/whale.svg | 25 + .../icons/color/browser/yandex_browser.svg | 4 + frontend/app/svg/icons/color/chrome.svg | 16 + frontend/app/svg/icons/color/country/de.svg | 13 + frontend/app/svg/icons/color/country/fr.svg | 13 + frontend/app/svg/icons/color/country/gb.svg | 25 + frontend/app/svg/icons/color/country/in.svg | 20 + frontend/app/svg/icons/color/country/us.svg | 20 + frontend/app/svg/icons/color/de.svg | 13 + .../app/svg/icons/color/device/desktop.svg | 5 + .../app/svg/icons/color/device/mobile.svg | 17 + .../svg/icons/color/device/other_phone.svg | 12 + .../app/svg/icons/color/device/tablet.svg | 5 + .../app/svg/icons/color/device/unkown.svg | 7 + frontend/app/svg/icons/color/edge.svg | 29 + frontend/app/svg/icons/color/fedora.svg | 5 + frontend/app/svg/icons/color/firefox.svg | 106 +++ frontend/app/svg/icons/color/fr.svg | 13 + frontend/app/svg/icons/color/gb.svg | 25 + frontend/app/svg/icons/color/in.svg | 20 + .../svg/icons/color/issues/bad_request.svg | 10 + .../app/svg/icons/color/issues/click_rage.svg | 7 + frontend/app/svg/icons/color/issues/cpu.svg | 13 + frontend/app/svg/icons/color/issues/crash.svg | 8 + .../app/svg/icons/color/issues/custom.svg | 7 + .../app/svg/icons/color/issues/dead_click.svg | 13 + .../app/svg/icons/color/issues/errors.svg | 6 + .../color/issues/excessive_scrolling.svg | 6 + .../svg/icons/color/issues/js_exception.svg | 6 + .../app/svg/icons/color/issues/memory.svg | 12 + .../icons/color/issues/missing_resource.svg | 9 + .../icons/color/issues/mouse_thrashing.svg | 5 + .../svg/icons/color/issues/slow_page_load.svg | 20 + frontend/app/svg/icons/color/microsoft.svg | 6 + frontend/app/svg/icons/color/opera.svg | 18 + frontend/app/svg/icons/color/os/android.svg | 6 + frontend/app/svg/icons/color/os/apple.svg | 4 + .../app/svg/icons/color/os/blackberry.svg | 10 + frontend/app/svg/icons/color/os/chrome_os.svg | 16 + .../app/svg/icons/color/os/elementary.svg | 3 + frontend/app/svg/icons/color/os/fedora.svg | 5 + frontend/app/svg/icons/color/os/freebsd.svg | 6 + frontend/app/svg/icons/color/os/gnome.svg | 3 + frontend/app/svg/icons/color/os/ios.svg | 6 + frontend/app/svg/icons/color/os/linux.svg | 91 +++ .../app/svg/icons/color/os/linux_mint.svg | 18 + frontend/app/svg/icons/color/os/macos.svg | 12 + frontend/app/svg/icons/color/os/microsoft.svg | 6 + frontend/app/svg/icons/color/os/ubuntu.svg | 4 + frontend/app/svg/icons/color/os/unkown.svg | 7 + frontend/app/svg/icons/color/safari.svg | 153 ++++ frontend/app/svg/icons/color/ubuntu.svg | 4 + frontend/app/svg/icons/color/us.svg | 20 + frontend/app/svg/icons/trash.svg | 5 +- frontend/app/types/session/session.ts | 2 +- frontend/app/utils/index.ts | 12 +- frontend/package.json | 6 +- frontend/scripts/icons.js | 6 +- frontend/yarn.lock | 409 ++++++---- yarn.lock | 4 + 346 files changed, 15161 insertions(+), 2793 deletions(-) create mode 100644 frontend/app/components/Dashboard/Widgets/CardSessionsByList.tsx delete mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts rename frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/{CustomMetriLineChart/CustomMetriLineChart.tsx => CustomMetricLineChart/CustomMetricLineChart.tsx} (51%) create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/index.ts create mode 100644 frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/SessionsBy.tsx create mode 100644 frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx create mode 100644 frontend/app/components/Dashboard/components/AddToDashboardButton.tsx create mode 100644 frontend/app/components/Dashboard/components/CreateCardButton.tsx create mode 100644 frontend/app/components/Dashboard/components/CreateDashboardButton.tsx delete mode 100644 frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CreateCard.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/ExampleCards.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/AreaChartCard.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/BarChart.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/Bars.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/CallsWithErrorsExample.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/Count.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/Funnel.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/InsightsExample.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/Path.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/PerfBreakdown.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByBrowser.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByCountry.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByIssues.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/BySystem.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUrl.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUser.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsByErrors.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsByIssues.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SlowestDomain.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/Tabs/CoreWebVitals.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/Tabs/PerformanceMonitoring.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/Tabs/ProductAnalytics.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/Tabs/WebAnalytics.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/Trend.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/WebVital.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/NewDashboardModal.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Option.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/SelectCard.tsx create mode 100644 frontend/app/components/Dashboard/components/DashboardList/NewDashModal/index.ts create mode 100644 frontend/app/components/Dashboard/components/FilterSeries/AddStepButton.tsx delete mode 100644 frontend/app/components/Dashboard/components/Sessions/SessionList/SessionList.tsx delete mode 100644 frontend/app/components/Dashboard/components/Sessions/SessionList/index.ts delete mode 100644 frontend/app/components/Dashboard/components/Sessions/SessionListItem/SessionListItem.tsx delete mode 100644 frontend/app/components/Dashboard/components/Sessions/SessionListItem/index.ts delete mode 100644 frontend/app/components/Dashboard/components/Sessions/SessionWidget/SessionWidget.tsx delete mode 100644 frontend/app/components/Dashboard/components/Sessions/SessionWidget/index.ts create mode 100644 frontend/app/components/Dashboard/components/WidgetForm/CardBuilder.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetView/CardViewMenu.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetWrapper/CardMenu.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx create mode 100644 frontend/app/components/ModalContext.tsx create mode 100644 frontend/app/components/shared/Filters/FilterList/EventsOrder.tsx create mode 100644 frontend/app/components/ui/Icons/color_apple.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_Tor.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_applebot.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_Tor.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_applebot.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_chrome.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_chrome_mobile.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_chrome_mobile_ios.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_duck_duck_go.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_duckduckgo_mobile.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_edge.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_edge_mobile.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_facebook.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_firefox.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_firefox_ios.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_firefox_mobile.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_google.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_googlebot.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_huawei_browser.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_internet_explorer.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_miui_browser.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_mobile_safari.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_mobile_safari_ui.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_opera.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_safari.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_samsung_internet.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_uc_browser.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_unknown.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_whale.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_browser_icons_color_yandex_browser.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_chrome.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_chrome_mobile.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_chrome_mobile_ios.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_duck_duck_go.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_duckduckgo_mobile.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_edge.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_edge_mobile.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_facebook.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_firefox.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_firefox_ios.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_firefox_mobile.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_google.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_googlebot.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_huawei_browser.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_internet_explorer.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_miui_browser.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_mobile_safari.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_mobile_safari_ui.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_opera.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_safari.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_samsung_internet.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_uc_browser.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_unknown.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_whale.tsx create mode 100644 frontend/app/components/ui/Icons/color_browser_yandex_browser.tsx create mode 100644 frontend/app/components/ui/Icons/color_chrome.tsx create mode 100644 frontend/app/components/ui/Icons/color_country_de.tsx create mode 100644 frontend/app/components/ui/Icons/color_country_fr.tsx create mode 100644 frontend/app/components/ui/Icons/color_country_gb.tsx create mode 100644 frontend/app/components/ui/Icons/color_country_in.tsx create mode 100644 frontend/app/components/ui/Icons/color_country_us.tsx create mode 100644 frontend/app/components/ui/Icons/color_de.tsx create mode 100644 frontend/app/components/ui/Icons/color_device_desktop.tsx create mode 100644 frontend/app/components/ui/Icons/color_device_mobile.tsx create mode 100644 frontend/app/components/ui/Icons/color_device_other_phone.tsx create mode 100644 frontend/app/components/ui/Icons/color_device_tablet.tsx create mode 100644 frontend/app/components/ui/Icons/color_device_unkown.tsx create mode 100644 frontend/app/components/ui/Icons/color_edge.tsx create mode 100644 frontend/app/components/ui/Icons/color_fedora.tsx create mode 100644 frontend/app/components/ui/Icons/color_firefox.tsx create mode 100644 frontend/app/components/ui/Icons/color_fr.tsx create mode 100644 frontend/app/components/ui/Icons/color_gb.tsx create mode 100644 frontend/app/components/ui/Icons/color_in.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_bad_request.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_click_rage.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_cpu.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_crash.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_custom.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_dead_click.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_errors.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_excessive_scrolling.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_js_exception.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_memory.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_missing_resource.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_mouse_thrashing.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_mouse_trashing.tsx create mode 100644 frontend/app/components/ui/Icons/color_issues_slow_page_load.tsx create mode 100644 frontend/app/components/ui/Icons/color_microsoft.tsx create mode 100644 frontend/app/components/ui/Icons/color_opera.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_android.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_apple.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_blackberry.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_chrome_os.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_elementary.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_fedora.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_freebsd.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_gnome.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_ios.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_linux.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_linux_mint.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_macos.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_microsoft.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_ubuntu.tsx create mode 100644 frontend/app/components/ui/Icons/color_os_unkown.tsx create mode 100644 frontend/app/components/ui/Icons/color_safari.tsx create mode 100644 frontend/app/components/ui/Icons/color_ubuntu.tsx create mode 100644 frontend/app/components/ui/Icons/color_us.tsx create mode 100644 frontend/app/components/ui/Icons/integrations_apple.tsx create mode 100644 frontend/app/components/ui/Icons/integrations_chrome.tsx create mode 100644 frontend/app/components/ui/Icons/integrations_edge.tsx create mode 100644 frontend/app/components/ui/Icons/integrations_fedora.tsx create mode 100644 frontend/app/components/ui/Icons/integrations_firefox.tsx create mode 100644 frontend/app/components/ui/Icons/integrations_microsoft.tsx create mode 100644 frontend/app/components/ui/Icons/integrations_opera.tsx create mode 100644 frontend/app/components/ui/Icons/integrations_safari.tsx create mode 100644 frontend/app/components/ui/Icons/integrations_ubuntu.tsx create mode 100644 frontend/app/mstore/types/IconProvider.tsx create mode 100644 frontend/app/mstore/types/sessionsCardData.ts create mode 100644 frontend/app/svg/icons/color/apple.svg create mode 100644 frontend/app/svg/icons/color/browser/Tor.svg create mode 100644 frontend/app/svg/icons/color/browser/applebot.svg create mode 100644 frontend/app/svg/icons/color/browser/chrome.svg create mode 100644 frontend/app/svg/icons/color/browser/chrome_mobile.svg create mode 100644 frontend/app/svg/icons/color/browser/chrome_mobile_ios.svg create mode 100644 frontend/app/svg/icons/color/browser/duck_duck_go.svg create mode 100644 frontend/app/svg/icons/color/browser/duckduckgo_mobile.svg create mode 100644 frontend/app/svg/icons/color/browser/edge.svg create mode 100644 frontend/app/svg/icons/color/browser/edge_mobile.svg create mode 100644 frontend/app/svg/icons/color/browser/facebook.svg create mode 100644 frontend/app/svg/icons/color/browser/firefox.svg create mode 100644 frontend/app/svg/icons/color/browser/firefox_ios.svg create mode 100644 frontend/app/svg/icons/color/browser/firefox_mobile.svg create mode 100644 frontend/app/svg/icons/color/browser/google.svg create mode 100644 frontend/app/svg/icons/color/browser/googlebot.svg create mode 100644 frontend/app/svg/icons/color/browser/huawei_browser.svg create mode 100644 frontend/app/svg/icons/color/browser/internet_explorer.svg create mode 100644 frontend/app/svg/icons/color/browser/miui_browser.svg create mode 100644 frontend/app/svg/icons/color/browser/mobile_safari.svg create mode 100644 frontend/app/svg/icons/color/browser/mobile_safari_ui.svg create mode 100644 frontend/app/svg/icons/color/browser/opera.svg create mode 100644 frontend/app/svg/icons/color/browser/safari.svg create mode 100644 frontend/app/svg/icons/color/browser/samsung_internet.svg create mode 100644 frontend/app/svg/icons/color/browser/uc_browser.svg create mode 100644 frontend/app/svg/icons/color/browser/unknown.svg create mode 100644 frontend/app/svg/icons/color/browser/whale.svg create mode 100644 frontend/app/svg/icons/color/browser/yandex_browser.svg create mode 100644 frontend/app/svg/icons/color/chrome.svg create mode 100644 frontend/app/svg/icons/color/country/de.svg create mode 100644 frontend/app/svg/icons/color/country/fr.svg create mode 100644 frontend/app/svg/icons/color/country/gb.svg create mode 100644 frontend/app/svg/icons/color/country/in.svg create mode 100644 frontend/app/svg/icons/color/country/us.svg create mode 100644 frontend/app/svg/icons/color/de.svg create mode 100644 frontend/app/svg/icons/color/device/desktop.svg create mode 100644 frontend/app/svg/icons/color/device/mobile.svg create mode 100644 frontend/app/svg/icons/color/device/other_phone.svg create mode 100644 frontend/app/svg/icons/color/device/tablet.svg create mode 100644 frontend/app/svg/icons/color/device/unkown.svg create mode 100644 frontend/app/svg/icons/color/edge.svg create mode 100644 frontend/app/svg/icons/color/fedora.svg create mode 100644 frontend/app/svg/icons/color/firefox.svg create mode 100644 frontend/app/svg/icons/color/fr.svg create mode 100644 frontend/app/svg/icons/color/gb.svg create mode 100644 frontend/app/svg/icons/color/in.svg create mode 100644 frontend/app/svg/icons/color/issues/bad_request.svg create mode 100644 frontend/app/svg/icons/color/issues/click_rage.svg create mode 100644 frontend/app/svg/icons/color/issues/cpu.svg create mode 100644 frontend/app/svg/icons/color/issues/crash.svg create mode 100644 frontend/app/svg/icons/color/issues/custom.svg create mode 100644 frontend/app/svg/icons/color/issues/dead_click.svg create mode 100644 frontend/app/svg/icons/color/issues/errors.svg create mode 100644 frontend/app/svg/icons/color/issues/excessive_scrolling.svg create mode 100644 frontend/app/svg/icons/color/issues/js_exception.svg create mode 100644 frontend/app/svg/icons/color/issues/memory.svg create mode 100644 frontend/app/svg/icons/color/issues/missing_resource.svg create mode 100644 frontend/app/svg/icons/color/issues/mouse_thrashing.svg create mode 100644 frontend/app/svg/icons/color/issues/slow_page_load.svg create mode 100644 frontend/app/svg/icons/color/microsoft.svg create mode 100644 frontend/app/svg/icons/color/opera.svg create mode 100644 frontend/app/svg/icons/color/os/android.svg create mode 100644 frontend/app/svg/icons/color/os/apple.svg create mode 100644 frontend/app/svg/icons/color/os/blackberry.svg create mode 100644 frontend/app/svg/icons/color/os/chrome_os.svg create mode 100644 frontend/app/svg/icons/color/os/elementary.svg create mode 100644 frontend/app/svg/icons/color/os/fedora.svg create mode 100644 frontend/app/svg/icons/color/os/freebsd.svg create mode 100644 frontend/app/svg/icons/color/os/gnome.svg create mode 100644 frontend/app/svg/icons/color/os/ios.svg create mode 100644 frontend/app/svg/icons/color/os/linux.svg create mode 100644 frontend/app/svg/icons/color/os/linux_mint.svg create mode 100644 frontend/app/svg/icons/color/os/macos.svg create mode 100644 frontend/app/svg/icons/color/os/microsoft.svg create mode 100644 frontend/app/svg/icons/color/os/ubuntu.svg create mode 100644 frontend/app/svg/icons/color/os/unkown.svg create mode 100644 frontend/app/svg/icons/color/safari.svg create mode 100644 frontend/app/svg/icons/color/ubuntu.svg create mode 100644 frontend/app/svg/icons/color/us.svg create mode 100644 yarn.lock diff --git a/frontend/app/Router.tsx b/frontend/app/Router.tsx index 48bc7d351..51cae3feb 100644 --- a/frontend/app/Router.tsx +++ b/frontend/app/Router.tsx @@ -1,193 +1,196 @@ -import React, { useEffect, useRef } from 'react'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { connect, ConnectedProps } from 'react-redux'; -import { Loader } from 'UI'; -import { fetchUserInfo, setJwt } from 'Duck/user'; -import { fetchList as fetchSiteList } from 'Duck/site'; -import { withStore } from 'App/mstore'; -import { Map } from 'immutable'; +import React, {useEffect, useRef} from 'react'; +import {withRouter, RouteComponentProps} from 'react-router-dom'; +import {connect, ConnectedProps} from 'react-redux'; +import {Loader} from 'UI'; +import {fetchUserInfo, setJwt} from 'Duck/user'; +import {fetchList as fetchSiteList} from 'Duck/site'; +import {withStore} from 'App/mstore'; +import {Map} from 'immutable'; import * as routes from './routes'; -import { fetchTenants } from 'Duck/user'; -import { setSessionPath } from 'Duck/sessions'; -import { ModalProvider } from 'Components/Modal'; -import { GLOBAL_DESTINATION_PATH, IFRAME, JWT_PARAM } from 'App/constants/storageKeys'; +import {fetchTenants} from 'Duck/user'; +import {setSessionPath} from 'Duck/sessions'; +import {ModalProvider} from 'Components/Modal'; +import {GLOBAL_DESTINATION_PATH, IFRAME, JWT_PARAM} from 'App/constants/storageKeys'; import PublicRoutes from 'App/PublicRoutes'; import Layout from 'App/layout/Layout'; -import { fetchListActive as fetchMetadata } from 'Duck/customField'; -import { init as initSite } from 'Duck/site'; +import {fetchListActive as fetchMetadata} from 'Duck/customField'; +import {init as initSite} from 'Duck/site'; import PrivateRoutes from 'App/PrivateRoutes'; -import { checkParam } from 'App/utils'; +import {checkParam} from 'App/utils'; import IFrameRoutes from 'App/IFrameRoutes'; +import {ModalProvider as NewModalProvider} from 'Components/ModalContext'; interface RouterProps extends RouteComponentProps, ConnectedProps { - isLoggedIn: boolean; - sites: Map; - loading: boolean; - changePassword: boolean; - isEnterprise: boolean; - fetchUserInfo: () => any; - fetchTenants: () => any; - setSessionPath: (path: any) => any; - fetchSiteList: (siteId?: number) => any; - match: { - params: { - siteId: string; - } - }; - mstore: any; - setJwt: (jwt: string) => any; - fetchMetadata: (siteId: string) => void; - initSite: (site: any) => void; + isLoggedIn: boolean; + sites: Map; + loading: boolean; + changePassword: boolean; + isEnterprise: boolean; + fetchUserInfo: () => any; + fetchTenants: () => any; + setSessionPath: (path: any) => any; + fetchSiteList: (siteId?: number) => any; + match: { + params: { + siteId: string; + } + }; + mstore: any; + setJwt: (jwt: string) => any; + fetchMetadata: (siteId: string) => void; + initSite: (site: any) => void; } const Router: React.FC = (props) => { - const { - isLoggedIn, - siteId, - sites, - loading, - location, - fetchUserInfo, - fetchSiteList, - history, - match: { params: { siteId: siteIdFromPath } }, - setSessionPath, - } = props; - const [isIframe, setIsIframe] = React.useState(false); - const [isJwt, setIsJwt] = React.useState(false); + const { + isLoggedIn, + siteId, + sites, + loading, + location, + fetchUserInfo, + fetchSiteList, + history, + match: {params: {siteId: siteIdFromPath}}, + setSessionPath, + } = props; + const [isIframe, setIsIframe] = React.useState(false); + const [isJwt, setIsJwt] = React.useState(false); - const handleJwtFromUrl = () => { - const urlJWT = new URLSearchParams(location.search).get('jwt'); - if (urlJWT) { - props.setJwt(urlJWT); + const handleJwtFromUrl = () => { + const urlJWT = new URLSearchParams(location.search).get('jwt'); + if (urlJWT) { + props.setJwt(urlJWT); + } + }; + + const handleDestinationPath = () => { + if (!isLoggedIn && location.pathname !== routes.login()) { + localStorage.setItem(GLOBAL_DESTINATION_PATH, location.pathname + location.search); + } + }; + + const handleUserLogin = async () => { + await fetchUserInfo(); + const siteIdFromPath = parseInt(location.pathname.split('/')[1]); + await fetchSiteList(siteIdFromPath); + props.mstore.initClient(); + + const destinationPath = localStorage.getItem(GLOBAL_DESTINATION_PATH); + if ( + destinationPath && + destinationPath !== routes.login() && + destinationPath !== routes.signup() && + destinationPath !== '/' + ) { + const url = new URL(destinationPath, window.location.origin); + checkParams(url.search) + history.push(destinationPath); + localStorage.removeItem(GLOBAL_DESTINATION_PATH); + } + }; + + const checkParams = (search?: string) => { + const _isIframe = checkParam('iframe', IFRAME, search); + const _isJwt = checkParam('jwt', JWT_PARAM, search); + setIsIframe(_isIframe); + setIsJwt(_isJwt); } - }; - const handleDestinationPath = () => { - if (!isLoggedIn && location.pathname !== routes.login()) { - localStorage.setItem(GLOBAL_DESTINATION_PATH, location.pathname + location.search); - } - }; - - const handleUserLogin = async () => { - await fetchUserInfo(); - const siteIdFromPath = parseInt(location.pathname.split('/')[1]); - await fetchSiteList(siteIdFromPath); - props.mstore.initClient(); - - const destinationPath = localStorage.getItem(GLOBAL_DESTINATION_PATH); - if ( - destinationPath && - destinationPath !== routes.login() && - destinationPath !== routes.signup() && - destinationPath !== '/' - ) { - const url = new URL(destinationPath, window.location.origin); - checkParams(url.search) - history.push(destinationPath); - localStorage.removeItem(GLOBAL_DESTINATION_PATH); - } - }; - - const checkParams = (search?: string) => { - const _isIframe = checkParam('iframe', IFRAME, search); - const _isJwt = checkParam('jwt', JWT_PARAM, search); - setIsIframe(_isIframe); - setIsJwt(_isJwt); - } - - useEffect(() => { - checkParams(); - handleJwtFromUrl(); - }, []); - - useEffect(() => { - // handleJwtFromUrl(); - handleDestinationPath(); - - - setSessionPath(previousLocation ? previousLocation : location); - }, [location]); - - useEffect(() => { - if (prevIsLoggedIn !== isLoggedIn && isLoggedIn) { - handleUserLogin(); - } - }, [isLoggedIn]); - - useEffect(() => { - if (siteId && siteId !== lastFetchedSiteIdRef.current) { - const activeSite = sites.find((s) => s.id == siteId); - props.initSite(activeSite); - props.fetchMetadata(siteId); - lastFetchedSiteIdRef.current = siteId; - } - }, [siteId]); - - const lastFetchedSiteIdRef = useRef(null); - - function usePrevious(value: any) { - const ref = useRef(); useEffect(() => { - ref.current = value; - }, [value]); - return ref.current; - } + checkParams(); + handleJwtFromUrl(); + }, []); - const prevIsLoggedIn = usePrevious(isLoggedIn); - const previousLocation = usePrevious(location); + useEffect(() => { + // handleJwtFromUrl(); + handleDestinationPath(); - const hideHeader = (location.pathname && location.pathname.includes('/session/')) || - location.pathname.includes('/assist/') || location.pathname.includes('multiview'); - if (isIframe) { - return ; - } + setSessionPath(previousLocation ? previousLocation : location); + }, [location]); - return isLoggedIn ? ( - - - - - - - - ) : ; + useEffect(() => { + if (prevIsLoggedIn !== isLoggedIn && isLoggedIn) { + handleUserLogin(); + } + }, [isLoggedIn]); + + useEffect(() => { + if (siteId && siteId !== lastFetchedSiteIdRef.current) { + const activeSite = sites.find((s) => s.id == siteId); + props.initSite(activeSite); + props.fetchMetadata(siteId); + lastFetchedSiteIdRef.current = siteId; + } + }, [siteId]); + + const lastFetchedSiteIdRef = useRef(null); + + function usePrevious(value: any) { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }, [value]); + return ref.current; + } + + const prevIsLoggedIn = usePrevious(isLoggedIn); + const previousLocation = usePrevious(location); + + const hideHeader = (location.pathname && location.pathname.includes('/session/')) || + location.pathname.includes('/assist/') || location.pathname.includes('multiview'); + + if (isIframe) { + return ; + } + + return isLoggedIn ? ( + + + + + + + + + + ) : ; }; const mapStateToProps = (state: Map) => { - const siteId = state.getIn(['site', 'siteId']); - const jwt = state.getIn(['user', 'jwt']); - const changePassword = state.getIn(['user', 'account', 'changePassword']); - const userInfoLoading = state.getIn(['user', 'fetchUserInfoRequest', 'loading']); - const sitesLoading = state.getIn(['site', 'fetchListRequest', 'loading']); + const siteId = state.getIn(['site', 'siteId']); + const jwt = state.getIn(['user', 'jwt']); + const changePassword = state.getIn(['user', 'account', 'changePassword']); + const userInfoLoading = state.getIn(['user', 'fetchUserInfoRequest', 'loading']); + const sitesLoading = state.getIn(['site', 'fetchListRequest', 'loading']); - return { - siteId, - changePassword, - sites: state.getIn(['site', 'list']), - isLoggedIn: jwt !== null && !changePassword, - loading: siteId === null || userInfoLoading || sitesLoading, - email: state.getIn(['user', 'account', 'email']), - account: state.getIn(['user', 'account']), - organisation: state.getIn(['user', 'account', 'name']), - tenantId: state.getIn(['user', 'account', 'tenantId']), - tenants: state.getIn(['user', 'tenants']), - isEnterprise: - state.getIn(['user', 'account', 'edition']) === 'ee' || - state.getIn(['user', 'authDetails', 'edition']) === 'ee' - }; + return { + siteId, + changePassword, + sites: state.getIn(['site', 'list']), + isLoggedIn: jwt !== null && !changePassword, + loading: siteId === null || userInfoLoading || sitesLoading, + email: state.getIn(['user', 'account', 'email']), + account: state.getIn(['user', 'account']), + organisation: state.getIn(['user', 'account', 'name']), + tenantId: state.getIn(['user', 'account', 'tenantId']), + tenants: state.getIn(['user', 'tenants']), + isEnterprise: + state.getIn(['user', 'account', 'edition']) === 'ee' || + state.getIn(['user', 'authDetails', 'edition']) === 'ee' + }; }; const mapDispatchToProps = { - fetchUserInfo, - fetchTenants, - setSessionPath, - fetchSiteList, - setJwt, - fetchMetadata, - initSite + fetchUserInfo, + fetchTenants, + setSessionPath, + fetchSiteList, + setJwt, + fetchMetadata, + initSite }; const connector = connect(mapStateToProps, mapDispatchToProps); diff --git a/frontend/app/components/Alerts/AlertForm.js b/frontend/app/components/Alerts/AlertForm.js index fcec66918..852780e9b 100644 --- a/frontend/app/components/Alerts/AlertForm.js +++ b/frontend/app/components/Alerts/AlertForm.js @@ -1,382 +1,384 @@ -import React, { useEffect } from 'react'; -import { Button, Form, Input, SegmentSelection, Checkbox, Icon } from 'UI'; -import { alertConditions as conditions } from 'App/constants'; +import React, {useEffect} from 'react'; +import {Form, Input, SegmentSelection, Checkbox, Icon} from 'UI'; +import {alertConditions as conditions} from 'App/constants'; import stl from './alertForm.module.css'; import DropdownChips from './DropdownChips'; -import { validateEmail } from 'App/validate'; +import {validateEmail} from 'App/validate'; import cn from 'classnames'; -import { useStore } from 'App/mstore' -import { observer } from 'mobx-react-lite' +import {useStore} from 'App/mstore' +import {observer} from 'mobx-react-lite' import Select from 'Shared/Select'; +import {Button} from "antd"; const thresholdOptions = [ - { label: '15 minutes', value: 15 }, - { label: '30 minutes', value: 30 }, - { label: '1 hour', value: 60 }, - { label: '2 hours', value: 120 }, - { label: '4 hours', value: 240 }, - { label: '1 day', value: 1440 }, + {label: '15 minutes', value: 15}, + {label: '30 minutes', value: 30}, + {label: '1 hour', value: 60}, + {label: '2 hours', value: 120}, + {label: '4 hours', value: 240}, + {label: '1 day', value: 1440}, ]; const changeOptions = [ - { label: 'change', value: 'change' }, - { label: '% change', value: 'percent' }, + {label: 'change', value: 'change'}, + {label: '% change', value: 'percent'}, ]; -const Circle = ({ text }) => ( -
- {text} -
+const Circle = ({text}) => ( +
+ {text} +
); -const Section = ({ index, title, description, content }) => ( -
-
- -
- {title} - {description &&
{description}
} -
-
+const Section = ({index, title, description, content}) => ( +
+
+ +
+ {title} + {description &&
{description}
} +
+
-
{content}
-
+
{content}
+
); function AlertForm(props) { - const { - slackChannels, - msTeamsChannels, - webhooks, - onDelete, - style = { width: '580px', height: '100vh' }, - } = props; - const { alertsStore } = useStore() - const { - triggerOptions, - loading, - } = alertsStore - const instance = alertsStore.instance - const deleting = loading + const { + slackChannels, + msTeamsChannels, + webhooks, + onDelete, + style = {height: "calc('100vh - 40px')"}, + } = props; + const {alertsStore} = useStore() + const { + triggerOptions, + loading, + } = alertsStore + const instance = alertsStore.instance + const deleting = loading - const write = ({ target: { value, name } }) => alertsStore.edit({ [name]: value }); - const writeOption = (e, { name, value }) => alertsStore.edit({ [name]: value.value }); - const onChangeCheck = ({ target: { checked, name } }) => alertsStore.edit({ [name]: checked }); + const write = ({target: {value, name}}) => alertsStore.edit({[name]: value}); + const writeOption = (e, {name, value}) => alertsStore.edit({[name]: value.value}); + const onChangeCheck = ({target: {checked, name}}) => alertsStore.edit({[name]: checked}); - useEffect(() => { - void alertsStore.fetchTriggerOptions(); - }, []); + useEffect(() => { + void alertsStore.fetchTriggerOptions(); + }, []); - const writeQueryOption = (e, { name, value }) => { - const { query } = instance; - alertsStore.edit({ query: { ...query, [name]: value } }); - }; + const writeQueryOption = (e, {name, value}) => { + const {query} = instance; + alertsStore.edit({query: {...query, [name]: value}}); + }; - const writeQuery = ({ target: { value, name } }) => { - const { query } = instance; - alertsStore.edit({ query: { ...query, [name]: value } }); - }; + const writeQuery = ({target: {value, name}}) => { + const {query} = instance; + alertsStore.edit({query: {...query, [name]: value}}); + }; - const metric = - instance && instance.query.left - ? triggerOptions.find((i) => i.value === instance.query.left) - : null; - const unit = metric ? metric.unit : ''; - const isThreshold = instance.detectionMethod === 'threshold'; + const metric = + instance && instance.query.left + ? triggerOptions.find((i) => i.value === instance.query.left) + : null; + const unit = metric ? metric.unit : ''; + const isThreshold = instance.detectionMethod === 'threshold'; - return ( -
props.onSubmit(instance)} - id="alert-form" - > -
- -
-
- alertsStore.edit({ [name]: value })} - value={{ value: instance.detectionMethod }} - list={[ - { name: 'Threshold', value: 'threshold' }, - { name: 'Change', value: 'change' }, - ]} - /> -
- {isThreshold && - 'Eg. Alert me if memory.avg is greater than 500mb over the past 4 hours.'} - {!isThreshold && - 'Eg. Alert me if % change of memory.avg is greater than 10% over the past 4 hours compared to the previous 4 hours.'} -
-
-
- } - /> - -
- -
- {!isThreshold && ( -
- - i.value === instance.query.left)} - // onChange={ writeQueryOption } - onChange={({ value }) => - writeQueryOption(null, { name: 'left', value: value.value }) - } + return ( + props.onSubmit(instance)} + id="alert-form" + > +
+ -
- -
- -
- writeOption(null, {name: 'change', value})} + id="change-dropdown" + /> +
+ )} + +
+ + + writeQueryOption(null, {name: 'operator', value: value.value}) + } + /> + {unit && ( + <> + + {'test'} + + )} + {!unit && ( + + )} +
+
+ +
+ + writeOption(null, {name: 'previousPeriod', value})} + /> +
+ )} +
+ } + /> + +
+ +
+
+ + + + +
+ + {instance.slack && ( +
+ +
+ alertsStore.edit({slackInput: selected})} + /> +
+
+ )} + {instance.msteams && ( +
+ +
+ alertsStore.edit({msteamsInput: selected})} + /> +
+
+ )} + + {instance.email && ( +
+ +
+ alertsStore.edit({emailInput: selected})} + /> +
+
+ )} + + {instance.webhook && ( +
+ + alertsStore.edit({webhookInput: selected})} + /> +
+ )} +
} - /> - {unit && ( - <> - - {'test'} - - )} - {!unit && ( - - )} -
- - -
- - writeOption(null, { name: 'previousPeriod', value })} - /> -
- )} - } - /> -
- -
-
- - - - -
- - {instance.slack && ( -
- -
- alertsStore.edit({ slackInput: selected })} - /> -
+
+
+ +
+
- )} - {instance.msteams && ( -
- -
- alertsStore.edit({ msteamsInput: selected })} - /> -
+
+ {instance.exists() && ( + + )}
- )} - - {instance.email && ( -
- -
- alertsStore.edit({ emailInput: selected })} - /> -
-
- )} - - {instance.webhook && ( -
- - alertsStore.edit({ webhookInput: selected })} - /> -
- )}
- } - /> -
- -
-
- -
- -
-
- {instance.exists() && ( - - )} -
-
- - ); + + ); }; export default observer(AlertForm); diff --git a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx index 3821a3f45..50e0c3cf6 100644 --- a/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx +++ b/frontend/app/components/Alerts/AlertFormModal/AlertFormModal.tsx @@ -1,10 +1,10 @@ -import React, { useEffect, useState } from 'react'; -import { SlideModal } from 'UI'; -import { useStore } from 'App/mstore' -import { observer } from 'mobx-react-lite' +import React, {useEffect, useState} from 'react'; +import {SlideModal} from 'UI'; +import {useStore} from 'App/mstore' +import {observer} from 'mobx-react-lite' import AlertForm from '../AlertForm'; -import { SLACK, TEAMS, WEBHOOK } from 'App/constants/schedule'; -import { confirm } from 'UI'; +import {SLACK, TEAMS, WEBHOOK} from 'App/constants/schedule'; +import {confirm} from 'UI'; interface Select { label: string; @@ -17,9 +17,10 @@ interface Props { metricId?: number; onClose?: () => void; } + function AlertFormModal(props: Props) { - const { alertsStore, settingsStore } = useStore() - const { metricId = null, showModal = false } = props; + const {alertsStore, settingsStore} = useStore() + const {metricId = null, showModal = false} = props; const [showForm, setShowForm] = useState(false); const webhooks = settingsStore.webhooks useEffect(() => { @@ -32,7 +33,7 @@ function AlertFormModal(props: Props) { const msTeamsChannels: Select[] = [] webhooks.forEach((hook) => { - const option = { value: hook.webhookId, label: hook.name } + const option = {value: hook.webhookId, label: hook.name} if (hook.type === SLACK) { slackChannels.push(option) } diff --git a/frontend/app/components/Assist/RecordingsList/Recordings.tsx b/frontend/app/components/Assist/RecordingsList/Recordings.tsx index 4da82724d..00d99e6b5 100644 --- a/frontend/app/components/Assist/RecordingsList/Recordings.tsx +++ b/frontend/app/components/Assist/RecordingsList/Recordings.tsx @@ -26,7 +26,7 @@ function Recordings(props: Props) { }; return ( -
+
diff --git a/frontend/app/components/Client/Audit/AuditView/AuditView.tsx b/frontend/app/components/Client/Audit/AuditView/AuditView.tsx index 4a0430622..0f21bd71c 100644 --- a/frontend/app/components/Client/Audit/AuditView/AuditView.tsx +++ b/frontend/app/components/Client/Audit/AuditView/AuditView.tsx @@ -29,7 +29,7 @@ function AuditView() { } return useObserver(() => ( -
+
diff --git a/frontend/app/components/Client/CustomFields/CustomFields.js b/frontend/app/components/Client/CustomFields/CustomFields.js index 933e6170d..01cabb532 100644 --- a/frontend/app/components/Client/CustomFields/CustomFields.js +++ b/frontend/app/components/Client/CustomFields/CustomFields.js @@ -68,7 +68,7 @@ function CustomFields(props) { const { fields, loading } = props; return ( -
+

{'Metadata'}

diff --git a/frontend/app/components/Client/Integrations/Integrations.tsx b/frontend/app/components/Client/Integrations/Integrations.tsx index 9ed478fc4..e81a7038a 100644 --- a/frontend/app/components/Client/Integrations/Integrations.tsx +++ b/frontend/app/components/Client/Integrations/Integrations.tsx @@ -108,7 +108,7 @@ function Integrations(props: Props) { return ( <> -
+
{!hideHeader && Integrations
} />} @@ -117,15 +117,7 @@ function Integrations(props: Props) {
0 ? 'p-2' : ''} - grid-cols-1 // default to 1 column - sm:grid-cols-1 // 1 column on small screens and up - md:grid-cols-2 // 2 columns on medium screens and up - lg:grid-cols-3 // 3 columns on large screens and up - xl:grid-cols-3 // 3 columns on extra-large screens + mt-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 `)}> {allIntegrations.map((integration: any) => ( -
+

Modules

  • OpenReplay's modules are a collection of advanced features that provide enhanced functionality.
  • @@ -54,7 +54,7 @@ function Modules(props: Props) {
-
+
{modulesState.map((module) => (
diff --git a/frontend/app/components/Client/ProfileSettings/ProfileSettings.js b/frontend/app/components/Client/ProfileSettings/ProfileSettings.js index f7a7dbaca..2b3ea3e8e 100644 --- a/frontend/app/components/Client/ProfileSettings/ProfileSettings.js +++ b/frontend/app/components/Client/ProfileSettings/ProfileSettings.js @@ -19,7 +19,7 @@ export default class ProfileSettings extends React.PureComponent { render() { const { account, isEnterprise } = this.props; return ( -
+
Account
} />
diff --git a/frontend/app/components/Client/Roles/Roles.tsx b/frontend/app/components/Client/Roles/Roles.tsx index 5335e9764..470cd611b 100644 --- a/frontend/app/components/Client/Roles/Roles.tsx +++ b/frontend/app/components/Client/Roles/Roles.tsx @@ -65,7 +65,7 @@ function Roles(props: Props) { return ( -
+

Roles and Access

diff --git a/frontend/app/components/Client/SessionsListingSettings.tsx b/frontend/app/components/Client/SessionsListingSettings.tsx index d60a44774..79a78061a 100644 --- a/frontend/app/components/Client/SessionsListingSettings.tsx +++ b/frontend/app/components/Client/SessionsListingSettings.tsx @@ -19,7 +19,7 @@ const connector = connect(mapStateToProps); function SessionsListingSettings(props: Props) { return ( -
+
Sessions Listing
} />
diff --git a/frontend/app/components/Client/Sites/Sites.tsx b/frontend/app/components/Client/Sites/Sites.tsx index d4ef04aae..b9f046442 100644 --- a/frontend/app/components/Client/Sites/Sites.tsx +++ b/frontend/app/components/Client/Sites/Sites.tsx @@ -111,7 +111,7 @@ const Sites = ({ loading, sites, user, init }: PropsFromRedux) => { return ( -
+
Projects
} diff --git a/frontend/app/components/Client/Users/UsersView.tsx b/frontend/app/components/Client/Users/UsersView.tsx index 5f5b53fb6..6478c7b60 100644 --- a/frontend/app/components/Client/Users/UsersView.tsx +++ b/frontend/app/components/Client/Users/UsersView.tsx @@ -36,7 +36,7 @@ function UsersView(props: Props) { }, []); return ( -
+
+

{'Webhooks'}

diff --git a/frontend/app/components/Dashboard/Widgets/CardSessionsByList.tsx b/frontend/app/components/Dashboard/Widgets/CardSessionsByList.tsx new file mode 100644 index 000000000..262916873 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CardSessionsByList.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { List, Progress, Typography } from "antd"; +import cn from "classnames"; + +interface Props { + list: any; + selected: any; + onClickHandler: (event: any, data: any) => void; +} + +function CardSessionsByList({ list, selected, onClickHandler }: Props) { + return ( + ( + onClickHandler(e, row)} // Remove onClick handler to disable click interaction + style={{ + borderBottom: '1px dotted rgba(0, 0, 0, 0.05)', + padding: '4px 10px', + lineHeight: '1px' + }} + className={cn('rounded', selected === row.name ? 'bg-active-blue' : '')} // Remove hover:bg-active-blue and cursor-pointer + > + +
+ {row.name} + {row.sessionCount} +
+ + +
+ )} + /> + + )} + /> + ); +} + +export default CardSessionsByList; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts deleted file mode 100644 index f05a16274..000000000 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CustomMetriLineChart'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx similarity index 51% rename from frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx rename to frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx index 0118617ba..68d6044e0 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart/CustomMetriLineChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/CustomMetricLineChart.tsx @@ -1,7 +1,7 @@ import React from 'react' -import { Styles } from '../../common'; -import { ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts'; -import { LineChart, Line, Legend } from 'recharts'; +import {Styles} from '../../common'; +import {ResponsiveContainer, XAxis, YAxis, CartesianGrid, Tooltip} from 'recharts'; +import {LineChart, Line, Legend} from 'recharts'; interface Props { data: any; @@ -9,46 +9,56 @@ interface Props { // seriesMap: any; colors: any; onClick?: (event, index) => void; + yaxis?: any; + label?: string; } -function CustomMetriLineChart(props: Props) { - const { data = { chart: [], namesMap: [] }, params, colors, onClick = () => null } = props; + +function CustomMetricLineChart(props: Props) { + const { + data = {chart: [], namesMap: []}, + params, + colors, + onClick = () => null, + yaxis = {...Styles.yaxis}, + label = 'Number of Sessions' + } = props; return ( - + - + - Styles.tickFormatter(val)} - label={{ + label={{ ...Styles.axisLabelLeft, - value: "Number of Sessions" + value: label || "Number of Sessions" }} /> - + - { Array.isArray(data.namesMap) && data.namesMap.map((key, index) => ( + {Array.isArray(data.namesMap) && data.namesMap.map((key, index) => ( @@ -58,4 +68,4 @@ function CustomMetriLineChart(props: Props) { ) } -export default CustomMetriLineChart +export default CustomMetricLineChart diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/index.ts b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/index.ts new file mode 100644 index 000000000..b8570c250 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart/index.ts @@ -0,0 +1 @@ +export { default } from './CustomMetricLineChart'; diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx index 84ab4805a..de6fb9c87 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart/CustomMetricOverviewChart.tsx @@ -1,51 +1,52 @@ import React from 'react' -import { Styles } from '../../common'; -import { AreaChart, ResponsiveContainer, XAxis, YAxis, Area, Tooltip } from 'recharts'; +import {Styles} from '../../common'; +import {AreaChart, ResponsiveContainer, XAxis, YAxis, Area, Tooltip} from 'recharts'; import CountBadge from '../../common/CountBadge'; -import { numberWithCommas } from 'App/utils'; +import {numberWithCommas} from 'App/utils'; interface Props { data: any; } + function CustomMetricOverviewChart(props: Props) { - const { data } = props; + const {data} = props; const gradientDef = Styles.gradientDef(); - + return (
-
+
- + {gradientDef} - - + + @@ -57,16 +58,16 @@ function CustomMetricOverviewChart(props: Props) { export default CustomMetricOverviewChart -const countView = (avg: any, unit: any) => { +const countView = (avg: any, unit: any) => { if (unit === 'mb') { - if (!avg) return 0; - const count = Math.trunc(avg / 1024 / 1024); - return numberWithCommas(count); + 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); + 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 + return avg ? numberWithCommas(avg) : 0; +} diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx index 51c5e0ce3..9dea09596 100644 --- a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricTable/CustomMetricTable.tsx @@ -1,24 +1,25 @@ import React from 'react' -import { Table } from '../../common'; -import { List } from 'immutable'; -import { filtersMap } from 'Types/filter/newFilter'; -import { NoContent, Icon } from 'UI'; -import { tableColumnName } from 'App/constants/filterOptions'; -import { numberWithCommas } from 'App/utils'; +import {Table} from '../../common'; +import {List} from 'immutable'; +import {filtersMap} from 'Types/filter/newFilter'; +import {NoContent, Icon} from 'UI'; +import {tableColumnName} from 'App/constants/filterOptions'; +import {numberWithCommas} from 'App/utils'; const getColumns = (metric) => { return [ { - key: 'name', - title: tableColumnName[metric.metricOf], - toText: name => name || 'Unidentified', - width: '70%', + key: 'name', + title: tableColumnName[metric.metricOf], + toText: name => name || 'Unidentified', + width: '70%', + icon: true, }, { - key: 'sessionCount', - title: 'Sessions', - toText: sessions => numberWithCommas(sessions), - width: '30%', + key: 'sessionCount', + title: 'Sessions', + toText: sessions => numberWithCommas(sessions), + width: '30%', }, ] } @@ -29,13 +30,14 @@ interface Props { onClick?: (filters: any) => void; isTemplate?: boolean; } + function CustomMetricTable(props: Props) { - const { metric = {}, data = { values: [] }, onClick = () => null, isTemplate } = props; + const {metric = {}, data = {values: []}, onClick = () => null, isTemplate} = props; const rows = List(data.values); const onClickHandler = (event: any, data: any) => { const filters = Array(); - let filter = { ...filtersMap[metric.metricOf] } + let filter = {...filtersMap[metric.metricOf]} filter.value = [data.name] filter.type = filter.key delete filter.key @@ -49,28 +51,28 @@ function CustomMetricTable(props: Props) { onClick(filters); } return ( -
- - - No data for the selected time period -
- } - > - - - +
+ + + No data for the selected time period +
+ } + > +
+ + ); } diff --git a/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/SessionsBy.tsx b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/SessionsBy.tsx new file mode 100644 index 000000000..5abc2d895 --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/CustomMetricsWidgets/SessionsBy.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import {Button, Space} from 'antd'; +import {filtersMap} from 'Types/filter/newFilter'; +import {Icon} from 'UI'; +import {Empty} from 'antd'; +import {ArrowRight} from "lucide-react"; +import CardSessionsByList from "Components/Dashboard/Widgets/CardSessionsByList"; +import {useModal} from "Components/ModalContext"; + +interface Props { + metric?: any; + data: any; + onClick?: (filters: any) => void; + isTemplate?: boolean; +} + +function SessionsBy(props: Props) { + const {metric = {}, data = {values: []}, onClick = () => null, isTemplate} = props; + const [selected, setSelected] = React.useState(null); + const total = data.values.length + const {openModal, closeModal} = useModal(); + + const onClickHandler = (event: any, data: any) => { + const filters = Array(); + let filter = {...filtersMap[metric.metricOf]}; + filter.value = [data.name]; + filter.type = filter.key; + delete filter.key; + delete filter.operatorOptions; + delete filter.category; + delete filter.icon; + delete filter.label; + delete filter.options; + + setSelected(data.name) + + filters.push(filter); + onClick(filters); + } + + const showMore = () => { + openModal( + { + closeModal(); + onClickHandler(null, item) + }} selected={selected}/>, { + title: metric.name, + width: 600, + }) + } + + return ( +
+ {data.values && data.values.length === 0 ? ( + + + No data for the selected time period +
+ } + /> + ) : ( +
+ + {total > 3 && ( +
+ +
+ )} +
+ )} + + ); +} + +export default SessionsBy; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx index 3ef08f392..56baccb08 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/CallWithErrors/CallWithErrors.tsx @@ -46,30 +46,28 @@ const cols = [ interface Props { data: any - metric?: any isTemplate?: boolean } function CallWithErrors(props: Props) { - const { data, metric } = props; + const { data } = props; const [search, setSearch] = React.useState('') const test = (value = '', serach: any) => getRE(serach, 'i').test(value); - const _data = search ? metric.data.chart.filter((i: any) => test(i.urlHostpath, search)) : metric.data.chart; + const _data = search ? data.chart.filter((i: any) => test(i.urlHostpath, search)) : data.chart; const write = ({ target: { name, value } }: any) => { setSearch(value) }; return ( -
- +
- {metric.data.chart.map((item, i) => + {data.chart.map((item, i) => { - const isMoreThanK = count >= 1000; - return numberWithCommas(isMoreThanK ? Math.trunc(count / 1000) + 'k' : count); + const isMoreThanK = count >= 1000; + return numberWithCommas(isMoreThanK ? Math.trunc(count / 1000) + 'k' : count); } export default { - customMetricColors, - colors, - colorsTeal, - colorsPie, - colorsx, - compareColors, - compareColorsx, - lineColor: '#2A7B7F', - lineColorCompare: '#394EFF', - strokeColor: colors[2], - xaxis: { - axisLine: { stroke: '#CCCCCC' }, - interval: 0, - dataKey: "time", - tick: { fill: '#999999', fontSize: 9 }, - tickLine: { stroke: '#CCCCCC' }, - strokeWidth: 0.5 - }, - yaxis: { - axisLine: { stroke: '#CCCCCC' }, - tick: { fill: '#999999', fontSize: 9 }, - tickLine: { stroke: '#CCCCCC' }, - }, - axisLabelLeft: { - angle: -90, - fill: '#999999', - offset: 10, - style: { textAnchor: 'middle' }, - position: 'insideLeft', - fontSize: 11 - }, - tickFormatter: val => `${countView(val)}`, - tickFormatterBytes: val => Math.round(val / 1024 / 1024), - chartMargins: { left: 0, right: 20, top: 10, bottom: 5 }, - tooltip: { - cursor: { - fill: '#f6f6f6' + customMetricColors, + colors, + colorsTeal, + colorsPie, + colorsx, + compareColors, + compareColorsx, + lineColor: '#2A7B7F', + lineColorCompare: '#394EFF', + strokeColor: compareColors[2], + xaxis: { + axisLine: {stroke: '#CCCCCC'}, + interval: 0, + dataKey: "time", + tick: {fill: '#999999', fontSize: 9}, + tickLine: {stroke: '#CCCCCC'}, + strokeWidth: 0.5 }, - contentStyle: { - padding: '5px', - background: 'white', - border: '1px solid #DDD', - borderRadius: '3px', - lineHeight: '1.25rem', - color: '#888', - fontSize: '10px' + yaxis: { + axisLine: {stroke: '#CCCCCC'}, + tick: {fill: '#999999', fontSize: 9}, + tickLine: {stroke: '#CCCCCC'}, }, - labelStyle: {}, - formatter: (value, name, { unit }) => { - if (unit && unit.trim() === 'mb') { - return numberWithCommas(Math.round(value / 1024 / 1024)) - } - return numberWithCommas(Math.round(value)) + axisLabelLeft: { + angle: -90, + fill: '#999999', + offset: 10, + style: {textAnchor: 'middle'}, + position: 'insideLeft', + fontSize: 11 }, - itemStyle: { - lineHeight: '0.75rem', - color: '#000', - fontSize: '12px' - } - }, - gradientDef: () => ( - - - - - - - - - - - ) -}; \ No newline at end of file + tickFormatter: val => `${countView(val)}`, + tickFormatterBytes: val => Math.round(val / 1024 / 1024), + chartMargins: {left: 0, right: 20, top: 10, bottom: 5}, + tooltip: { + cursor: { + fill: '#f6f6f6' + }, + contentStyle: { + padding: '5px', + background: 'white', + border: '1px solid #DDD', + borderRadius: '3px', + lineHeight: '1.25rem', + color: '#888', + fontSize: '10px' + }, + labelStyle: {}, + formatter: (value, name, {unit}) => { + if (unit && unit.trim() === 'mb') { + return numberWithCommas(Math.round(value / 1024 / 1024)) + } + return numberWithCommas(Math.round(value)) + }, + itemStyle: { + lineHeight: '0.75rem', + color: '#000', + fontSize: '12px' + } + }, + gradientDef: () => ( + + + + + + + + + + + ) +}; diff --git a/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx new file mode 100644 index 000000000..4cbf7627d --- /dev/null +++ b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import {Card, Col, Modal, Row, Typography} from "antd"; +import {Grid2x2CheckIcon, Plus} from "lucide-react"; +import NewDashboardModal from "Components/Dashboard/components/DashboardList/NewDashModal"; +import {useStore} from "App/mstore"; + +interface Props { + open: boolean; + onClose?: () => void; +} + +function AddCardSelectionModal(props: Props) { + const {metricStore} = useStore(); + const [open, setOpen] = React.useState(false); + const [isLibrary, setIsLibrary] = React.useState(false); + + const onCloseModal = () => { + setOpen(false); + props.onClose && props.onClose(); + } + + const onClick = (isLibrary: boolean) => { + if (!isLibrary) { + metricStore.init(); + } + setIsLibrary(isLibrary); + setOpen(true); + } + return ( + <> + + +
+ onClick(true)}> +
+ + Add from library + {/*

Select from 12 available

*/} +
+
+ + + onClick(false)}> +
+ + Create New Card +
+
+ + + + + + ); +} + +export default AddCardSelectionModal; diff --git a/frontend/app/components/Dashboard/components/AddToDashboardButton.tsx b/frontend/app/components/Dashboard/components/AddToDashboardButton.tsx new file mode 100644 index 000000000..c7139afb3 --- /dev/null +++ b/frontend/app/components/Dashboard/components/AddToDashboardButton.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import {Grid2x2Check} from "lucide-react" +import {Button, Modal} from "antd"; +import Select from "Shared/Select/Select"; +import {Form} from "UI"; +import {useStore} from "App/mstore"; + +interface Props { + metricId: string; +} + +function AddToDashboardButton({metricId}: Props) { + const {dashboardStore} = useStore(); + const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({ + key: i.id, + label: i.name, + value: i.dashboardId, + })); + const [selectedId, setSelectedId] = React.useState(dashboardOptions[0].value); + + const onSave = (close: any) => { + const dashboard = dashboardStore.getDashboard(selectedId) + if (dashboard) { + dashboardStore.addWidgetToDashboard(dashboard, [metricId]).then(close) + } + } + + const onClick = () => { + Modal.confirm({ + title: 'Add to selected dashboard', + icon: null, + content: ( + + - + */}
diff --git a/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx b/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx index 072e2bf44..837315497 100644 --- a/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx +++ b/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx @@ -1,134 +1,128 @@ import React from 'react'; import Breadcrumb from 'Shared/Breadcrumb'; -import { withSiteId } from 'App/routes'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { Button, PageTitle, confirm, Tooltip } from 'UI'; +import {withSiteId} from 'App/routes'; +import {withRouter, RouteComponentProps} from 'react-router-dom'; +import {Button, PageTitle, confirm, Tooltip} from 'UI'; import SelectDateRange from 'Shared/SelectDateRange'; -import { useStore } from 'App/mstore'; -import { useModal } from 'App/components/Modal'; +import {useStore} from 'App/mstore'; +import {useModal} from 'App/components/Modal'; import DashboardOptions from '../DashboardOptions'; import withModal from 'App/components/Modal/withModal'; -import { observer } from 'mobx-react-lite'; +import {observer} from 'mobx-react-lite'; import DashboardEditModal from '../DashboardEditModal'; -import AddCardModal from '../AddCardModal'; +import CreateDashboardButton from "Components/Dashboard/components/CreateDashboardButton"; +import CreateCard from "Components/Dashboard/components/DashboardList/NewDashModal/CreateCard"; +import CreateCardButton from "Components/Dashboard/components/CreateCardButton"; interface IProps { - dashboardId: string; - siteId: string; - renderReport?: any; + dashboardId: string; + siteId: string; + renderReport?: any; } + type Props = IProps & RouteComponentProps; const MAX_CARDS = 29; + function DashboardHeader(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 period = dashboardStore.period; + const {siteId, dashboardId} = props; + const {dashboardStore} = useStore(); + const {showModal} = useModal(); + const [focusTitle, setFocusedInput] = React.useState(true); + const [showEditModal, setShowEditModal] = React.useState(false); + const period = dashboardStore.period; - const dashboard: any = dashboardStore.selectedDashboard; - const canAddMore: boolean = dashboard?.widgets?.length <= MAX_CARDS; + const dashboard: any = dashboardStore.selectedDashboard; + const canAddMore: boolean = dashboard?.widgets?.length <= MAX_CARDS; - const onEdit = (isTitle: boolean) => { - dashboardStore.initDashboard(dashboard); - setFocusedInput(isTitle); - setShowEditModal(true); - }; + const onEdit = (isTitle: boolean) => { + dashboardStore.initDashboard(dashboard); + setFocusedInput(isTitle); + setShowEditModal(true); + }; - const onDelete = async () => { - if ( - await confirm({ - header: 'Confirm', - confirmButton: 'Yes, delete', - confirmation: `Are you sure you want to permanently delete this Dashboard?`, - }) - ) { - dashboardStore.deleteDashboard(dashboard).then(() => { - props.history.push(withSiteId(`/dashboard`, siteId)); - }); - } - }; - return ( -
- 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" - /> -
-
- - - -
-
- dashboardStore.setPeriod(period)} - right={true} + const onDelete = async () => { + if ( + await confirm({ + header: 'Delete Dashboard', + confirmButton: 'Yes, delete', + confirmation: `Are you sure you want to permanently delete this Dashboard?`, + }) + ) { + dashboardStore.deleteDashboard(dashboard).then(() => { + props.history.push(withSiteId(`/dashboard`, siteId)); + }); + } + }; + return ( +
+ 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" + /> +
+
+ + +
+ dashboardStore.setPeriod(period)} + right={true} + isAnt={true} + useButtonStyle={true} + /> +
+ +
+ +
+
+
+
+ {/* @ts-ignore */} + +

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

+
+
-
-
- {/* @ts-ignore */} - -

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

-
-
-
- ); + ); } export default withRouter(withModal(observer(DashboardHeader))); diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx index 9651ba90c..77350083e 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -1,67 +1,122 @@ -import { observer } from 'mobx-react-lite'; +import {LockOutlined, TeamOutlined} from '@ant-design/icons'; +import {Empty, Switch, Table, TableColumnsType, Tag, Tooltip, Typography} from 'antd'; +import {observer} from 'mobx-react-lite'; import React from 'react'; -import { NoContent, Pagination } from 'UI'; -import { useStore } from 'App/mstore'; -import { sliceListPerPage } from 'App/utils'; -import DashboardListItem from './DashboardListItem'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -import { Tooltip } from 'antd'; +import {connect} from 'react-redux'; +import {withRouter} from 'react-router-dom'; -function DashboardList() { - const { dashboardStore } = useStore(); - const list = dashboardStore.filteredList; - const dashboardsSearch = dashboardStore.filter.query; - const lenth = list.length; +import {checkForRecent} from 'App/date'; +import {useStore} from 'App/mstore'; +import Dashboard from 'App/mstore/types/dashboard'; +import {dashboardSelected, withSiteId} from 'App/routes'; - return ( - - -
- {dashboardsSearch !== '' ? 'No matching results' : "You haven't created any dashboards yet"} -
-
- } - subtext={ -
- A Dashboard is a collection of Utilize cards to visualize key user interactions or product performance metrics.
} className="text-center">Cards that can be shared across teams. -
- } - > -
-
-
Title
-
Visibility
-
Last Modified
-
+import AnimatedSVG, {ICONS} from 'Shared/AnimatedSVG/AnimatedSVG'; +import CreateDashboardButton from "Components/Dashboard/components/CreateDashboardButton"; +import {useHistory} from "react-router"; - {sliceListPerPage(list, dashboardStore.page - 1, dashboardStore.pageSize).map( - (dashboard: any) => ( - - - - ) - )} -
+function DashboardList({siteId}: { siteId: string }) { + const {dashboardStore} = useStore(); + const list = dashboardStore.filteredList; + const dashboardsSearch = dashboardStore.filter.query; + const history = useHistory(); -
-
- Showing{' '} - {Math.min(list.length, dashboardStore.pageSize)}{' '} - out of {list.length} Dashboards -
- dashboardStore.updateKey('page', page)} - limit={dashboardStore.pageSize} - debounceRequest={100} - /> -
- - ); + + const tableConfig: TableColumnsType = [ + { + title: 'Title', + dataIndex: 'name', + width: '25%', + render: (t) =>
{t}
, + }, + { + title: 'Description', + ellipsis: { + showTitle: false, + }, + width: '25%', + dataIndex: 'description', + }, + { + title: 'Last Modified', + dataIndex: 'updatedAt', + width: '16.67%', + sorter: (a, b) => a.updatedAt.toMillis() - b.updatedAt.toMillis(), + sortDirections: ['ascend', 'descend'], + render: (date) => checkForRecent(date, 'LLL dd, yyyy, hh:mm a'), + }, + { + title: 'Modified By', + dataIndex: 'updatedBy', + width: '16.67%', + sorter: (a, b) => a.updatedBy.localeCompare(b.updatedBy), + sortDirections: ['ascend', 'descend'], + }, + { + title: ( +
+
Visibility
+ + dashboardStore.updateKey('filter', { + ...dashboardStore.filter, + showMine: !dashboardStore.filter.showMine, + })} checkedChildren={'Public'} unCheckedChildren={'Private'}/> +
+ ), + width: '16.67%', + dataIndex: 'isPublic', + render: (isPublic: boolean) => ( + : }> + {isPublic ? 'Team' : 'Private'} + + ), + }, + ]; + return ( + list.length === 0 && !dashboardStore.filter.showMine ? ( + } + + imageStyle={{height: 300}} + description={( +
+
+ + Create your first dashboard. + +
+ Organize your product and technical insights as cards in dashboards to see the bigger picture,
take action and improve user experience. +
+
+ +
+
+
+ )} + /> + ) : ( +
+ `Showing ${range[0]}-${range[1]} of ${total} items`, + size: 'small', + }} + onRow={(record) => ({ + onClick: () => { + dashboardStore.selectDashboardById(record.dashboardId); + const path = withSiteId( + dashboardSelected(record.dashboardId), + siteId + ); + history.push(path); + }, + })} + />) + ); + } -export default observer(DashboardList); +export default connect((state: any) => ({ + siteId: state.getIn(['site', 'siteId']), +}))(observer(DashboardList)); diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx deleted file mode 100644 index e076dd6d5..000000000 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardListItem.tsx +++ /dev/null @@ -1,48 +0,0 @@ -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 index d60a6886c..e0ad2048c 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx @@ -1,8 +1,8 @@ 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'; +import { Input } from 'antd'; let debounceUpdate: any = () => {}; @@ -24,16 +24,15 @@ function DashboardSearch() { }; return ( -
- - -
+ dashboardStore.updateKey('filter', { ...dashboardStore.filter, query: value })} + /> ); } diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx index 1c5f1ae46..829852dd9 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardsView.tsx @@ -3,13 +3,13 @@ import withPageTitle from 'HOCs/withPageTitle'; import DashboardList from './DashboardList'; import Header from './Header'; -function DashboardsView({ history, siteId }: { history: any; siteId: string }) { - return ( -
-
- -
- ); +function DashboardsView({history, siteId}: { history: any; siteId: string }) { + return ( +
+
+ +
+ ); } export default withPageTitle('Dashboards - OpenReplay')(DashboardsView); diff --git a/frontend/app/components/Dashboard/components/DashboardList/Header.tsx b/frontend/app/components/Dashboard/components/DashboardList/Header.tsx index 9290ff799..8eb74ae6d 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/Header.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/Header.tsx @@ -1,65 +1,27 @@ import React from 'react'; -import { Button, PageTitle, Toggler, Icon } from 'UI'; -import Select from 'Shared/Select'; + +import {PageTitle} from 'UI'; + import DashboardSearch from './DashboardSearch'; -import { useStore } from 'App/mstore'; -import { observer, useObserver } from 'mobx-react-lite'; -import { withSiteId } from 'App/routes'; +import CreateDashboardButton from "Components/Dashboard/components/CreateDashboardButton"; -function Header({ history, siteId }: { history: any; siteId: string }) { - const { dashboardStore } = useStore(); - const sort = useObserver(() => dashboardStore.sort); - - const onAddDashboardClick = () => { - dashboardStore.initDashboard(); - dashboardStore.save(dashboardStore.dashboardInstance).then(async (syncedDashboard) => { - dashboardStore.selectDashboardById(syncedDashboard.dashboardId); - history.push(withSiteId(`/dashboard/${syncedDashboard.dashboardId}`, siteId)); - }); - }; - - return ( - <> -
-
- -
-
- -
-
- -
-
-
-
- - dashboardStore.updateKey('filter', { - ...dashboardStore.filter, - showMine: !dashboardStore.filter.showMine, - }) - } - /> - - setSelectedId(value.value)} + onChange={({value}: any) => setSelectedId(value.value)} /> - + - + )); } diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css index 42045607f..cda44bd57 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.module.css @@ -2,4 +2,4 @@ & > tippy-popper > tippy-tooltip { padding: 0!important; } -} +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx index 6018ef5f7..ffea3f484 100644 --- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx +++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx @@ -1,111 +1,114 @@ -import React, { useEffect } from 'react'; -import { observer } from 'mobx-react-lite'; -import { useStore } from 'App/mstore'; -import { Loader } from 'UI'; -import { withSiteId } from 'App/routes'; +import React, {useEffect} from 'react'; +import {observer} from 'mobx-react-lite'; +import {useStore} from 'App/mstore'; +import {Loader} from 'UI'; +import {withSiteId} from 'App/routes'; import withModal from 'App/components/Modal/withModal'; import DashboardWidgetGrid from '../DashboardWidgetGrid'; -import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { useModal } from 'App/components/Modal'; +import {withRouter, RouteComponentProps} from 'react-router-dom'; +import {useModal} from 'App/components/Modal'; import DashboardModal from '../DashboardModal'; import AlertFormModal from 'App/components/Alerts/AlertFormModal'; import withPageTitle from 'HOCs/withPageTitle'; import withReport from 'App/components/hocs/withReport'; import DashboardHeader from '../DashboardHeader'; +import {useHistory} from "react-router"; interface IProps { - siteId: string; - dashboardId: any; - renderReport?: any; + siteId: string; + dashboardId: any; + renderReport?: any; } type Props = IProps & RouteComponentProps; function DashboardView(props: Props) { - const { siteId, dashboardId } = props; - const { dashboardStore } = useStore(); - const { showModal, hideModal } = useModal(); + const {siteId, dashboardId} = props; + const {dashboardStore} = useStore(); + const {showModal, hideModal} = useModal(); + const history = useHistory(); - const showAlertModal = dashboardStore.showAlertModal; - const loading = dashboardStore.fetchingDashboard; - const dashboard: any = dashboardStore.selectedDashboard; + const showAlertModal = dashboardStore.showAlertModal; + const loading = dashboardStore.fetchingDashboard; + const dashboard: any = dashboardStore.selectedDashboard; - const queryParams = new URLSearchParams(props.location.search); + const queryParams = new URLSearchParams(location.search); - const trimQuery = () => { - if (!queryParams.has('modal')) return; - queryParams.delete('modal'); - props.history.replace({ - search: queryParams.toString(), - }); - }; + const trimQuery = () => { + if (!queryParams.has('modal')) return; + queryParams.delete('modal'); + history.replace({ + search: queryParams.toString(), + }); + }; - useEffect(() => { - if (showAlertModal) { - showModal( - { - hideModal(); - dashboardStore.toggleAlertModal(false) - }} - />, - { right: false, width: 580 }, - () => dashboardStore.toggleAlertModal(false) - ) - } - }, [showAlertModal]) + useEffect(() => { + if (showAlertModal) { + showModal( + { + hideModal(); + dashboardStore.toggleAlertModal(false) + }} + />, + {right: false, width: 580}, + () => dashboardStore.toggleAlertModal(false) + ) + } + }, [showAlertModal]) - const pushQuery = () => { - if (!queryParams.has('modal')) props.history.push('?modal=addMetric'); - }; + const pushQuery = () => { + if (!queryParams.has('modal')) history.push('?modal=addMetric'); + }; - useEffect(() => { - if (queryParams.has('modal')) { - onAddWidgets(); - trimQuery(); - } - }, []); + useEffect(() => { + if (queryParams.has('modal')) { + onAddWidgets(); + trimQuery(); + } + }, []); - useEffect(() => { - const isExists = dashboardStore.getDashboardById(dashboardId); - if (!isExists) { - props.history.push(withSiteId(`/dashboard`, siteId)); - } - }, [dashboardId]); + useEffect(() => { + const isExists = dashboardStore.getDashboardById(dashboardId); + if (!isExists) { + history.push(withSiteId(`/dashboard`, siteId)); + } + }, [dashboardId]); - useEffect(() => { - if (!dashboard || !dashboard.dashboardId) return; - dashboardStore.fetch(dashboard.dashboardId); - }, [dashboard]); + useEffect(() => { + if (!dashboard || !dashboard.dashboardId) return; + dashboardStore.fetch(dashboard.dashboardId); + }, [dashboard]); - const onAddWidgets = () => { - dashboardStore.initDashboard(dashboard); - showModal( - , - { right: true } + const onAddWidgets = () => { + dashboardStore.initDashboard(dashboard); + showModal( + , + {right: true} + ); + }; + + if (!dashboard) return null; + + return ( + +
+ {/* @ts-ignore */} + + + +
+
); - }; - - if (!dashboard) return null; - - return ( - -
- {/* @ts-ignore */} - - - -
-
- ); } + // @ts-ignore export default withPageTitle('Dashboards - OpenReplay')( - withReport(withRouter(withModal(observer(DashboardView)))) + withReport(withModal(observer(DashboardView))) ); diff --git a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx index f8a3bdd32..b318855ef 100644 --- a/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx +++ b/frontend/app/components/Dashboard/components/DashboardWidgetGrid/DashboardWidgetGrid.tsx @@ -1,112 +1,117 @@ import React from 'react'; -import { useStore } from 'App/mstore'; +import {useStore} from 'App/mstore'; import WidgetWrapper from '../WidgetWrapper'; -import { NoContent, Loader, Icon } from 'UI'; -import { useObserver } from 'mobx-react-lite'; +import {NoContent, Loader, Icon} from 'UI'; +import {useObserver} from 'mobx-react-lite'; import Widget from 'App/mstore/types/widget'; import MetricTypeList from '../MetricTypeList'; +import WidgetWrapperNew from "Components/Dashboard/components/WidgetWrapper/WidgetWrapperNew"; +import {Empty} from "antd"; interface Props { - siteId: string; - dashboardId: string; - onEditHandler: () => void; - id?: string; + siteId: string; + dashboardId: string; + onEditHandler: () => void; + id?: string; } + function DashboardWidgetGrid(props: Props) { - const { dashboardId, siteId } = props; - const { dashboardStore } = useStore(); - const loading = useObserver(() => dashboardStore.isLoading); - const dashboard = dashboardStore.selectedDashboard; - const list = useObserver(() => dashboard?.widgets); - const smallWidgets: Widget[] = []; - const regularWidgets: Widget[] = []; + const {dashboardId, siteId} = props; + const {dashboardStore} = useStore(); + const loading = useObserver(() => dashboardStore.isLoading); + const dashboard = dashboardStore.selectedDashboard; + const list = useObserver(() => dashboard?.widgets); + const smallWidgets: Widget[] = []; + const regularWidgets: Widget[] = []; - list.forEach((item) => { - if (item.config.col === 1) { - smallWidgets.push(item); - } else { - regularWidgets.push(item); - } - }); - - const smallWidgetsLen = smallWidgets.length; - - return useObserver(() => ( - // @ts-ignore - - -
-
- There are no cards in this dashboard -
-
- Create a card from any of the below types or pick an existing one from your library. -
-
-
- -
-
+ list?.forEach((item) => { + if (item.config.col === 1) { + smallWidgets.push(item); + } else { + regularWidgets.push(item); } - > -
{smallWidgets.length > 0 ? ( - <> -
- - Web Vitals -
+ }); - {smallWidgets && - smallWidgets.map((item: any, index: any) => ( - - - dashboard.swapWidgetPosition(dragIndex, hoverIndex) + const smallWidgetsLen = smallWidgets.length; - }dashboardId={dashboardId} - siteId={siteId} - isWidget={true} - grid="vitals" - /> - - ))} + return useObserver(() => ( + // @ts-ignore + list?.length === 0 ? : ( + + +
+
+ There are no cards in this dashboard +
+
+ Create a card from any of the below types or pick an existing one from your library. +
+
+ {/*
*/} + {/* */} + {/*
*/} +
+ } + > +
{smallWidgets.length > 0 ? ( + <> +
+ + Web Vitals +
- - ) : null} + {smallWidgets && + smallWidgets.map((item: any, index: any) => ( + + + dashboard?.swapWidgetPosition(dragIndex, hoverIndex) - {smallWidgets.length > 0 && regularWidgets.length > 0 ? ( -
- - All Cards -
- ) : null} + } dashboardId={dashboardId} + siteId={siteId} + isWidget={true} + grid="vitals" + /> +
+ ))} - {regularWidgets && - regularWidgets.map((item: any, index: any) => ( - - - dashboard.swapWidgetPosition(dragIndex, hoverIndex) - } - dashboardId={dashboardId} - siteId={siteId} - isWidget={true} - grid="other" - /> - - ))} -
- - - )); + + ) : null} + + {smallWidgets.length > 0 && regularWidgets.length > 0 ? ( +
+ + All Cards +
+ ) : null} + + {regularWidgets && + regularWidgets.map((item: any, index: any) => ( + + + dashboard?.swapWidgetPosition(dragIndex, hoverIndex) + } + dashboardId={dashboardId} + siteId={siteId} + isWidget={true} + grid="other" + /> + + ))} + + + + ) + )); } export default DashboardWidgetGrid; diff --git a/frontend/app/components/Dashboard/components/FilterSeries/AddStepButton.tsx b/frontend/app/components/Dashboard/components/FilterSeries/AddStepButton.tsx new file mode 100644 index 000000000..cc3c08f92 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FilterSeries/AddStepButton.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import FilterSelection from "Shared/Filters/FilterSelection/FilterSelection"; +import {PlusIcon} from "lucide-react"; +import {Button} from "antd"; +import {useStore} from "App/mstore"; + +interface Props { + series: any; + excludeFilterKeys: Array; +} + +function AddStepButton({series, excludeFilterKeys}: Props) { + const {metricStore} = useStore(); + const metric: any = metricStore.instance; + + const onAddFilter = (filter: any) => { + series.filter.addFilter(filter); + metric.updateKey('hasChanged', true) + } + return ( + + + + ); +} + +export default AddStepButton; diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index 14af62f56..e87a4dd54 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -1,119 +1,176 @@ -import React, { useState } from 'react'; +import React, {useState} from 'react'; import FilterList from 'Shared/Filters/FilterList'; -import { Button, Icon } from 'UI'; -import FilterSelection from 'Shared/Filters/FilterSelection'; import SeriesName from './SeriesName'; import cn from 'classnames'; -import { observer } from 'mobx-react-lite'; +import {observer} from 'mobx-react-lite'; import ExcludeFilters from './ExcludeFilters'; +import AddStepButton from "Components/Dashboard/components/FilterSeries/AddStepButton"; +import {Button, Space} from "antd"; +import {ChevronDown, ChevronUp, Trash} from "lucide-react"; + + +const FilterCountLabels = observer((props: { filters: any, toggleExpand: any }) => { + const events = props.filters.filter((i: any) => i && i.isEvent).length; + const filters = props.filters.filter((i: any) => i && !i.isEvent).length; + return
+ + {events > 0 && ( + + )} + + {filters > 0 && ( + + )} + +
; +}); + +const FilterSeriesHeader = observer((props: { + expanded: boolean, + hidden: boolean, + seriesIndex: number, + series: any, + onRemove: (seriesIndex: any) => void, + canDelete: boolean | undefined, + toggleExpand: () => void +}) => { + + const onUpdate = (name: any) => { + props.series.update('name', name) + } + return
+ + + {!props.expanded && + } + + + +
; +}) interface Props { - seriesIndex: number; - series: any; - onRemoveSeries: (seriesIndex: any) => void; - canDelete?: boolean; - supportsEmpty?: boolean; - hideHeader?: boolean; - emptyMessage?: any; - observeChanges?: () => void; - excludeFilterKeys?: Array; - canExclude?: boolean; + seriesIndex: number; + series: any; + onRemoveSeries: (seriesIndex: any) => void; + canDelete?: boolean; + supportsEmpty?: boolean; + hideHeader?: boolean; + emptyMessage?: any; + observeChanges?: () => void; + excludeFilterKeys?: Array; + canExclude?: boolean; + expandable?: boolean; } function FilterSeries(props: Props) { - const { - observeChanges = () => {}, - canDelete, - hideHeader = false, - emptyMessage = 'Add user event or filter to define the series by clicking Add Step.', - supportsEmpty = true, - excludeFilterKeys = [], - canExclude = false, - } = props; - const [expanded, setExpanded] = useState(true); - const { series, seriesIndex } = props; + const { + observeChanges = () => { + }, + canDelete, + hideHeader = false, + emptyMessage = 'Add user event or filter to define the series by clicking Add Step.', + supportsEmpty = true, + excludeFilterKeys = [], + canExclude = false, + expandable = false + } = props; + const [expanded, setExpanded] = useState(!expandable); + const {series, seriesIndex} = props; - const onAddFilter = (filter: any) => { - series.filter.addFilter(filter); - observeChanges(); - }; + const onUpdateFilter = (filterIndex: any, filter: any) => { + series.filter.updateFilter(filterIndex, filter); + observeChanges(); + }; - const onUpdateFilter = (filterIndex: any, filter: any) => { - series.filter.updateFilter(filterIndex, filter); - observeChanges(); - }; + const onFilterMove = (newFilters: any) => { + series.filter.replaceFilters(newFilters.toArray()) + observeChanges(); + } - const onFilterMove = (newFilters: any) => { - series.filter.replaceFilters(newFilters.toArray()) - observeChanges(); - } + const onChangeEventsOrder = (_: any, {name, value}: any) => { + console.log(name, value) + series.filter.updateKey(name, value); + observeChanges(); + }; - const onChangeEventsOrder = (_: any, { name, value }: any) => { - series.filter.updateKey(name, value); - observeChanges(); - }; + const onRemoveFilter = (filterIndex: any) => { + series.filter.removeFilter(filterIndex); + observeChanges(); + }; - const onRemoveFilter = (filterIndex: any) => { - series.filter.removeFilter(filterIndex); - observeChanges(); - }; + return ( +
+ {canExclude && } - return ( -
- {canExclude && } -
-
- series.update('name', name)} - /> -
- -
-
- -
- -
setExpanded(!expanded)} className="ml-3"> - -
-
-
- {expanded && ( - <> -
- {series.filter.filters.length > 0 ? ( - - ) : ( -
{emptyMessage}
+ {!hideHeader && ( +
-
-
- - - -
-
- - )} -
- ); + + {expandable && !expanded && ( + + setExpanded(!expanded)}/> +
+ ); } export default observer(FilterSeries); diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx index da3c83c3f..e0513d416 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx @@ -59,7 +59,7 @@ function FunnelIssues() { return useObserver(() => (
-

Most significant issues identified in this funnel

+

Most significant issues identified in this funnel

diff --git a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx index e32350288..3c7b27b3b 100644 --- a/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx +++ b/frontend/app/components/Dashboard/components/MetricViewHeader/MetricViewHeader.tsx @@ -1,135 +1,146 @@ import React from 'react'; -import { PageTitle, Button, Toggler, Icon } from "UI"; -import { Segmented } from 'antd'; +import {PageTitle, Button, Toggler, Icon} from "UI"; +import {Segmented} from 'antd'; import MetricsSearch from '../MetricsSearch'; import Select from 'Shared/Select'; -import { useStore } from 'App/mstore'; -import { observer, useObserver } from 'mobx-react-lite'; -import { DROPDOWN_OPTIONS } from 'App/constants/card'; +import {useStore} from 'App/mstore'; +import {observer, useObserver} from 'mobx-react-lite'; +import {DROPDOWN_OPTIONS} from 'App/constants/card'; import AddCardModal from 'Components/Dashboard/components/AddCardModal'; -import { useModal } from 'Components/Modal'; +import {useModal} from 'Components/Modal'; +import AddCardSelectionModal from "Components/Dashboard/components/AddCardSelectionModal"; +import NewDashboardModal from "Components/Dashboard/components/DashboardList/NewDashModal"; -function MetricViewHeader({ siteId }: { siteId: string }) { - const { metricStore } = useStore(); - const filter = metricStore.filter; - const { showModal } = useModal(); +function MetricViewHeader({siteId}: { siteId: string }) { + const {metricStore} = useStore(); + const filter = metricStore.filter; + const {showModal} = useModal(); + const [showAddCardModal, setShowAddCardModal] = React.useState(false); - return ( -
-
-
- + return ( +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+ + metricStore.updateKey('filter', {...filter, showMine: !filter.showMine}) + } + /> + metricStore.updateKey('sort', {by: value.value})} + plain={true} + className='ml-4' + /> +
+ + {/**/} + setShowAddCardModal(false)} + open={showAddCardModal} + isCreatingNewCard={true} + /> +
-
- -
- -
-
-
- -
-
- - metricStore.updateKey('filter', { ...filter, showMine: !filter.showMine }) - } - /> - metricStore.updateKey('sort', { by: value.value })} - plain={true} - className='ml-4' - /> -
-
-
- ); + ); } export default observer(MetricViewHeader); -function DashboardDropdown({ onChange, plain = false }: { plain?: boolean; onChange: any }) { - const { dashboardStore, metricStore } = useStore(); - const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({ - key: i.id, - label: i.name, - value: i.dashboardId - })); +function DashboardDropdown({onChange, plain = false}: { plain?: boolean; onChange: any }) { + const {dashboardStore, metricStore} = useStore(); + const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({ + key: i.id, + label: i.name, + value: i.dashboardId + })); - return ( - onChange(value)} + isMulti={true} + /> + ); } function ListViewToggler() { - const { metricStore } = useStore(); - const listView = useObserver(() => metricStore.listView); - return ( -
- - -
List
-
, - value: 'list' - }, - { - label:
- -
Grid
-
, - value: 'grid' - } - ]} - onChange={(val) => { - metricStore.updateKey('listView', val === 'list') - }} - value={listView ? 'list' : 'grid'} - /> -
- ); + const {metricStore} = useStore(); + const listView = useObserver(() => metricStore.listView); + return ( +
+ + +
List
+
, + value: 'list' + }, + { + label:
+ +
Grid
+
, + value: 'grid' + } + ]} + onChange={(val) => { + metricStore.updateKey('listView', val === 'list') + }} + value={listView ? 'list' : 'grid'} + /> +
+ ); } diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx index fa7ce7019..5edeec2d9 100644 --- a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx +++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx @@ -9,7 +9,7 @@ interface Props { } function MetricsView({ siteId }: Props) { return useObserver(() => ( -
+
diff --git a/frontend/app/components/Dashboard/components/Sessions/SessionList/SessionList.tsx b/frontend/app/components/Dashboard/components/Sessions/SessionList/SessionList.tsx deleted file mode 100644 index f85110edb..000000000 --- a/frontend/app/components/Dashboard/components/Sessions/SessionList/SessionList.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import SessionListItem from '../SessionListItem'; - -function SessionList(props) { - return ( -
- Session list - -
- ); -} - -export default SessionList; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Sessions/SessionList/index.ts b/frontend/app/components/Dashboard/components/Sessions/SessionList/index.ts deleted file mode 100644 index 779c9df2a..000000000 --- a/frontend/app/components/Dashboard/components/Sessions/SessionList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionList'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Sessions/SessionListItem/SessionListItem.tsx b/frontend/app/components/Dashboard/components/Sessions/SessionListItem/SessionListItem.tsx deleted file mode 100644 index 898d2d341..000000000 --- a/frontend/app/components/Dashboard/components/Sessions/SessionListItem/SessionListItem.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; - -interface Props { - session: any; -} -function SessionListItem(props: Props) { - return ( -
- Session list item -
- ); -} - -export default SessionListItem; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Sessions/SessionListItem/index.ts b/frontend/app/components/Dashboard/components/Sessions/SessionListItem/index.ts deleted file mode 100644 index 1c2791143..000000000 --- a/frontend/app/components/Dashboard/components/Sessions/SessionListItem/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionListItem'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Sessions/SessionWidget/SessionWidget.tsx b/frontend/app/components/Dashboard/components/Sessions/SessionWidget/SessionWidget.tsx deleted file mode 100644 index 0eb5ca1a5..000000000 --- a/frontend/app/components/Dashboard/components/Sessions/SessionWidget/SessionWidget.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import SessionList from '../SessionList'; - -function SessionWidget(props) { - return ( -
- -
- ); -} - -export default SessionWidget; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Sessions/SessionWidget/index.ts b/frontend/app/components/Dashboard/components/Sessions/SessionWidget/index.ts deleted file mode 100644 index 64b9563f5..000000000 --- a/frontend/app/components/Dashboard/components/Sessions/SessionWidget/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SessionWidget'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 2416152dc..c36d44558 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -1,5 +1,5 @@ import React, {useState, useRef, useEffect} from 'react'; -import CustomMetriLineChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetriLineChart'; +import CustomMetricLineChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricLineChart'; 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'; @@ -33,6 +33,7 @@ import ClickMapCard from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/ import InsightsCard from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard'; import SankeyChart from 'Shared/Insights/SankeyChart'; import CohortCard from '../../Widgets/CustomMetricsWidgets/CohortCard'; +import SessionsBy from "Components/Dashboard/Widgets/CustomMetricsWidgets/SessionsBy"; interface Props { metric: any; @@ -140,7 +141,7 @@ function WidgetChart(props: Props) { if (metricType === TIMESERIES) { if (viewType === 'lineChart') { return ( - + // ); } else if (viewType === 'pieChart') { return ( @@ -229,7 +236,7 @@ function WidgetChart(props: Props) { if (metricType === RETENTION) { if (viewType === 'trend') { return ( - dashboardStore.drillDownPeriod); const drillDownFilter = useObserver(() => dashboardStore.drillDownFilter); @@ -21,16 +21,15 @@ function WidgetDateRange(props: Props) { } return ( - <> - Time Range + + {label && {label}} metric.setPeriod(period)} onChange={onChangePeriod} right={true} - /> - + /> + ); } -export default WidgetDateRange; \ No newline at end of file +export default WidgetDateRange; diff --git a/frontend/app/components/Dashboard/components/WidgetForm/CardBuilder.tsx b/frontend/app/components/Dashboard/components/WidgetForm/CardBuilder.tsx new file mode 100644 index 000000000..b7066b5d8 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/CardBuilder.tsx @@ -0,0 +1,330 @@ +import React, {useEffect, useState, useCallback} from 'react'; +import {observer} from 'mobx-react-lite'; +import {useStore} from 'App/mstore'; +import {metricOf, issueOptions, issueCategories} from 'App/constants/filterOptions'; +import {FilterKey} from 'Types/filter/filterType'; +import {withSiteId, dashboardMetricDetails, metricDetails} from 'App/routes'; +import {Icon, confirm} from 'UI'; +import {Card, Input, Space, Button, Segmented} from 'antd'; +import {AudioWaveform} from "lucide-react"; +import FilterSeries from '../FilterSeries'; +import Select from 'Shared/Select'; +import MetricTypeDropdown from './components/MetricTypeDropdown'; +import MetricSubtypeDropdown from './components/MetricSubtypeDropdown'; +import {eventKeys} from 'App/types/filter/newFilter'; +import {renderClickmapThumbnail} from './renderMap'; +import FilterItem from 'Shared/Filters/FilterItem'; +import { + TIMESERIES, TABLE, CLICKMAP, FUNNEL, ERRORS, RESOURCE_MONITORING, + PERFORMANCE, WEB_VITALS, INSIGHTS, USER_PATH, RETENTION +} from 'App/constants/card'; +import {useParams} from 'react-router-dom'; +import {useHistory} from "react-router"; + +const tableOptions = metricOf.filter((i) => i.type === 'table'); + +const AIInput = ({value, setValue, placeholder, onEnter}) => ( + setValue(e.target.value)} + className='w-full mb-2' + onKeyDown={(e) => e.key === 'Enter' && onEnter()} + /> +); + +const PredefinedMessage = () => ( +
+ +
Filtering and drill-downs will be supported soon for this card type.
+
+); + +const MetricTabs = ({metric, writeOption}: any) => { + if (![TABLE].includes(metric.metricType)) return null; + + const onChange = (value: string) => { + console.log('value', value); + writeOption({ + value: { + value + }, name: 'metricOf' + }); + } + + return ( + + ) +} + +const MetricOptions = ({metric, writeOption}: any) => { + const isUserPath = metric.metricType === USER_PATH; + + return ( +
+
+ Card showing + + + {isUserPath && ( + <> + + + + )} + {metric.metricOf === FilterKey.ISSUE && metric.metricType === TABLE && ( + <> + issue type + + + )} + {metric.metricType === TABLE && + !(metric.metricOf === FilterKey.ERRORS || metric.metricOf === FilterKey.SESSIONS) && ( + <> + showing + - const undoChanges = () => { - const w = new Widget(); - metricStore.merge(w.fromJson(initialInstance), false); - }; + showing + + + )} - const fetchChartData = () => { - void aiFiltersStore.getCardData(aiAskChart, metric.toJson()) - } + {metric.metricType === INSIGHTS && ( + <> + of + - - showing - - - )} - - {metric.metricType === INSIGHTS && ( - <> - of - - - )} -
-
- - {isPathAnalysis && ( -
- {metric.startType === 'start' ? 'Start Point' : 'End Point'} - - { - metric.updateStartPoint(val); - }} onRemoveFilter={() => { - }} /> -
- )} - - {isPredefined && ( -
- -
- Filtering and drill-downs will be supported soon for this card type. -
-
- )} - {testingKey ? setAiQuery(e.target.value)} - className="w-full mb-2" - onKeyDown={handleKeyDown} - /> : null} - {testingKey ? setAiAskChart(e.target.value)} - className="w-full mb-2" - onKeyDown={handleChartKeyDown} - /> : null} - {aiFiltersStore.isLoading ? ( -
-
- Loading -
-
- ) : null} - {!isPredefined && ( -
-
- {`${isTable || isFunnel || isClickmap || isInsights || isPathAnalysis || isRetention ? 'Filter by' : 'Chart Series'}`} - {!isTable && !isFunnel && !isClickmap && !isInsights && !isPathAnalysis && !isRetention && ( - - )} -
- - {metric.series.length > 0 && - metric.series - .slice(0, isTable || isFunnel || isClickmap || isInsights || isRetention ? 1 : metric.series.length) - .map((series: any, index: number) => ( -
- metric.updateKey('hasChanged', true)} - hideHeader={isTable || isClickmap || isInsights || isPathAnalysis || isFunnel} - seriesIndex={index} - series={series} - onRemoveSeries={() => metric.removeSeries(index)} - canDelete={metric.series.length > 1} - emptyMessage={ - isTable - ? 'Filter data using any event or attribute. Use Add Step button below to do so.' - : 'Add user event or filter to define the series by clicking Add Step.' - } - /> + {metric.metricType === 'table' && + !(metric.metricOf === FilterKey.ERRORS || metric.metricOf === FilterKey.SESSIONS) && ( + <> + showing + setAiQuery(e.target.value)} + className="w-full mb-2" + onKeyDown={handleKeyDown} + /> : null} + {testingKey ? setAiAskChart(e.target.value)} + className="w-full mb-2" + onKeyDown={handleChartKeyDown} + /> : null} + {aiFiltersStore.isLoading ? ( +
+
+ Loading +
+
+ ) : null} + {!isPredefined && ( +
+
+ {`${isTable || isFunnel || isClickmap || isInsights || isPathAnalysis || isRetention ? 'Filter by' : 'Chart Series'}`} + {!isTable && !isFunnel && !isClickmap && !isInsights && !isPathAnalysis && !isRetention && ( + + )} +
+ + {metric.series.length > 0 && + metric.series + .slice(0, isTable || isFunnel || isClickmap || isInsights || isRetention ? 1 : metric.series.length) + .map((series: any, index: number) => ( +
+ metric.updateKey('hasChanged', true)} + hideHeader={isTable || isClickmap || isInsights || isPathAnalysis || isFunnel} + seriesIndex={index} + series={series} + onRemoveSeries={() => metric.removeSeries(index)} + canDelete={metric.series.length > 1} + emptyMessage={ + isTable + ? 'Filter data using any event or attribute. Use Add Step button below to do so.' + : 'Add user event or filter to define the series by clicking Add Step.' + } + /> +
+ ))} +
+ )} + +
+ +
+ + {metric.exists() && metric.hasChanged && ( + + )} +
+
+
+ {metric.exists() && ( + + )} +
+
-
-
- ); + ); } export default observer(WidgetForm); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx new file mode 100644 index 000000000..014d0f6bb --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx @@ -0,0 +1,179 @@ +import React from 'react'; +import {Card, Space, Typography, Button} from "antd"; +import {useStore} from "App/mstore"; +import {eventKeys} from "Types/filter/newFilter"; +import { + CLICKMAP, + ERRORS, + FUNNEL, + INSIGHTS, + PERFORMANCE, + RESOURCE_MONITORING, + RETENTION, + TABLE, + USER_PATH, WEB_VITALS +} from "App/constants/card"; +import FilterSeries from "Components/Dashboard/components/FilterSeries/FilterSeries"; +import {metricOf} from "App/constants/filterOptions"; +import {AudioWaveform, ChevronDown, ChevronUp, PlusIcon} from "lucide-react"; +import {observer} from "mobx-react-lite"; +import AddStepButton from "Components/Dashboard/components/FilterSeries/AddStepButton"; +import {Icon} from "UI"; +import FilterItem from "Shared/Filters/FilterItem"; +import {FilterKey} from "Types/filter/filterType"; + +function WidgetFormNew() { + const {metricStore, dashboardStore, aiFiltersStore} = useStore(); + const metric: any = metricStore.instance; + + const eventsLength = metric.series[0].filter.filters.filter((i: any) => i && i.isEvent).length; + const filtersLength = metric.series[0].filter.filters.filter((i: any) => i && !i.isEvent).length; + const isClickMap = metric.metricType === CLICKMAP; + const isPathAnalysis = metric.metricType === USER_PATH; + const excludeFilterKeys = isClickMap || isPathAnalysis ? eventKeys : []; + const hasFilters = filtersLength > 0 || eventsLength > 0; + const isPredefined = [ERRORS, PERFORMANCE, RESOURCE_MONITORING, WEB_VITALS].includes(metric.metricType); + + return isPredefined ? : ( + + + + {!hasFilters && ( + + )} + + + {hasFilters && ( + + )} + + ); +} + +export default observer(WidgetFormNew); + + +function DefineSteps({metric, excludeFilterKeys}: any) { + return ( + + Define Steps + + + ); +} + + +const FilterSection = observer(({metric, excludeFilterKeys}: any) => { + // const timeseriesOptions = metricOf.filter((i) => i.type === 'timeseries'); + // const tableOptions = metricOf.filter((i) => i.type === 'table'); + const isTable = metric.metricType === TABLE; + const isClickMap = metric.metricType === CLICKMAP; + const isFunnel = metric.metricType === FUNNEL; + const isInsights = metric.metricType === INSIGHTS; + const isPathAnalysis = metric.metricType === USER_PATH; + const isRetention = metric.metricType === RETENTION; + const canAddSeries = metric.series.length < 3; + const eventsLength = metric.series[0].filter.filters.filter((i: any) => i && i.isEvent).length; + // const cannotSaveFunnel = isFunnel && (!metric.series[0] || eventsLength <= 1); + + const isSingleSeries = isTable || isFunnel || isClickMap || isInsights || isRetention + + // const onAddFilter = (filter: any) => { + // metric.series[0].filter.addFilter(filter); + // metric.updateKey('hasChanged', true) + // } + + return ( + <> + { + metric.series.length > 0 && metric.series + .slice(0, isSingleSeries ? 1 : metric.series.length) + .map((series: any, index: number) => ( +
+ metric.updateKey('hasChanged', true)} + hideHeader={isTable || isClickMap || isInsights || isPathAnalysis || isFunnel} + seriesIndex={index} + series={series} + onRemoveSeries={() => metric.removeSeries(index)} + canDelete={metric.series.length > 1} + emptyMessage={ + isTable + ? 'Filter data using any event or attribute. Use Add Step button below to do so.' + : 'Add user event or filter to define the series by clicking Add Step.' + } + expandable={isSingleSeries} + /> +
+ )) + } + + {!isSingleSeries && canAddSeries && ( + + + + )} + + ); +}) + + +const PathAnalysisFilter = observer(({metric}: any) => ( + +
+ metric.updateStartPoint(val)} + onRemoveFilter={() => { + }} + /> +
+
+)); + +const AdditionalFilters = observer(() => { + const {metricStore, dashboardStore, aiFiltersStore} = useStore(); + const metric: any = metricStore.instance; + + return ( + + {metric.metricType === USER_PATH && } + + ) +}); + + +const PredefinedMessage = () => ( +
+ +
Filtering and drill-downs will be supported soon for this card type.
+
+); diff --git a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx index 88da5d888..a7ae663d2 100644 --- a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx @@ -42,7 +42,7 @@ function WidgetPredefinedChart(props: Props) { case FilterKey.ERRORS_PER_TYPE: return case FilterKey.ERRORS_PER_DOMAINS: - return + return case FilterKey.RESOURCES_BY_PARTY: return case FilterKey.IMPACTED_SESSIONS_BY_JS_ERRORS: @@ -52,7 +52,7 @@ function WidgetPredefinedChart(props: Props) { case FilterKey.DOMAINS_ERRORS_5XX: return case FilterKey.CALLS_ERRORS: - return + return // PERFORMANCE case FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES: diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index 0a2727132..2420319a4 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -1,148 +1,135 @@ import React from 'react'; import cn from 'classnames'; import WidgetWrapper from '../WidgetWrapper'; -import { useStore } from 'App/mstore'; -import { SegmentSelection, Button, Icon } from 'UI'; -import { observer } from 'mobx-react-lite'; -import { FilterKey } from 'Types/filter/filterType'; -import WidgetDateRange from '../WidgetDateRange/WidgetDateRange'; +import {useStore} from 'App/mstore'; +// import {SegmentSelection, Button, Icon} from 'UI'; +import {observer} from 'mobx-react-lite'; +// import {FilterKey} from 'Types/filter/filterType'; +// import WidgetDateRange from '../WidgetDateRange/WidgetDateRange'; import ClickMapRagePicker from "Components/Dashboard/components/ClickMapRagePicker"; -import DashboardSelectionModal from '../DashboardSelectionModal/DashboardSelectionModal'; -import { CLICKMAP, TABLE, TIMESERIES, RETENTION, USER_PATH } from 'App/constants/card'; -import { Space, Switch } from 'antd'; +// import DashboardSelectionModal from '../DashboardSelectionModal/DashboardSelectionModal'; +import {CLICKMAP, TABLE, TIMESERIES, RETENTION, USER_PATH} from 'App/constants/card'; +import {Space, Switch} from 'antd'; +// import AddToDashboardButton from "Components/Dashboard/components/AddToDashboardButton"; interface Props { className?: string; name: string; isEditing?: boolean; } + function WidgetPreview(props: Props) { - const [showDashboardSelectionModal, setShowDashboardSelectionModal] = React.useState(false); - const { className = '' } = props; - const { metricStore, dashboardStore } = useStore(); - const dashboards = dashboardStore.dashboards; + const {className = ''} = props; + const {metricStore, dashboardStore} = useStore(); + // const dashboards = dashboardStore.dashboards; const metric: any = metricStore.instance; - const isTimeSeries = metric.metricType === TIMESERIES; - const isTable = metric.metricType === TABLE; - const isRetention = metric.metricType === RETENTION; - const disableVisualization = metric.metricOf === FilterKey.SESSIONS || metric.metricOf === FilterKey.ERRORS; - - const changeViewType = (_, { name, value }: any) => { - metric.update({ [ name ]: value }); - } - - const canAddToDashboard = metric.exists() && dashboards.length > 0; + // const isTimeSeries = metric.metricType === TIMESERIES; + // const isTable = metric.metricType === TABLE; + // const isRetention = metric.metricType === RETENTION; + // const disableVisualization = metric.metricOf === FilterKey.SESSIONS || metric.metricOf === FilterKey.ERRORS; + // + // const changeViewType = (_, {name, value}: any) => { + // metric.update({[name]: value}); + // } return ( <> -
-
-

- {props.name} -

-
- {metric.metricType === USER_PATH && ( - { - e.preventDefault(); - metric.update({ hideExcess: !metric.hideExcess }); - }} - > - - - Hide Minor Paths - - - )} - {isTimeSeries && ( - <> - Visualization - - - )} +
+
+

+ {props.name} +

+
+ {metric.metricType === USER_PATH && ( + { + e.preventDefault(); + metric.update({hideExcess: !metric.hideExcess}); + }} + > + + + Hide Minor Paths + + + )} - {!disableVisualization && isTable && ( - <> - Visualization - - - )} + {/*{isTimeSeries && (*/} + {/* <>*/} + {/* Visualization*/} + {/* */} + {/* */} + {/*)}*/} - {isRetention && ( - <> - Visualization - - - )} -
- {metric.metricType === CLICKMAP ? ( - - ) : null} - - {/* add to dashboard */} - {metric.exists() && ( - - )} + {/*{!disableVisualization && isTable && (*/} + {/* <>*/} + {/* Visualization*/} + {/* */} + {/* */} + {/*)}*/} + + {/*{isRetention && (*/} + {/* <>*/} + {/* Visualization*/} + {/* */} + {/**/} + {/*)}*/} + +
+ {metric.metricType === CLICKMAP ? ( + + ) : null} + + + {/* add to dashboard */} + {/*{metric.exists() && (*/} + {/* */} + {/*)}*/} +
+
+
+
-
- -
-
- { canAddToDashboard && ( - setShowDashboardSelectionModal(false)} - /> - )} ); } diff --git a/frontend/app/components/Dashboard/components/WidgetView/CardViewMenu.tsx b/frontend/app/components/Dashboard/components/WidgetView/CardViewMenu.tsx new file mode 100644 index 000000000..a90ff63f1 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetView/CardViewMenu.tsx @@ -0,0 +1,99 @@ +import {useHistory} from "react-router"; +import {useStore} from "App/mstore"; +import {useObserver} from "mobx-react-lite"; +import {Button, Drawer, Dropdown, MenuProps, message, Modal} from "antd"; +import {BellIcon, EllipsisVertical, TrashIcon} from "lucide-react"; +import {toast} from "react-toastify"; +import React from "react"; +import {useModal} from "Components/ModalContext"; +import AlertFormModal from "Components/Alerts/AlertFormModal/AlertFormModal"; + +const CardViewMenu = () => { + const history = useHistory(); + const {alertsStore, dashboardStore, metricStore} = useStore(); + const widget = useObserver(() => metricStore.instance); + const {openModal, closeModal} = useModal(); + + const showAlertModal = () => { + const seriesId = widget.series[0] && widget.series[0].seriesId || ''; + alertsStore.init({query: {left: seriesId}}) + openModal(, { + // title: 'Set Alerts', + placement: 'right', + width: 620, + }); + } + + const items: MenuProps['items'] = [ + { + key: 'alert', + label: "Set Alerts", + icon: , + disabled: !widget.exists() || widget.metricType === 'predefined', + onClick: showAlertModal, + }, + { + key: 'remove', + danger: true, + label: 'Remove', + icon: , + onClick: () => { + Modal.confirm({ + title: 'Are you sure you want to remove this card?', + icon: null, + // content: 'Bla bla ...', + footer: (_, {OkBtn, CancelBtn}) => ( + <> + + + + ), + onOk: () => { + metricStore.delete(widget).then(r => { + history.goBack(); + }).catch(() => { + toast.error('Failed to remove card'); + }); + }, + }) + } + }, + ]; + + const onClick: MenuProps['onClick'] = ({key}) => { + if (key === 'alert') { + message.info('Set Alerts'); + } else if (key === 'remove') { + Modal.confirm({ + title: 'Are you sure you want to remove this card?', + icon: null, + // content: 'Bla bla ...', + footer: (_, {OkBtn, CancelBtn}) => ( + <> + + + + ), + onOk: () => { + metricStore.delete(widget).then(r => { + history.goBack(); + }).catch(() => { + toast.error('Failed to remove card'); + }); + }, + }) + } + }; + + return ( +
+ +
+ ); +}; + +export default CardViewMenu; diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index be46a16d3..02e586420 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -1,18 +1,15 @@ -import React, { useState } from 'react'; -import { useStore } from 'App/mstore'; -import cn from 'classnames'; -import { Icon, Loader, NoContent } from 'UI'; -import WidgetForm from '../WidgetForm'; +import React, {useState} from 'react'; +import {useStore} from 'App/mstore'; +import {Icon, Loader, NoContent} from 'UI'; import WidgetPreview from '../WidgetPreview'; import WidgetSessions from '../WidgetSessions'; -import { useObserver } from 'mobx-react-lite'; -import WidgetName from '../WidgetName'; -import { withSiteId } from 'App/routes'; +import {useObserver} from 'mobx-react-lite'; +import {dashboardMetricDetails, metricDetails, withSiteId} from 'App/routes'; import FunnelIssues from '../Funnels/FunnelIssues/FunnelIssues'; import Breadcrumb from 'Shared/Breadcrumb'; -import { FilterKey } from 'Types/filter/filterType'; -import { Prompt } from 'react-router'; -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; +import {FilterKey} from 'Types/filter/filterType'; +import {Prompt, useHistory} from 'react-router'; +import AnimatedSVG, {ICONS} from 'Shared/AnimatedSVG/AnimatedSVG'; import { TIMESERIES, TABLE, @@ -21,36 +18,46 @@ import { INSIGHTS, USER_PATH, RETENTION, - } from 'App/constants/card'; +} from 'App/constants/card'; import CardIssues from '../CardIssues'; import CardUserList from '../CardUserList/CardUserList'; +import WidgetViewHeader from "Components/Dashboard/components/WidgetView/WidgetViewHeader"; +import WidgetFormNew from "Components/Dashboard/components/WidgetForm/WidgetFormNew"; +import {Space} from "antd"; +import {renderClickmapThumbnail} from "Components/Dashboard/components/WidgetForm/renderMap"; +import Widget from "App/mstore/types/widget"; interface Props { history: any; match: any; siteId: any; } + function WidgetView(props: Props) { const { match: { - params: { siteId, dashboardId, metricId }, + params: {siteId, dashboardId, metricId}, }, } = props; - const { metricStore, dashboardStore } = useStore(); + // const siteId = location.pathname.split('/')[1]; + // const dashboardId = location.pathname.split('/')[3]; + const {metricStore, dashboardStore} = useStore(); const widget = useObserver(() => metricStore.instance); const loading = useObserver(() => metricStore.isLoading); const [expanded, setExpanded] = useState(!metricId || metricId === 'create'); const hasChanged = useObserver(() => widget.hasChanged); - const dashboards = useObserver(() => dashboardStore.dashboards); const dashboard = useObserver(() => dashboards.find((d: any) => d.dashboardId == dashboardId)); const dashboardName = dashboard ? dashboard.name : null; const [metricNotFound, setMetricNotFound] = useState(false); + const history = useHistory(); + const [initialInstance, setInitialInstance] = useState(); + const isClickMap = widget.metricType === CLICKMAP; React.useEffect(() => { if (metricId && metricId !== 'create') { metricStore.fetch(metricId, dashboardStore.period).catch((e) => { - if (e.status === 404 || e.status === 422) { + if (e.response.status === 404 || e.response.status === 422) { setMetricNotFound(true); } }); @@ -59,13 +66,44 @@ function WidgetView(props: Props) { } }, []); - const onBackHandler = () => { - props.history.goBack(); + // const onBackHandler = () => { + // props.history.goBack(); + // }; + // + // const openEdit = () => { + // if (expanded) return; + // setExpanded(true); + // }; + + const undoChanges = () => { + const w = new Widget(); + metricStore.merge(w.fromJson(initialInstance), false); }; - const openEdit = () => { - if (expanded) return; - setExpanded(true); + const onSave = async () => { + const wasCreating = !widget.exists(); + if (isClickMap) { + try { + widget.thumbnail = await renderClickmapThumbnail(); + } catch (e) { + console.error(e); + } + } + const savedMetric = await metricStore.save(widget); + setInitialInstance(widget.toJson()); + if (wasCreating) { + if (parseInt(dashboardId, 10) > 0) { + history.replace( + withSiteId(dashboardMetricDetails(dashboardId, savedMetric.metricId), siteId) + ); + void dashboardStore.addWidgetToDashboard( + dashboardStore.getDashboard(parseInt(dashboardId, 10))!, + [savedMetric.metricId] + ); + } else { + history.replace(withSiteId(metricDetails(savedMetric.metricId), siteId)); + } + } }; return useObserver(() => ( @@ -80,57 +118,47 @@ function WidgetView(props: Props) { }} /> -
+
- +
Metric not found!
} > -
-
-

- metricStore.merge({ name })} canEdit={expanded} /> -

-
setExpanded(!expanded)}> -
- {expanded ? 'Collapse' : 'Edit'} - -
-
-
+ + - {expanded && } -
+ - - - {widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && ( - <> - {(widget.metricType === TABLE || widget.metricType === TIMESERIES || widget.metricType === CLICKMAP || widget.metricType === INSIGHTS) && } - {widget.metricType === FUNNEL && } - - )} + {/*
*/} + {/* */} + {/*
*/} - {widget.metricType === USER_PATH && } - {widget.metricType === RETENTION && } + + + {widget.metricOf !== FilterKey.SESSIONS && widget.metricOf !== FilterKey.ERRORS && ( + <> + {(widget.metricType === TABLE || widget.metricType === TIMESERIES || widget.metricType === CLICKMAP || widget.metricType === INSIGHTS) && + } + {widget.metricType === FUNNEL && } + + )} + + {widget.metricType === USER_PATH && } + {widget.metricType === RETENTION && } +
diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx new file mode 100644 index 000000000..49a22e3a1 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import cn from "classnames"; +import WidgetName from "Components/Dashboard/components/WidgetName"; +import {useStore} from "App/mstore"; +import {useObserver} from "mobx-react-lite"; +import AddToDashboardButton from "Components/Dashboard/components/AddToDashboardButton"; +import WidgetDateRange from "Components/Dashboard/components/WidgetDateRange/WidgetDateRange"; +import {Button, Space} from "antd"; +import CardViewMenu from "Components/Dashboard/components/WidgetView/CardViewMenu"; + +interface Props { + onClick?: () => void; + onSave: () => void; + undoChanges?: () => void; +} + +function WidgetViewHeader({onClick, onSave, undoChanges}: Props) { + const {metricStore, dashboardStore} = useStore(); + const widget = useObserver(() => metricStore.instance); + + return ( +
+

+ metricStore.merge({name})} + canEdit={true}/> +

+ + + + + + +
+ ); +} + +export default WidgetViewHeader; diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx index 548bc9570..c9cbac62f 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/AlertButton.tsx @@ -1,27 +1,34 @@ import React from 'react'; import WidgetIcon from './WidgetIcon'; -import { useStore } from 'App/mstore'; +import {useStore} from 'App/mstore'; +import {Button} from "antd"; +import {BellIcon} from "lucide-react"; +import {useModal} from "Components/ModalContext"; +import AlertFormModal from "Components/Alerts/AlertFormModal/AlertFormModal"; interface Props { seriesId: string; - initAlert: Function; + initAlert?: Function; } + function AlertButton(props: Props) { - const { seriesId } = props; - const { dashboardStore, alertsStore } = useStore(); + const {seriesId} = props; + const {dashboardStore, alertsStore} = useStore(); + const {openModal, closeModal} = useModal(); const onClick = () => { - dashboardStore.toggleAlertModal(true); - alertsStore.init({ query: { left: seriesId }}) + // dashboardStore.toggleAlertModal(true); + alertsStore.init({query: {left: seriesId}}) + openModal(, { + // title: 'Set Alerts', + placement: 'right', + width: 620, + }); } return ( -
- -
+
+ ); +} + +export default CardMenu; diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx index a84264c64..0a342af52 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx @@ -97,7 +97,7 @@ function WidgetWrapper(props: Props & RouteComponentProps) { return (
void; + isWidget?: boolean; + hideName?: boolean; + grid?: string; + isGridView?: boolean; +} + +function WidgetWrapperNew(props: Props & RouteComponentProps) { + const {dashboardStore} = useStore(); + const { + isWidget = false, + active = false, + index = 0, + moveListItem = null, + isPreview = false, + isTemplate = false, + siteId, + grid = '', + isGridView = false, + } = props; + const widget: any = props.widget; + const isTimeSeries = widget.metricType === TIMESERIES; + const isPredefined = widget.metricType === 'predefined'; + const dashboard = dashboardStore.selectedDashboard; + + const [{isDragging}, dragRef] = useDrag({ + type: 'item', + item: {index, grid}, + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + }); + + const [{isOver, canDrop}, dropRef] = useDrop({ + accept: 'item', + drop: (item: any) => { + if (item.index === index || item.grid !== grid) return; + moveListItem(item.index, index); + }, + canDrop(item) { + return item.grid === grid; + }, + collect: (monitor: any) => ({ + isOver: monitor.isOver(), + canDrop: monitor.canDrop(), + }), + }); + + const onChartClick = () => { + if (!isWidget || isPredefined) return; + props.history.push( + withSiteId(dashboardMetricDetails(dashboard?.dashboardId, widget.metricId), siteId) + ); + }; + + const ref: any = useRef(null); + const dragDropRef: any = dragRef(dropRef(ref)); + const addOverlay = + isTemplate || + (!isPredefined && + isWidget && + widget.metricOf !== FilterKey.ERRORS && + widget.metricOf !== FilterKey.SESSIONS); + + return ( + null} + id={`widget-${widget.widgetId}`} + title={!props.hideName ? widget.name : null} + extra={isWidget ? [ +
+ {!isPredefined && isTimeSeries && !isGridView && ( + + )} + + {!isTemplate && !isGridView && ( + + )} +
+ ] : []} + styles={{ + header: { + padding: '0 14px', + borderBottom: 'none', + minHeight: 44, + fontWeight: 500, + fontSize: 14, + }, + body: { + padding: 0, + }, + }} + > + {!isTemplate && isWidget && isPredefined && ( + +
+ {'Cannot drill down system provided metrics'} +
+
+ )} + + {addOverlay && } + + +
+ +
+
+
+ ); +} + +export default withRouter(observer(WidgetWrapperNew)); diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/widgetWrapper.module.css b/frontend/app/components/Dashboard/components/WidgetWrapper/widgetWrapper.module.css index c9d6530ec..899e4007e 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/widgetWrapper.module.css +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/widgetWrapper.module.css @@ -13,6 +13,7 @@ left: 0; right: 0; } + .overlayDashboard { - top: 20%!important; + top: 40px !important; } diff --git a/frontend/app/components/ForgotPassword/ForgotPassword.tsx b/frontend/app/components/ForgotPassword/ForgotPassword.tsx index cb6aced80..2f962e94b 100644 --- a/frontend/app/components/ForgotPassword/ForgotPassword.tsx +++ b/frontend/app/components/ForgotPassword/ForgotPassword.tsx @@ -23,7 +23,7 @@ function ForgotPassword(props: Props) {
-
+
{creatingNewPassword ? (

Welcome, join your organization by creating a new password diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx index b3aef756c..c4a35fded 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx @@ -1,146 +1,148 @@ -import { durationFormatted } from 'App/date'; +import {durationFormatted} from 'App/date'; import React from 'react'; import FunnelStepText from './FunnelStepText'; -import { Icon } from 'UI'; +import {Icon} from 'UI'; +import {Space} from "antd"; interface Props { - filter: any; - index?: number; - focusStage?: (index: number, isFocused: boolean) => void - focusedFilter?: number | null + filter: any; + index?: number; + focusStage?: (index: number, isFocused: boolean) => void + focusedFilter?: number | null } function FunnelBar(props: Props) { - const { filter, index, focusStage, focusedFilter } = props; + const {filter, index, focusStage, focusedFilter} = props; - const isFocused = focusedFilter && index ? focusedFilter === index - 1 : false; - return ( -
- -
-
-
- {filter.completedPercentageTotal}% -
-
-
focusStage?.(index! - 1, filter.isActive)} - className={'hover:border border-red-lightest'} - /> -
-
- {/* @ts-ignore */} -
- - {filter.sessionsCount} Sessions - + const isFocused = focusedFilter && index ? focusedFilter === index - 1 : false; + return ( +
+ +
+
+
+ {filter.completedPercentageTotal}% +
+
+
focusStage?.(index! - 1, filter.isActive)} + className={'hover:border border-red-lightest'} + /> +
+
+ {/* @ts-ignore */} +
+ + {filter.sessionsCount} Sessions + ({filter.completedPercentage}%) Completed +
+ + 0 ? "red" : "gray-light"} size={16}/> + 0 ? 'color-red' : 'disabled')}>{filter.droppedCount} Sessions + 0 ? 'color-red' : 'disabled')}>({filter.droppedPercentage}%) Dropped + +
- {/* @ts-ignore */} -
- - {filter.droppedCount} Sessions - ({filter.droppedPercentage}%) Dropped -
-
-
- ); + ); } export function UxTFunnelBar(props: Props) { - const { filter } = props; + const {filter} = props; - return ( -
-
{filter.title}
-
-
-
- {((filter.completed/(filter.completed+filter.skipped))*100).toFixed(1)}% -
-
-
-
- {/* @ts-ignore */} -
-
- - {filter.completed}completed this step -
-
- - + return ( +
+
{filter.title}
+
+
+
+ {((filter.completed / (filter.completed + filter.skipped)) * 100).toFixed(1)}% +
+
+
+
+ {/* @ts-ignore */} +
+
+ + {filter.completed}completed this step +
+
+ + {durationFormatted(filter.avgCompletionTime)} - + Avg. completion time -
+
+
+ {/* @ts-ignore */} +
+ + {filter.skipped} skipped +
+
- {/* @ts-ignore */} -
- - {filter.skipped} skipped -
-
-
- ); + ); } export default FunnelBar; const calculatePercentage = (completed: number, dropped: number) => { - const total = completed + dropped; - if (dropped === 0) return 100; - if (total === 0) return 0; + const total = completed + dropped; + if (dropped === 0) return 100; + if (total === 0) return 0; - return Math.round((completed / dropped) * 100); + return Math.round((completed / dropped) * 100); }; diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx index 5feefb888..22f95d3c2 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx @@ -8,7 +8,7 @@ import { NoContent, Icon } from 'UI'; import { useModal } from 'App/components/Modal'; interface Props { - metric: Widget; + metric?: Widget; isWidget?: boolean; data: any; } diff --git a/frontend/app/components/Login/Login.tsx b/frontend/app/components/Login/Login.tsx index e4140308f..519f693c3 100644 --- a/frontend/app/components/Login/Login.tsx +++ b/frontend/app/components/Login/Login.tsx @@ -78,7 +78,7 @@ const Login: React.FC = ({errors, loading, authDetails, login, setJw
-
+

Login to your account

diff --git a/frontend/app/components/ModalContext.tsx b/frontend/app/components/ModalContext.tsx new file mode 100644 index 000000000..f7402dd5c --- /dev/null +++ b/frontend/app/components/ModalContext.tsx @@ -0,0 +1,64 @@ +import React, {createContext, useContext, useState, ReactNode} from 'react'; +import {Drawer} from 'antd'; + +interface ModalConfig { + title?: string; + placement?: 'top' | 'right' | 'bottom' | 'left'; + width?: number; +} + +interface ModalContextType { + openModal: (content: ReactNode, config?: ModalConfig) => void; + closeModal: () => void; +} + +const defaultConfig: ModalConfig = { + title: 'Modal Title', + placement: 'right', + width: 428 +}; + +const ModalContext = createContext({ + openModal: () => { + }, + closeModal: () => { + } +}); + +export const useModal = () => useContext(ModalContext); + +export const ModalProvider = ({children}: { children: ReactNode }) => { + const [showModal, setShowModal] = useState(false); + const [modalContent, setModalContent] = useState(null); + const [modalConfig, setModalConfig] = useState(defaultConfig); + + const openModal = (content: ReactNode, config: ModalConfig = defaultConfig) => { + setModalContent(content); + setModalConfig(config); + setShowModal(true); + }; + + const closeModal = () => { + setShowModal(false); + setTimeout(() => { + setModalContent(null); + setModalConfig(defaultConfig); + }, 200) + }; + + return ( + + {children} + + {modalContent} + + + ); +}; diff --git a/frontend/app/components/UsabilityTesting/UsabilityTesting.tsx b/frontend/app/components/UsabilityTesting/UsabilityTesting.tsx index b39e2128b..9c5d374d9 100644 --- a/frontend/app/components/UsabilityTesting/UsabilityTesting.tsx +++ b/frontend/app/components/UsabilityTesting/UsabilityTesting.tsx @@ -104,11 +104,11 @@ function TestsTable() { /> -
+
- +

Usability Tests - +

diff --git a/frontend/app/components/shared/MainSearchBar/components/TagList.tsx b/frontend/app/components/shared/MainSearchBar/components/TagList.tsx index 0f1bdbc92..983622a32 100644 --- a/frontend/app/components/shared/MainSearchBar/components/TagList.tsx +++ b/frontend/app/components/shared/MainSearchBar/components/TagList.tsx @@ -7,8 +7,8 @@ import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import { FilterKey } from 'Types/filter/filterType'; import { addOptionsToFilter } from 'Types/filter/newFilter'; -import { Button, Icon, confirm } from 'UI'; -import { Typography } from 'antd'; +import { Icon, confirm } from 'UI'; +import { Button, Typography } from 'antd'; import { toast } from 'react-toastify'; function TagList(props: { @@ -44,9 +44,15 @@ function TagList(props: { }); }; return ( - ); } diff --git a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx index f5cdfb889..5770d5e76 100644 --- a/frontend/app/components/shared/SavedSearch/SavedSearch.tsx +++ b/frontend/app/components/shared/SavedSearch/SavedSearch.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from 'react'; -import { Button, Icon } from 'UI'; +import { Icon } from 'UI'; +import { Button } from 'antd'; import { connect } from 'react-redux'; import { fetchList as fetchListSavedSearch } from 'Duck/search'; import cn from 'classnames'; @@ -27,11 +28,14 @@ function SavedSearch(props: Props) { return (
{ savedSearch.exists() && ( diff --git a/frontend/app/components/shared/Select/Select.tsx b/frontend/app/components/shared/Select/Select.tsx index 9ebb378b6..f4f5f32a5 100644 --- a/frontend/app/components/shared/Select/Select.tsx +++ b/frontend/app/components/shared/Select/Select.tsx @@ -64,7 +64,7 @@ export default function ({ menu: (provided: any, state: any) => ({ ...provided, top: 31, - borderRadius: '3px', + borderRadius: '.5rem', right: right ? 0 : undefined, border: `1px solid ${colors['gray-light']}`, // borderRadius: '3px', diff --git a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx index 706f2d0fc..b1173b315 100644 --- a/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx +++ b/frontend/app/components/shared/SelectDateRange/SelectDateRange.tsx @@ -1,6 +1,6 @@ import { DownOutlined } from '@ant-design/icons'; import Period from 'Types/app/period'; -import { Dropdown, DatePicker } from 'antd'; +import { Dropdown, Button } from 'antd'; import cn from 'classnames'; import { observer } from 'mobx-react-lite'; import React from 'react'; @@ -20,14 +20,14 @@ interface Props { timezone?: string; isAnt?: boolean; small?: boolean; + useButtonStyle?: boolean; // New prop to control button style [x: string]: any; } function SelectDateRange(props: Props) { const [isCustom, setIsCustom] = React.useState(false); - const [isCustomOpen, setIsCustomOpen] = React.useState(false); - const { right = false, period, disableCustom = false, timezone } = props; + const { right = false, period, disableCustom = false, timezone, useButtonStyle = false } = props; let selectedValue = DATE_RANGE_OPTIONS.find( (obj: any) => obj.value === period.rangeName ); @@ -41,13 +41,11 @@ function SelectDateRange(props: Props) { setIsCustom(true); }, 1); } else { - // @ts-ignore props.onChange(new Period({ rangeName: value })); } }; const onApplyDateRange = (value: any) => { - // @ts-ignore const range = new Period({ rangeName: CUSTOM_RANGE, start: value.start, @@ -61,34 +59,31 @@ function SelectDateRange(props: Props) { const customRange = isCustomRange ? period.rangeFormatted() : ''; if (props.isAnt) { - const onAntUpdate = (val: any) => { - onChange(val); + const menuProps = { + items: options.map((opt) => ({ + label: opt.label, + key: opt.value, + })), + defaultSelectedKeys: selectedValue?.value ? [selectedValue.value] : undefined, + onClick: (e: any) => { + onChange(e.key); + }, }; + return (
- ({ - label: opt.label, - key: opt.value, - })), - defaultSelectedKeys: selectedValue?.value - ? [selectedValue.value] - : undefined, - onClick: (e: any) => { - onChange(e.key); - }, - }} - onChange={onAntUpdate} - style={{ width: 170 }} - defaultValue={selectedValue?.value ?? undefined} - > -
-
{isCustomRange ? customRange : selectedValue?.label}
- -
+ + {useButtonStyle ? ( + + ) : ( +
+ {isCustomRange ? customRange : selectedValue?.label} + +
+ )}
{isCustom && (
); } + return (