diff --git a/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx b/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx index 09c1ce06c..e49ac65d9 100644 --- a/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx +++ b/frontend/app/components/Assist/AssistSearchField/AssistSearchField.tsx @@ -7,7 +7,7 @@ import { edit as editFilter, fetchFilterSearch, } from 'Duck/liveSearch'; -import { Button } from 'UI'; +import { Button } from 'antd'; import { useModal } from 'App/components/Modal'; import SessionSearchField from 'Shared/SessionSearchField'; import { MODULES } from 'Components/Client/Modules'; @@ -42,11 +42,11 @@ function AssistSearchField(props: Props) { {props.isEnterprise && props.modules.includes(MODULES.OFFLINE_RECORDINGS) - ? : null + ? : null } - + - - )} - - )} + return ( +
+ {data.values && data.values.length === 0 ? ( + + + No data for the selected time period +
+ } + /> + ) : ( +
+ {/* TODO - remove slice once the api pagination is fixed */} + + {total > 3 && ( +
+ +
+ )}
- ); + )} + + ); } export default SessionsBy; diff --git a/frontend/app/components/Dashboard/Widgets/ListWithIcons.tsx b/frontend/app/components/Dashboard/Widgets/ListWithIcons.tsx new file mode 100644 index 000000000..0b69fb4cc --- /dev/null +++ b/frontend/app/components/Dashboard/Widgets/ListWithIcons.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { List, Progress, Typography } from 'antd'; +import cn from 'classnames'; + +interface ListItem { + icon?: any; + title: string; + progress: number; + value?: number; +} + +interface Props { + list: ListItem[]; +} + +function ListWithIcons({ list = [] }: 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')} // Remove hover:bg-active-blue and cursor-pointer + > + +
+ {row.name} + {row.value} +
+ + + + )} + /> +
+ )} + /> + ); +} + +export default ListWithIcons; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx index c33ef2be8..ab2e4b6b3 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/ErrorsPerDomain/ErrorsPerDomain.tsx @@ -1,38 +1,34 @@ import React from 'react'; -import { NoContent } from 'UI'; -import { Styles } from '../../common'; -import { numberWithCommas } from 'App/utils'; -import Bar from './Bar'; -import { NO_METRIC_DATA } from 'App/constants/messages' +import { Icon, NoContent } from 'UI'; +import { NO_METRIC_DATA } from 'App/constants/messages'; +import ListWithIcons from 'Components/Dashboard/Widgets/ListWithIcons'; interface Props { - data: any + data: any; } + function ErrorsPerDomain(props: Props) { - const { data } = props; - // const firstAvg = 10; - const firstAvg = data.chart[0] && data.chart[0].errorsCount; - return ( - -
- {data.chart.map((item, i) => - - )} -
-
- ); + const { data } = props; + const highest = data.chart[0] && data.chart[0].errorsCount; + const list = data.chart.slice(0, 4).map((item: any) => ({ + name: item.domain, + icon: , + value: Math.round(item.errorsCount), + progress: Math.round((item.errorsCount * 100) / highest) + })); + + return ( + +
+ +
+
+ ); } export default ErrorsPerDomain; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx index ca6c836e3..1644d77d1 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser/SessionsPerBrowser.tsx @@ -2,43 +2,43 @@ import React from 'react'; import { NoContent } from 'UI'; import { Styles } from '../../common'; import Bar from './Bar'; -import { NO_METRIC_DATA } from 'App/constants/messages' +import { NO_METRIC_DATA } from 'App/constants/messages'; interface Props { - data: any - metric?: any + data: any; } -function SessionsPerBrowser(props: Props) { - const { data, metric } = props; - const firstAvg = metric.data.chart[0] && metric.data.chart[0].count; - const getVersions = item => { - return Object.keys(item) - .filter(i => i !== 'browser' && i !== 'count' && i !== 'time' && i !== 'timestamp') - .map(i => ({ key: 'v' +i, value: item[i]})) - } - return ( - -
- {metric.data.chart.map((item, i) => - - )} -
-
- ); +function SessionsPerBrowser(props: Props) { + const { data } = props; + const firstAvg = data.chart[0] && data.chart[0].count; + + const getVersions = item => { + return Object.keys(item) + .filter(i => i !== 'browser' && i !== 'count' && i !== 'time' && i !== 'timestamp') + .map(i => ({ key: 'v' + i, value: item[i] })); + }; + return ( + +
+ {data.chart.map((item, i) => + + )} +
+
+ ); } export default SessionsPerBrowser; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx index 758a6575a..7a2afeb9d 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SlowestDomains/SlowestDomains.tsx @@ -1,38 +1,35 @@ import React from 'react'; -import { NoContent } from 'UI'; -import { Styles } from '../../common'; -import { numberWithCommas } from 'App/utils'; -import Bar from './Bar'; -import { NO_METRIC_DATA } from 'App/constants/messages' +import { Icon, NoContent } from 'UI'; +import { NO_METRIC_DATA } from 'App/constants/messages'; +import ListWithIcons from 'Components/Dashboard/Widgets/ListWithIcons'; interface Props { - data: any - metric?: any + data: any; } + function SlowestDomains(props: Props) { - const { data, metric } = props; - const firstAvg = metric.data.chart[0] && metric.data.chart[0].value; - return ( - -
- {metric.data.chart.map((item, i) => - - )} -
-
- ); + const { data } = props; + // TODO - move this to the store + const highest = data.chart[0]?.value; + const list = data.chart.slice(0, 4).map((item: any) => ({ + name: item.domain, + icon: , + value: Math.round(item.value) + 'ms', + progress: Math.round((item.value * 100) / highest) + })); + + return ( + +
+ +
+
+ ); } export default SlowestDomains; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.js b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.js index 6b944518c..707f21eb9 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.js +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/Scale.js @@ -1,25 +1,25 @@ -import React from 'react' +import React from 'react'; import { Styles } from '../../common'; import cn from 'classnames'; import stl from './scale.module.css'; function Scale({ colors }) { - const lastIndex = (Styles.colorsTeal.length - 1) - + const lastIndex = (Styles.compareColors.length - 1); + return ( -
- {Styles.colorsTeal.map((c, i) => ( +
+ {Styles.compareColors.map((c, i) => (
- { i === 0 &&
Slow
} - { i === lastIndex &&
Fast
} + {i === 0 &&
Slow
} + {i === lastIndex &&
Fast
}
))}
- ) + ); } -export default Scale +export default Scale; diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.module.css b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.module.css index a42f4af12..ec2c46f02 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.module.css +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.module.css @@ -15,7 +15,7 @@ &:focus, &:hover { - fill: $teal !important; + fill: #2E3ECC !important; outline: 0; } } @@ -25,23 +25,23 @@ } .heat_index5 { - fill: #3EAAAF !important; + fill: #B0B8FF !important; } .heat_index4 { - fill:#5FBABF !important; + fill:#6171FF !important; } .heat_index3 { - fill: #7BCBCF !important; + fill: #394EFF !important; } .heat_index2 { - fill: #96DCDF !important; + fill: #2E3ECC !important; } .heat_index1 { - fill: #ADDCDF !important; + fill: #222F99 !important; } .tooltip { @@ -52,4 +52,4 @@ background-color: white; font-size: 12px; line-height: 1.2; -} \ No newline at end of file +} diff --git a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx index ca049a677..ef9565b2b 100644 --- a/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx +++ b/frontend/app/components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation/SpeedIndexByLocation.tsx @@ -8,99 +8,105 @@ import WorldMap from '@svg-maps/world'; import { SVGMap } from 'react-svg-map'; import stl from './SpeedIndexByLocation.module.css'; import cn from 'classnames'; -import { NO_METRIC_DATA } from 'App/constants/messages' +import { NO_METRIC_DATA } from 'App/constants/messages'; interface Props { - metric?: any; + data?: any; } + function SpeedIndexByLocation(props: Props) { - const { metric } = props; - const wrapper: any = React.useRef(null); - let map: any = null; - const [tooltipStyle, setTooltipStyle] = React.useState({ display: 'none' }); - const [pointedLocation, setPointedLocation] = React.useState(null); - const dataMap: any = React.useMemo(() => { - const data: any = {}; - const max = metric.data.chart.reduce((acc: any, item: any) => Math.max(acc, item.value), 0); - const min = metric.data.chart.reduce((acc: any, item: any) => Math.min(acc, item.value), 0); - metric.data.chart.forEach((item: any) => { - if (!item || !item.userCountry) { return } - item.perNumber = positionOfTheNumber(min, max, item.value, 5); - data[item.userCountry.toLowerCase()] = item; - }); - return data; - }, []); + const { data } = props; + console.log('data', data); + const wrapper: any = React.useRef(null); + let map: any = null; + const [tooltipStyle, setTooltipStyle] = React.useState({ display: 'none' }); + const [pointedLocation, setPointedLocation] = React.useState(null); - const getLocationClassName = (location: any) => { - const i = dataMap[location.id] ? dataMap[location.id].perNumber : 0; - const cls = stl['heat_index' + i]; - return cn(stl.location, cls); + const dataMap: any = React.useMemo(() => { + const _data: any = {}; + const max = data.chart?.reduce((acc: any, item: any) => Math.max(acc, item.value), 0); + const min = data.chart?.reduce((acc: any, item: any) => Math.min(acc, item.value), 0); + data.chart?.forEach((item: any) => { + console.log('item', item); + if (!item || !item.userCountry) { + return; + } + item.perNumber = positionOfTheNumber(min, max, item.value, 5); + _data[item.userCountry.toLowerCase()] = item; + }); + return _data; + }, [data.chart]); + + const getLocationClassName = (location: any) => { + const i = dataMap[location.id] ? dataMap[location.id].perNumber : 0; + const cls = stl['heat_index' + i]; + return cn(stl.location, cls); + }; + + const getLocationName = (event: any) => { + if (!event) return null; + const id = event.target.attributes.id.value; + const name = event.target.attributes.name.value; + const percentage = dataMap[id] ? dataMap[id].perNumber : 0; + return { name, id, percentage }; + }; + + const handleLocationMouseOver = (event: any) => { + const pointedLocation = getLocationName(event); + setPointedLocation(pointedLocation); + }; + + const handleLocationMouseOut = () => { + setTooltipStyle({ display: 'none' }); + setPointedLocation(null); + }; + + const handleLocationMouseMove = (event: any) => { + const tooltipStyle = { + display: 'block', + top: event.clientY + 10, + left: event.clientX - 100 }; + setTooltipStyle(tooltipStyle); + }; - const getLocationName = (event: any) => { - if (!event) return null; - const id = event.target.attributes.id.value; - const name = event.target.attributes.name.value; - const percentage = dataMap[id] ? dataMap[id].perNumber : 0; - return { name, id, percentage }; - }; - - const handleLocationMouseOver = (event: any) => { - const pointedLocation = getLocationName(event); - setPointedLocation(pointedLocation); - }; - - const handleLocationMouseOut = () => { - setTooltipStyle({ display: 'none' }); - setPointedLocation(null); - }; - - const handleLocationMouseMove = (event: any) => { - const tooltipStyle = { - display: 'block', - top: event.clientY + 10, - left: event.clientX - 100, - }; - setTooltipStyle(tooltipStyle); - }; - - return ( - -
- + return ( + +
+ +
+ +
+
+ +
+
+ {pointedLocation && ( + <> +
{pointedLocation.name}
+
+ Avg: {dataMap[pointedLocation.id] ? numberWithCommas(parseInt(dataMap[pointedLocation.id].value)) : 0}
- -
-
- -
-
- {pointedLocation && ( - <> -
{pointedLocation.name}
-
- Avg: {dataMap[pointedLocation.id] ? numberWithCommas(parseInt(dataMap[pointedLocation.id].value)) : 0} -
- - )} -
- - ); + + )} +
+
+ ); } export default observer(SpeedIndexByLocation); diff --git a/frontend/app/components/Dashboard/Widgets/common/Styles.js b/frontend/app/components/Dashboard/Widgets/common/Styles.js index 31f9711b6..0f8854911 100644 --- a/frontend/app/components/Dashboard/Widgets/common/Styles.js +++ b/frontend/app/components/Dashboard/Widgets/common/Styles.js @@ -6,7 +6,7 @@ const colors = ['#6774E2', '#929ACD', '#3EAAAF', '#565D97', '#8F9F9F', '#376F72' const colorsx = ['#256669', '#38999e', '#3eaaaf', '#51b3b7', '#78c4c7', '#9fd5d7', '#c5e6e7'].reverse(); const compareColors = ['#394EFF', '#4D5FFF', '#808DFF', '#B3BBFF', '#E5E8FF']; const compareColorsx = ["#222F99", "#2E3ECC", "#394EFF", "#6171FF", "#8895FF", "#B0B8FF", "#D7DCFF"].reverse(); -const customMetricColors = ['#3EAAAF', '#394EFF', '#565D97']; +const customMetricColors = ['#394EFF', '#3EAAAF', '#565D97']; const colorsPie = colors.concat(["#DDDDDD"]); const countView = count => { diff --git a/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx index 4cbf7627d..a59e7aaea 100644 --- a/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx +++ b/frontend/app/components/Dashboard/components/AddCardSelectionModal.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {Card, Col, Modal, Row, Typography} from "antd"; -import {Grid2x2CheckIcon, Plus} from "lucide-react"; +import {GalleryVertical, Plus} from "lucide-react"; import NewDashboardModal from "Components/Dashboard/components/DashboardList/NewDashModal"; import {useStore} from "App/mstore"; @@ -33,24 +33,22 @@ function AddCardSelectionModal(props: Props) { open={props.open} footer={null} onCancel={props.onClose} + className='addCard' > - onClick(true)}> -
- - Add from library - {/*

Select from 12 available

*/} -
-
+
onClick(true)}> + + Add from library + {/*

Select from 12 available

*/} +
+ - onClick(false)}> -
- - Create New Card -
-
+
onClick(false)}> + + Create New Card +
diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx index 0928f3a46..9fff441ec 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertsSearch.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Icon } from 'UI'; +import {Input} from 'antd'; import { debounce } from 'App/utils'; import { useStore } from 'App/mstore' import { observer } from 'mobx-react-lite' @@ -22,11 +23,12 @@ function AlertsSearch() { return (
-
diff --git a/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx b/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx index 09fb1eb1a..545413b0b 100644 --- a/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx +++ b/frontend/app/components/Dashboard/components/Alerts/AlertsView.tsx @@ -1,5 +1,7 @@ import React, { useEffect } from 'react'; -import { Button, PageTitle, Icon, Link } from 'UI'; +import { PageTitle, Icon, Link } from 'UI'; +import { Button } from 'antd'; +import { PlusOutlined } from '@ant-design/icons'; import withPageTitle from 'HOCs/withPageTitle'; import { withSiteId, alertCreate } from 'App/routes'; @@ -32,7 +34,14 @@ function AlertsView({ siteId }: IAlertsView) {
- + + + +
diff --git a/frontend/app/components/Dashboard/components/CardIssues/CardIssues.tsx b/frontend/app/components/Dashboard/components/CardIssues/CardIssues.tsx index 9495d37d0..00f9eae9f 100644 --- a/frontend/app/components/Dashboard/components/CardIssues/CardIssues.tsx +++ b/frontend/app/components/Dashboard/components/CardIssues/CardIssues.tsx @@ -89,7 +89,7 @@ function CardIssues() { }; return useObserver(() => ( -
+

Issues

diff --git a/frontend/app/components/Dashboard/components/CardUserList/CardUserList.tsx b/frontend/app/components/Dashboard/components/CardUserList/CardUserList.tsx index 3cbc720b6..e8d330da3 100644 --- a/frontend/app/components/Dashboard/components/CardUserList/CardUserList.tsx +++ b/frontend/app/components/Dashboard/components/CardUserList/CardUserList.tsx @@ -41,7 +41,7 @@ function CardUserList(props: RouteComponentProps) { }, [userId]); return ( -
+

Returning users between

diff --git a/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx b/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx index 8666ffbdb..23fee3374 100644 --- a/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx +++ b/frontend/app/components/Dashboard/components/DashboardEditModal/DashboardEditModal.tsx @@ -1,6 +1,8 @@ import { useObserver } from 'mobx-react-lite'; import React from 'react'; -import { Button, Modal, Form, Icon, Checkbox, Input } from 'UI'; +import { Modal, Form, Icon, Checkbox, Input } from 'UI'; +import {Button} from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; import { useStore } from 'App/mstore' interface Props { @@ -32,14 +34,13 @@ function DashboardEditModal(props: Props) {
{ 'Edit Dashboard' }
- } /> +
@@ -91,13 +92,13 @@ function DashboardEditModal(props: Props) {
- +
diff --git a/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx b/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx index 837315497..09f2348f6 100644 --- a/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx +++ b/frontend/app/components/Dashboard/components/DashboardHeader/DashboardHeader.tsx @@ -1,23 +1,24 @@ 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 {Breadcrumb} from 'Shared/Breadcrumb'; +import BackButton from '../../../shared/Breadcrumb/BackButton'; +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 CreateDashboardButton from "Components/Dashboard/components/CreateDashboardButton"; -import CreateCard from "Components/Dashboard/components/DashboardList/NewDashModal/CreateCard"; -import CreateCardButton from "Components/Dashboard/components/CreateCardButton"; +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; } @@ -25,104 +26,109 @@ 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: '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'} */} -

-
-
+ 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-dashed 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'} */} +

+
+
+
+ ); } 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 77350083e..56c73b692 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardList.tsx @@ -1,25 +1,29 @@ -import {LockOutlined, TeamOutlined} from '@ant-design/icons'; -import {Empty, Switch, Table, TableColumnsType, Tag, Tooltip, Typography} from 'antd'; -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 {connect} from 'react-redux'; -import {withRouter} from 'react-router-dom'; +import { connect } from 'react-redux'; +import { withRouter } from 'react-router-dom'; -import {checkForRecent} from 'App/date'; -import {useStore} from 'App/mstore'; +import { checkForRecent } from 'App/date'; +import { useStore } from 'App/mstore'; import Dashboard from 'App/mstore/types/dashboard'; -import {dashboardSelected, withSiteId} from 'App/routes'; +import { dashboardSelected, withSiteId } from 'App/routes'; -import AnimatedSVG, {ICONS} from 'Shared/AnimatedSVG/AnimatedSVG'; +import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import CreateDashboardButton from "Components/Dashboard/components/CreateDashboardButton"; -import {useHistory} from "react-router"; +import { useHistory } from "react-router"; +import classNames from 'classnames'; -function DashboardList({siteId}: { siteId: string }) { - const {dashboardStore} = useStore(); +function DashboardList({ siteId }: { siteId: string }) { + const { dashboardStore } = useStore(); const list = dashboardStore.filteredList; const dashboardsSearch = dashboardStore.filter.query; const history = useHistory(); + // Define custom width and height for each scenario + const searchImageDimensions = { width: 200, height: 'auto' }; + const defaultImageDimensions = { width: 600, height: 'auto' }; const tableConfig: TableColumnsType = [ { @@ -28,14 +32,6 @@ function DashboardList({siteId}: { siteId: string }) { width: '25%', render: (t) =>
{t}
, }, - { - title: 'Description', - ellipsis: { - showTitle: false, - }, - width: '25%', - dataIndex: 'description', - }, { title: 'Last Modified', dataIndex: 'updatedAt', @@ -45,54 +41,79 @@ function DashboardList({siteId}: { siteId: string }) { render: (date) => checkForRecent(date, 'LLL dd, yyyy, hh:mm a'), }, { - title: 'Modified By', - dataIndex: 'updatedBy', + title: 'Owner', + dataIndex: 'owner', width: '16.67%', - sorter: (a, b) => a.updatedBy.localeCompare(b.updatedBy), + sorter: (a, b) => a.owner?.localeCompare(b.owner), sortDirections: ['ascend', 'descend'], }, { title: ( -
+
Visibility
- - dashboardStore.updateKey('filter', { - ...dashboardStore.filter, - showMine: !dashboardStore.filter.showMine, - })} checkedChildren={'Public'} unCheckedChildren={'Private'}/> + + + dashboardStore.updateKey('filter', { + ...dashboardStore.filter, + showMine: !dashboardStore.filter.showMine, + })} checkedChildren={'Team'} unCheckedChildren={'Private'} /> +
), width: '16.67%', dataIndex: 'isPublic', render: (isPublic: boolean) => ( - : }> + : }> {isPublic ? 'Team' : 'Private'} ), }, ]; + + const emptyDescription = dashboardsSearch !== '' ? ( +
+
+ + No search results found. + +
+ Try adjusting your search criteria or creating a new dashboard. +
+
+
+ ) : ( +
+
+ + Create your first dashboard. + +
+ Organize your product and technical insights as cards in dashboards to see the bigger picture. +
+
+ +
+
+
+ ); + + const emptyImage = dashboardsSearch !== '' ? ICONS.NO_RESULTS : ICONS.NO_DASHBOARDS; + const imageDimensions = dashboardsSearch !== '' ? searchImageDimensions : defaultImageDimensions; + 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. -
-
- -
-
-
- )} + image={} + imageStyle={{ + width: imageDimensions.width, + height: imageDimensions.height, + margin: 'auto', + padding: '2rem 0' + }} + description={emptyDescription} /> +
) : ( ) + /> + ) ); - + } export default connect((state: any) => ({ diff --git a/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx index e0ad2048c..219145c18 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/DashboardSearch.tsx @@ -29,7 +29,7 @@ function DashboardSearch() { allowClear name="dashboardsSearch" className="w-full" - placeholder="Filter by title or description" + placeholder="Filter by dashboard title" onChange={write} onSearch={(value) => dashboardStore.updateKey('filter', { ...dashboardStore.filter, query: value })} /> diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary.tsx index 887b82299..e000bd2d8 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary.tsx @@ -52,7 +52,7 @@ function CardsLibrary(props: Props) {
onItemClick(metric.metricId)}> - diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/ExampleCards.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/ExampleCards.tsx index 5ab52b1cc..777f02175 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/ExampleCards.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/ExampleCards.tsx @@ -1,761 +1,739 @@ -import ExampleFunnel from "./Examples/Funnel"; -import ExamplePath from "./Examples/Path"; -import ExampleTrend from "./Examples/Trend"; -import Trend from "./Examples/Trend"; -import PerfBreakdown from "./Examples/PerfBreakdown"; -import ByBrowser from "./Examples/SessionsBy/ByBrowser"; -import BySystem from "./Examples/SessionsBy/BySystem"; -import ByCountry from "./Examples/SessionsBy/ByCountry"; -import ByUrl from "./Examples/SessionsBy/ByUrl"; -import {ERRORS, FUNNEL, INSIGHTS, PERFORMANCE, TABLE, TIMESERIES, USER_PATH, WEB_VITALS} from "App/constants/card"; -import {FilterKey} from "Types/filter/filterType"; -import {Activity, BarChart, TableCellsMerge, TrendingUp} from "lucide-react"; -import WebVital from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/WebVital"; -import Bars from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/Bars"; -import ByIssues from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByIssues"; -import InsightsExample from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/InsightsExample"; -import ByUser from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUser"; -import BarChartCard from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/BarChart"; -import AreaChartCard from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/AreaChartCard"; +import ExampleFunnel from './Examples/Funnel'; +import ExamplePath from './Examples/Path'; +import ExampleTrend from './Examples/Trend'; +import Trend from './Examples/Trend'; +import PerfBreakdown from './Examples/PerfBreakdown'; +import ByBrowser from './Examples/SessionsBy/ByBrowser'; +import BySystem from './Examples/SessionsBy/BySystem'; +import ByCountry from './Examples/SessionsBy/ByCountry'; +import ByUrl from './Examples/SessionsBy/ByUrl'; +import { ERRORS, FUNNEL, INSIGHTS, PERFORMANCE, TABLE, TIMESERIES, USER_PATH, WEB_VITALS } from 'App/constants/card'; +import { FilterKey } from 'Types/filter/filterType'; +import { Activity, BarChart, TableCellsMerge, TrendingUp } from 'lucide-react'; +import WebVital from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/WebVital'; +import Bars from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/Bars'; +import ByIssues from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByIssues'; +import InsightsExample from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/InsightsExample'; +import ByUser from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUser'; +import BarChartCard from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/BarChart'; +import AreaChartCard from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/AreaChartCard'; import CallsWithErrorsExample - from "Components/Dashboard/components/DashboardList/NewDashModal/Examples/CallsWithErrorsExample"; + from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/CallsWithErrorsExample'; +import SessionsPerBrowserExample + from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsPerBrowserExample'; +import SlowestDomains + from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/SlowestDomains'; +import SpeedIndexByLocationExample + from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample'; export const CARD_CATEGORY = { - PRODUCT_ANALYTICS: 'product-analytics', - PERFORMANCE_MONITORING: 'performance-monitoring', - WEB_ANALYTICS: 'web-analytics', - ERROR_TRACKING: 'error-tracking', - WEB_VITALS: 'web-vitals', -} + PRODUCT_ANALYTICS: 'product-analytics', + PERFORMANCE_MONITORING: 'performance-monitoring', + WEB_ANALYTICS: 'web-analytics', + ERROR_TRACKING: 'error-tracking', + WEB_VITALS: 'web-vitals' +}; export const CARD_CATEGORIES = [ - {key: CARD_CATEGORY.PRODUCT_ANALYTICS, label: 'Product Analytics', icon: TrendingUp, types: [USER_PATH, ERRORS]}, - {key: CARD_CATEGORY.PERFORMANCE_MONITORING, label: 'Performance Monitoring', icon: Activity, types: [TIMESERIES]}, - {key: CARD_CATEGORY.WEB_ANALYTICS, label: 'Web Analytics', icon: BarChart, types: [TABLE]}, - {key: CARD_CATEGORY.ERROR_TRACKING, label: 'Errors Tracking', icon: TableCellsMerge, types: [WEB_VITALS]}, - {key: CARD_CATEGORY.WEB_VITALS, label: 'Web Vitals', icon: TableCellsMerge, types: [WEB_VITALS]} + { key: CARD_CATEGORY.PRODUCT_ANALYTICS, label: 'Product Analytics', icon: TrendingUp, types: [USER_PATH, ERRORS] }, + { key: CARD_CATEGORY.PERFORMANCE_MONITORING, label: 'Performance Monitoring', icon: Activity, types: [TIMESERIES] }, + { key: CARD_CATEGORY.WEB_ANALYTICS, label: 'Web Analytics', icon: BarChart, types: [TABLE] }, + { key: CARD_CATEGORY.ERROR_TRACKING, label: 'Errors Tracking', icon: TableCellsMerge, types: [WEB_VITALS] }, + { key: CARD_CATEGORY.WEB_VITALS, label: 'Web Vitals', icon: TableCellsMerge, types: [WEB_VITALS] } ]; export interface CardType { - title: string; - key: string; - cardType: string; - category: string; - example: any; - metricOf?: string; - width?: number; - data?: any; - height?: number; + title: string; + key: string; + cardType: string; + category: string; + example: any; + metricOf?: string; + width?: number; + data?: any; + height?: number; + isEnterprise?: boolean; } export const CARD_LIST: CardType[] = [ - { - title: 'Funnel', - key: FUNNEL, - cardType: FUNNEL, - category: CARD_CATEGORIES[0].key, - example: ExampleFunnel, - width: 4, - height: 356, - data: { - stages: [ - { - "value": [ - "/sessions" - ], - "type": "location", - "operator": "contains", - "sessionsCount": 1586, - "dropPct": null, - "usersCount": 470, - "dropDueToIssues": 0 - }, - { - "value": [], - "type": "click", - "operator": "onAny", - "sessionsCount": 1292, - "dropPct": 18, - "usersCount": 450, - "dropDueToIssues": 294 - } - ], - totalDropDueToIssues: 294 + { + title: 'Funnel', + key: FUNNEL, + cardType: FUNNEL, + category: CARD_CATEGORIES[0].key, + example: ExampleFunnel, + width: 4, + height: 356, + data: { + stages: [ + { + 'value': [ + '/sessions' + ], + 'type': 'location', + 'operator': 'contains', + 'sessionsCount': 1586, + 'dropPct': null, + 'usersCount': 470, + 'dropDueToIssues': 0 + }, + { + 'value': [], + 'type': 'click', + 'operator': 'onAny', + 'sessionsCount': 1292, + 'dropPct': 18, + 'usersCount': 450, + 'dropDueToIssues': 294 } + ], + totalDropDueToIssues: 294 + } + }, + { + title: 'Path Finder', + key: USER_PATH, + cardType: USER_PATH, + category: CARD_CATEGORIES[0].key, + example: ExamplePath + }, + { + title: 'Sessions Trend', + key: TIMESERIES, + cardType: TIMESERIES, + metricOf: 'sessionCount', + category: CARD_CATEGORIES[0].key, + data: { + chart: generateTimeSeriesData(), + label: 'Number of Sessions', + namesMap: [ + 'Series 1' + ] }, - { - title: 'Path Finder', - key: USER_PATH, - cardType: USER_PATH, - category: CARD_CATEGORIES[0].key, - example: ExamplePath, + example: ExampleTrend + }, + + { + title: 'Sessions by Issues', + key: FilterKey.ISSUE, + cardType: TABLE, + metricOf: FilterKey.ISSUE, + category: CARD_CATEGORIES[0].key, + example: ByIssues + }, + + { + title: 'Insights', + key: INSIGHTS, + cardType: INSIGHTS, + metricOf: 'issueCategories', + category: CARD_CATEGORIES[0].key, + width: 4, + isEnterprise: true, + example: InsightsExample + }, + + // Performance Monitoring + { + title: 'CPU Load', + key: FilterKey.CPU, + cardType: PERFORMANCE, + metricOf: FilterKey.CPU, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateAreaData(), + label: 'CPU Load (%)', + namesMap: [ + 'Series 1' + ] }, - { - title: 'Sessions Trend', - key: TIMESERIES, - cardType: TIMESERIES, - metricOf: 'sessionCount', - category: CARD_CATEGORIES[0].key, - data: { - chart: generateTimeSeriesData(), - label: "Number of Sessions", - namesMap: [ - "Series 1" - ] + example: AreaChartCard + }, + + { + title: 'Crashes', + key: FilterKey.CRASHES, + cardType: PERFORMANCE, + metricOf: FilterKey.CRASHES, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateAreaData(), + namesMap: [ + 'Series 1' + ] + }, + example: AreaChartCard + }, + + { + title: 'Framerate', + key: FilterKey.FPS, + cardType: PERFORMANCE, + metricOf: FilterKey.FPS, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateAreaData(), + label: 'Frames Per Second', + namesMap: [ + 'Series 1' + ] + }, + example: AreaChartCard + }, + + { + title: 'DOM Building Time', + key: FilterKey.PAGES_DOM_BUILD_TIME, + cardType: PERFORMANCE, + metricOf: FilterKey.PAGES_DOM_BUILD_TIME, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateAreaData(), + label: 'DOM Build Time (ms)', + namesMap: [ + 'Series 1' + ] + }, + example: AreaChartCard + }, + + { + title: 'Memory Consumption', + key: FilterKey.MEMORY_CONSUMPTION, + cardType: PERFORMANCE, + metricOf: FilterKey.MEMORY_CONSUMPTION, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateAreaData(), + label: 'JS Heap Size (MB)', + unit: 'mb', + namesMap: [ + 'Series 1' + ] + }, + example: AreaChartCard + }, + + { + title: 'Page Response Time', + key: FilterKey.PAGES_RESPONSE_TIME, + cardType: PERFORMANCE, + metricOf: FilterKey.PAGES_RESPONSE_TIME, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateAreaData(), + label: 'Page Response Time (ms)', + namesMap: [ + 'Series 1' + ] + }, + example: AreaChartCard + }, + + { + title: 'Page Response Time Distribution', + key: FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION, + cardType: PERFORMANCE, + metricOf: FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateBarChartData(), + label: 'Number of Calls', + unit: 'ms', + namesMap: [ + 'Series 1' + ] + }, + example: BarChartCard + }, + + { + title: 'Resources vs Visually Completed', + key: FilterKey.RESOURCES_VS_VISUALLY_COMPLETE, + cardType: PERFORMANCE, + metricOf: FilterKey.RESOURCES_VS_VISUALLY_COMPLETE, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateBarChartData(), + namesMap: [ + 'Series 1' + ] + }, + example: BarChartCard + }, + + { + title: 'Sessions by Browser & Version', + key: FilterKey.SESSIONS_PER_BROWSER, + cardType: PERFORMANCE, + metricOf: FilterKey.SESSIONS_PER_BROWSER, + category: CARD_CATEGORIES[1].key, + data: generateRandomBarsData(), + example: SessionsPerBrowserExample + }, + + { + title: 'Slowest Domains', + key: FilterKey.SLOWEST_DOMAINS, + cardType: PERFORMANCE, + metricOf: FilterKey.SLOWEST_DOMAINS, + category: CARD_CATEGORIES[1].key, + // data: generateRandomBarsData(), + example: SlowestDomains + }, + + { + title: 'Speed Index by Location', + key: FilterKey.SPEED_LOCATION, + cardType: PERFORMANCE, + metricOf: FilterKey.SPEED_LOCATION, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateAreaData(), + namesMap: [ + 'Series 1' + ] + }, + example: SpeedIndexByLocationExample + }, + + { + title: 'Time to Render', + key: FilterKey.TIME_TO_RENDER, + cardType: PERFORMANCE, + metricOf: FilterKey.TIME_TO_RENDER, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateAreaData(), + label: 'Time to Render (ms)', + unit: 'ms', + namesMap: [ + 'Series 1' + ] + }, + example: AreaChartCard + }, + + { + title: 'Sessions Impacted by Slow Pages', + key: FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES, + cardType: PERFORMANCE, + metricOf: FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES, + category: CARD_CATEGORIES[1].key, + data: { + chart: generateAreaData(), + label: 'Number of Sessions', + namesMap: [ + 'Series 1' + ] + }, + example: AreaChartCard + }, + + + // Web analytics + { + title: 'Sessions by Users', + key: FilterKey.USERID, + cardType: TABLE, + metricOf: FilterKey.USERID, + category: CARD_CATEGORIES[2].key, + example: ByUser + }, + + { + title: 'Sessions by Browser', + key: FilterKey.USER_BROWSER, + cardType: TABLE, + metricOf: FilterKey.USER_BROWSER, + category: CARD_CATEGORIES[2].key, + example: ByBrowser + }, + // { + // title: 'Sessions by System', + // key: TYPE.SESSIONS_BY_SYSTEM, + // cardType: TABLE, + // metricOf: FilterKey.USER_OS, + // category: CARD_CATEGORIES[2].key, + // example: BySystem, + // }, + { + title: 'Sessions by Country', + key: FilterKey.USER_COUNTRY, + cardType: TABLE, + metricOf: FilterKey.USER_COUNTRY, + category: CARD_CATEGORIES[2].key, + example: ByCountry + }, + + { + title: 'Sessions by Device', + key: FilterKey.USER_DEVICE, + cardType: TABLE, + metricOf: FilterKey.USER_DEVICE, + category: CARD_CATEGORIES[2].key, + example: BySystem + }, + { + title: 'Sessions by URL', + key: FilterKey.LOCATION, + cardType: TABLE, + metricOf: FilterKey.LOCATION, + category: CARD_CATEGORIES[2].key, + example: ByUrl + }, + + // Errors Tracking + { + title: 'JS Errors', + key: FilterKey.IMPACTED_SESSIONS_BY_JS_ERRORS, + cardType: ERRORS, + metricOf: FilterKey.IMPACTED_SESSIONS_BY_JS_ERRORS, + category: CARD_CATEGORIES[3].key, + data: { + chart: generateBarChartData() + }, + example: BarChartCard + }, + { + title: 'Errors by Origin', + key: FilterKey.RESOURCES_BY_PARTY, + cardType: ERRORS, + metricOf: FilterKey.RESOURCES_BY_PARTY, + category: CARD_CATEGORIES[3].key, + data: { + chart: generateBarChartData() + }, + example: BarChartCard + }, + { + title: 'Errors by Domain', + key: FilterKey.ERRORS_PER_DOMAINS, + cardType: ERRORS, + metricOf: FilterKey.ERRORS_PER_DOMAINS, + category: CARD_CATEGORIES[3].key, + example: SlowestDomains + // data: generateRandomBarsData(), + }, + { + title: 'Errors by Type', + key: FilterKey.ERRORS_PER_TYPE, + cardType: ERRORS, + metricOf: FilterKey.ERRORS_PER_TYPE, + category: CARD_CATEGORIES[3].key, + data: { + chart: generateBarChartData() + }, + example: BarChartCard + }, + { + title: 'Calls with Errors', + key: FilterKey.CALLS_ERRORS, + cardType: ERRORS, + metricOf: FilterKey.CALLS_ERRORS, + category: CARD_CATEGORIES[3].key, + width: 4, + data: { + chart: [ + { + 'method': 'GET', + 'urlHostpath': 'https://openreplay.com', + 'allRequests': 1333, + '4xx': 1333, + '5xx': 0 }, - example: ExampleTrend, - }, - - { - title: 'Sessions by Issues', - key: FilterKey.ISSUE, - cardType: TABLE, - metricOf: FilterKey.ISSUE, - category: CARD_CATEGORIES[0].key, - example: ByIssues, - }, - - { - title: 'Insights', - key: INSIGHTS, - cardType: INSIGHTS, - metricOf: 'issueCategories', - category: CARD_CATEGORIES[0].key, - width: 4, - example: InsightsExample, - }, - - // Performance Monitoring - { - title: 'CPU Load', - key: FilterKey.CPU, - cardType: PERFORMANCE, - metricOf: FilterKey.CPU, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - label: "CPU Load (%)", - namesMap: [ - "Series 1" - ] + { + 'method': 'POST', + 'urlHostpath': 'https://company.domain.com', + 'allRequests': 10, + '4xx': 10, + '5xx': 0 }, - example: AreaChartCard, + { + 'method': 'PUT', + 'urlHostpath': 'https://example.com', + 'allRequests': 3, + '4xx': 3, + '5xx': 0 + } + ] }, + example: CallsWithErrorsExample + }, - { - title: 'Crashes', - key: FilterKey.CRASHES, - cardType: PERFORMANCE, - metricOf: FilterKey.CRASHES, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - namesMap: [ - "Series 1" - ] - }, - example: AreaChartCard, + { + title: '4xx Domains', + key: FilterKey.DOMAINS_ERRORS_4XX, + cardType: ERRORS, + metricOf: FilterKey.DOMAINS_ERRORS_4XX, + category: CARD_CATEGORIES[3].key, + data: { + chart: generateTimeSeriesData(), + label: 'Number of Errors', + namesMap: [ + 'Series 1' + ] }, + example: ExampleTrend + }, - { - title: 'Framerate', - key: FilterKey.FPS, - cardType: PERFORMANCE, - metricOf: FilterKey.FPS, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - label: "Frames Per Second", - namesMap: [ - "Series 1" - ] - }, - example: AreaChartCard, - }, - - { - title: 'DOM Building Time', - key: FilterKey.PAGES_DOM_BUILD_TIME, - cardType: PERFORMANCE, - metricOf: FilterKey.PAGES_DOM_BUILD_TIME, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - label: "DOM Build Time (ms)", - namesMap: [ - "Series 1" - ] - }, - example: AreaChartCard, - }, - - { - title: 'Memory Consumption', - key: FilterKey.MEMORY_CONSUMPTION, - cardType: PERFORMANCE, - metricOf: FilterKey.MEMORY_CONSUMPTION, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - label: "JS Heap Size (MB)", - unit: 'mb', - namesMap: [ - "Series 1" - ] - }, - example: AreaChartCard, - }, - - { - title: 'Page Response Time', - key: FilterKey.PAGES_RESPONSE_TIME, - cardType: PERFORMANCE, - metricOf: FilterKey.PAGES_RESPONSE_TIME, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - label: "Page Response Time (ms)", - namesMap: [ - "Series 1" - ] - }, - example: AreaChartCard, - }, - - { - title: 'Page Response Time Distribution', - key: FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION, - cardType: PERFORMANCE, - metricOf: FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - label: "Number of Calls", - namesMap: [ - "Series 1" - ] - }, - example: AreaChartCard, - }, - - { - title: 'Resources vs Visually Completed', - key: FilterKey.RESOURCES_VS_VISUALLY_COMPLETE, - cardType: PERFORMANCE, - metricOf: FilterKey.RESOURCES_VS_VISUALLY_COMPLETE, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateBarChartDate(), - namesMap: [ - "Series 1" - ] - }, - example: BarChartCard, - }, - - { - title: 'Sessions per Browser', - key: FilterKey.SESSIONS_PER_BROWSER, - cardType: PERFORMANCE, - metricOf: FilterKey.SESSIONS_PER_BROWSER, - category: CARD_CATEGORIES[1].key, - data: generateRandomBarsData(), - example: Bars, - }, - - { - title: 'Slowest Domains', - key: FilterKey.SLOWEST_DOMAINS, - cardType: PERFORMANCE, - metricOf: FilterKey.SLOWEST_DOMAINS, - category: CARD_CATEGORIES[1].key, - data: generateRandomBarsData(), - example: Bars, - }, - - { - title: 'Speed Index by Location', - key: FilterKey.SPEED_LOCATION, - cardType: PERFORMANCE, - metricOf: FilterKey.SPEED_LOCATION, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - namesMap: [ - "Series 1" - ] - }, - example: AreaChartCard, - }, - - { - title: 'Time to Render', - key: FilterKey.TIME_TO_RENDER, - cardType: PERFORMANCE, - metricOf: FilterKey.TIME_TO_RENDER, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - namesMap: [ - "Series 1" - ] - }, - example: AreaChartCard, - }, - - { - title: 'Sessions Impacted by Slow Pages', - key: FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES, - cardType: PERFORMANCE, - metricOf: FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES, - category: CARD_CATEGORIES[1].key, - data: { - chart: generateAreaData(), - namesMap: [ - "Series 1" - ] - }, - example: AreaChartCard, + { + title: '5xx Domains', + key: FilterKey.DOMAINS_ERRORS_5XX, + cardType: ERRORS, + metricOf: FilterKey.DOMAINS_ERRORS_5XX, + category: CARD_CATEGORIES[3].key, + data: { + chart: generateTimeSeriesData(), + label: 'Number of Errors', + namesMap: [ + 'Series 1' + ] }, + example: ExampleTrend + }, - // Web analytics - { - title: 'Sessions by Users', - key: FilterKey.USERID, - cardType: TABLE, - metricOf: FilterKey.USERID, - category: CARD_CATEGORIES[2].key, - example: ByUser, - }, + // Web vitals + { + title: 'Avg. CPU Load', + key: FilterKey.AVG_CPU, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_CPU, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, + { + title: 'Avg. DOM Content Load Time', + key: FilterKey.AVG_DOM_CONTENT_LOADED, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_DOM_CONTENT_LOADED, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: 'Sessions by Browser', - key: FilterKey.USER_BROWSER, - cardType: TABLE, - metricOf: FilterKey.USER_BROWSER, - category: CARD_CATEGORIES[2].key, - example: ByBrowser, - }, - // { - // title: 'Sessions by System', - // key: TYPE.SESSIONS_BY_SYSTEM, - // cardType: TABLE, - // metricOf: FilterKey.USER_OS, - // category: CARD_CATEGORIES[2].key, - // example: BySystem, - // }, - { - title: 'Sessions by Country', - key: FilterKey.USER_COUNTRY, - cardType: TABLE, - metricOf: FilterKey.USER_COUNTRY, - category: CARD_CATEGORIES[2].key, - example: ByCountry, - }, + { + title: 'DOM Content Loaded Start', + key: FilterKey.AVG_DOM_CONTENT_LOAD_START, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_DOM_CONTENT_LOAD_START, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: 'Sessions by Device', - key: FilterKey.USER_DEVICE, - cardType: TABLE, - metricOf: FilterKey.USER_DEVICE, - category: CARD_CATEGORIES[2].key, - example: BySystem, - }, - { - title: 'Sessions by URL', - key: FilterKey.LOCATION, - cardType: TABLE, - metricOf: FilterKey.LOCATION, - category: CARD_CATEGORIES[2].key, - example: ByUrl, - }, + { + title: 'Avg. First Meaningful Paint Time', + key: FilterKey.AVG_FIRST_CONTENTFUL_PIXEL, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_FIRST_CONTENTFUL_PIXEL, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - // Errors Tracking - { - title: 'JS Errors', - key: FilterKey.IMPACTED_SESSIONS_BY_JS_ERRORS, - cardType: ERRORS, - metricOf: FilterKey.IMPACTED_SESSIONS_BY_JS_ERRORS, - category: CARD_CATEGORIES[3].key, - data: { - chart: generateBarChartDate(), - }, - example: BarChartCard, - }, - { - title: 'Errors by Origin', - key: FilterKey.RESOURCES_BY_PARTY, - cardType: ERRORS, - metricOf: FilterKey.RESOURCES_BY_PARTY, - category: CARD_CATEGORIES[3].key, - data: { - chart: generateBarChartDate(), - }, - example: BarChartCard, - }, - { - title: 'Errors by Domain', - key: FilterKey.ERRORS_PER_DOMAINS, - cardType: ERRORS, - metricOf: FilterKey.ERRORS_PER_DOMAINS, - category: CARD_CATEGORIES[3].key, - example: Bars, - data: generateRandomBarsData(), - }, - { - title: 'Errors by Type', - key: FilterKey.ERRORS_PER_TYPE, - cardType: ERRORS, - metricOf: FilterKey.ERRORS_PER_TYPE, - category: CARD_CATEGORIES[3].key, - data: { - chart: generateBarChartDate(), - }, - example: BarChartCard, - }, - { - title: 'Calls with Errors', - key: FilterKey.CALLS_ERRORS, - cardType: ERRORS, - metricOf: FilterKey.CALLS_ERRORS, - category: CARD_CATEGORIES[3].key, - width: 4, - data: { - chart: [ - { - "method": "GET", - "urlHostpath": 'https://openreplay.com', - "allRequests": 1333, - "4xx": 1333, - "5xx": 0 - }, - { - "method": "POST", - "urlHostpath": 'https://company.domain.com', - "allRequests": 10, - "4xx": 10, - "5xx": 0 - }, - { - "method": "PUT", - "urlHostpath": 'https://example.com', - "allRequests": 3, - "4xx": 3, - "5xx": 0 - } - ], - }, - example: CallsWithErrorsExample, - }, + { + title: 'Avg. First Paint Time', + key: FilterKey.AVG_FIRST_PAINT, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_FIRST_PAINT, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: '4xx Domains', - key: FilterKey.DOMAINS_ERRORS_4XX, - cardType: ERRORS, - metricOf: FilterKey.DOMAINS_ERRORS_4XX, - category: CARD_CATEGORIES[3].key, - data: { - chart: generateTimeSeriesData(), - label: "Number of Errors", - namesMap: [ - "Series 1" - ] - }, - example: ExampleTrend, - }, + { + title: 'Avg. Frame Rate', + key: FilterKey.AVG_FPS, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_FPS, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: '5xx Domains', - key: FilterKey.DOMAINS_ERRORS_5XX, - cardType: ERRORS, - metricOf: FilterKey.DOMAINS_ERRORS_5XX, - category: CARD_CATEGORIES[3].key, - data: { - chart: generateTimeSeriesData(), - label: "Number of Errors", - namesMap: [ - "Series 1" - ] - }, - example: ExampleTrend, - }, + { + title: 'Avg. Load Time of Images', + key: FilterKey.AVG_IMAGE_LOAD_TIME, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_IMAGE_LOAD_TIME, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, + { + title: 'Avg. Load Time of Pages', + key: FilterKey.AVG_PAGE_LOAD_TIME, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_PAGE_LOAD_TIME, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - // Web vitals - { - title: 'CPU Load', - key: FilterKey.AVG_CPU, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_CPU, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, - { - title: 'DOM Content Loaded', - key: FilterKey.AVG_DOM_CONTENT_LOADED, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_DOM_CONTENT_LOADED, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, + { + title: 'Avg. DOM Build Time', + key: FilterKey.AVG_PAGES_DOM_BUILD_TIME, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_PAGES_DOM_BUILD_TIME, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: 'DOM Content Loaded Start', - key: FilterKey.AVG_DOM_CONTENT_LOAD_START, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_DOM_CONTENT_LOAD_START, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, + { + title: 'Page Response Time', + key: FilterKey.AVG_PAGES_RESPONSE_TIME, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_PAGES_RESPONSE_TIME, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: 'First Meaningful Paint', - key: FilterKey.AVG_FIRST_CONTENTFUL_PIXEL, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_FIRST_CONTENTFUL_PIXEL, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, + { + title: 'Request Load Time', + key: FilterKey.AVG_REQUEST_LOADT_IME, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_REQUEST_LOADT_IME, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, + { + title: 'Response Time', + key: FilterKey.AVG_RESPONSE_TIME, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_RESPONSE_TIME, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: 'First Paint', - key: FilterKey.AVG_FIRST_PAINT, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_FIRST_PAINT, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, + { + title: 'Session Duration', + key: FilterKey.AVG_SESSION_DURATION, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_SESSION_DURATION, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: 'Frame Rate', - key: FilterKey.AVG_FPS, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_FPS, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, + { + title: 'Time Till First Byte', + key: FilterKey.AVG_TILL_FIRST_BYTE, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_TILL_FIRST_BYTE, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: 'Image Load Time', - key: FilterKey.AVG_IMAGE_LOAD_TIME, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_IMAGE_LOAD_TIME, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, + { + title: 'Time to be Interactive', + key: FilterKey.AVG_TIME_TO_INTERACTIVE, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_TIME_TO_INTERACTIVE, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: 'Page Load Time', - key: FilterKey.AVG_PAGE_LOAD_TIME, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_PAGE_LOAD_TIME, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, + { + title: 'Time to Render', + key: FilterKey.AVG_TIME_TO_RENDER, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_TIME_TO_RENDER, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + }, - { - title: 'DOM Build Time', - key: FilterKey.AVG_PAGES_DOM_BUILD_TIME, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_PAGES_DOM_BUILD_TIME, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, - - { - title: 'Page Response Time', - key: FilterKey.AVG_PAGES_RESPONSE_TIME, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_PAGES_RESPONSE_TIME, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, - - { - title: 'Request Load Time', - key: FilterKey.AVG_REQUEST_LOADT_IME, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_REQUEST_LOADT_IME, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, - { - title: 'Response Time', - key: FilterKey.AVG_RESPONSE_TIME, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_RESPONSE_TIME, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, - - { - title: 'Session Duration', - key: FilterKey.AVG_SESSION_DURATION, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_SESSION_DURATION, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, - - { - title: 'Time Till First Byte', - key: FilterKey.AVG_TILL_FIRST_BYTE, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_TILL_FIRST_BYTE, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, - - { - title: 'Time to be Interactive', - key: FilterKey.AVG_TIME_TO_INTERACTIVE, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_TIME_TO_INTERACTIVE, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, - - { - title: 'Time to Render', - key: FilterKey.AVG_TIME_TO_RENDER, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_TIME_TO_RENDER, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, - - { - title: 'JS Heap Size', - key: FilterKey.AVG_USED_JS_HEAP_SIZE, - cardType: WEB_VITALS, - metricOf: FilterKey.AVG_USED_JS_HEAP_SIZE, - category: CARD_CATEGORIES[4].key, - width: 1, - height: 148, - data: generateWebVitalData(), - example: WebVital, - }, -] + { + title: 'JS Heap Size', + key: FilterKey.AVG_USED_JS_HEAP_SIZE, + cardType: WEB_VITALS, + metricOf: FilterKey.AVG_USED_JS_HEAP_SIZE, + category: CARD_CATEGORIES[4].key, + data: generateWebVitalData(), + example: WebVital + } +]; function generateRandomBarsData(): { total: number, values: { label: string, value: number }[] } { - const labels = ["company.domain.com", "openreplay.com"]; - const values = labels.map(label => ({ - label, - value: Math.floor(Math.random() * 100) - })); - const total = values.reduce((acc, curr) => acc + curr.value, 0); + const labels = ['company.domain.com', 'openreplay.com']; + const values = labels.map(label => ({ + label, + value: Math.floor(Math.random() * 100) + })); + const total = values.reduce((acc, curr) => acc + curr.value, 0); - return { - total, - values: values.sort((a, b) => b.value - a.value) - }; + return { + total, + values: values.sort((a, b) => b.value - a.value) + }; } function generateWebVitalData(): { value: number, chart: { timestamp: number, value: number }[], unit: string } { - const chart = Array.from({length: 7}, (_, i) => ({ - timestamp: Date.now() + i * 86400000, - value: parseFloat((Math.random() * 10).toFixed(15)) - })); + const chart = Array.from({ length: 7 }, (_, i) => ({ + timestamp: Date.now() + i * 86400000, + value: parseFloat((Math.random() * 10).toFixed(15)) + })); - const value = chart.reduce((acc, curr) => acc + curr.value, 0) / chart.length; + const value = chart.reduce((acc, curr) => acc + curr.value, 0) / chart.length; - return { - value, - chart, - unit: "%" - }; + return { + value, + chart, + unit: '%' + }; } function generateTimeSeriesData(): any[] { - const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]; - const pointsPerMonth = 3; // Number of points for each month + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']; + const pointsPerMonth = 3; // Number of points for each month - const data = months.flatMap((month, monthIndex) => - Array.from({length: pointsPerMonth}, (_, pointIndex) => ({ - time: month, - "Series 1": Math.floor(Math.random() * 90), - timestamp: Date.now() + (monthIndex * pointsPerMonth + pointIndex) * 86400000 - })) - ); + const data = months.flatMap((month, monthIndex) => + Array.from({ length: pointsPerMonth }, (_, pointIndex) => ({ + time: month, + 'Series 1': Math.floor(Math.random() * 90), + timestamp: Date.now() + (monthIndex * pointsPerMonth + pointIndex) * 86400000 + })) + ); - return data; + return data; } function generateAreaData(): any[] { - const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]; - const pointsPerMonth = 3; // Number of points for each month + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']; + const pointsPerMonth = 3; // Number of points for each month - const data = months.flatMap((month, monthIndex) => - Array.from({length: pointsPerMonth}, (_, pointIndex) => ({ - time: month, - "value": Math.floor(Math.random() * 90), - timestamp: Date.now() + (monthIndex * pointsPerMonth + pointIndex) * 86400000 - })) - ); + const data = months.flatMap((month, monthIndex) => + Array.from({ length: pointsPerMonth }, (_, pointIndex) => ({ + time: month, + 'value': Math.floor(Math.random() * 90), + timestamp: Date.now() + (monthIndex * pointsPerMonth + pointIndex) * 86400000 + })) + ); - return data; + return data; } function generateRandomValue(min: number, max: number): number { - return Math.floor(Math.random() * (max - min + 1)) + min; + return Math.floor(Math.random() * (max - min + 1)) + min; } -function generateBarChartDate(): any[] { - const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']; - return months.map(month => ({ - time: month, - value: generateRandomValue(1000, 5000), - })); +function generateBarChartData(): any[] { + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']; + return months.map(month => ({ + time: month, + value: generateRandomValue(1000, 5000) + })); } diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/AreaChartCard.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/AreaChartCard.tsx index d50fcab52..871c4519d 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/AreaChartCard.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/AreaChartCard.tsx @@ -46,7 +46,7 @@ function AreaChartCard(props: Props) { {/*
*/} {/* */} {/*
*/} - + void; - onClick?: any; - data?: any, + title: string; + type: string; + onCard: (card: string) => void; + onClick?: any; + data?: any, } function BarChartCard(props: Props) { - return ( - + {/**/} + {/* */} + {/* /!**!/*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* }/>*/} + {/* /!*}/>*!/*/} + {/* */} + {/**/} + + + - {/**/} - {/* */} - {/* /!**!/*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* }/>*/} - {/* /!*}/>*!/*/} - {/* */} - {/**/} + + + Styles.tickFormatter(val)} + label={{ ...Styles.axisLabelLeft, value: props.data?.label || 'Number of Errors' }} + allowDecimals={false} + /> + + + One} + dataKey="value" stackId="a" fill={Styles.colors[0]} /> + {/*3rd Party} dataKey="thirdParty" stackId="a"*/} + {/* fill={Styles.colors[2]}/>*/} + + + - - - - - Styles.tickFormatter(val)} - label={{...Styles.axisLabelLeft, value: "Number of Errors"}} - allowDecimals={false} - /> - - - One} - dataKey="value" stackId="a" fill={Styles.colors[0]}/> - {/*3rd Party} dataKey="thirdParty" stackId="a"*/} - {/* fill={Styles.colors[2]}/>*/} - - - - - ); + ); } export default BarChartCard; diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard.tsx index b64fac0a8..b464c1398 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard.tsx @@ -15,7 +15,7 @@ function ExCard({ }) { return (
onCard(type)}>
diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUrl.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUrl.tsx index b6d35ef76..28c0b9560 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUrl.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/ByUrl.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { Circle } from '../Count'; import ExCard from '../ExCard'; +import ByComponent from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component'; function ByUrl(props: any) { const [mode, setMode] = React.useState(0); @@ -47,57 +48,62 @@ function ByUrl(props: any) { const lineWidth = 240; return ( - -
{props.title}
-
setMode(Number(v))} - size='small' - /> -
-
- } - > -
- {rows.map((r) => ( -
- {r.icon} -
-
{mode === 0 ? r.label : r.ptitle}
-
-
-
-
-
-
{r.value}
-
- ))} -
- + + // + //
{props.title}
+ //
setMode(Number(v))} + // size='small' + // /> + //
+ //
+ // } + // > + //
+ // {rows.map((r) => ( + //
+ // {r.icon} + //
+ //
{mode === 0 ? r.label : r.ptitle}
+ //
+ //
+ //
+ //
+ //
+ //
{r.value}
+ //
+ // ))} + //
+ // ); } diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component.tsx index 2300ddeb2..67684ad10 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/Component.tsx @@ -1,67 +1,36 @@ -import ExCard from '../ExCard' -import React from 'react' -import CardSessionsByList from "Components/Dashboard/Widgets/CardSessionsByList"; +import ExCard from '../ExCard'; +import React from 'react'; +import CardSessionsByList from 'Components/Dashboard/Widgets/CardSessionsByList'; -function ByComponent({title, rows, lineWidth, onCard, type}: { - title: string - rows: { - label: string - progress: number - value: string - icon: React.ReactNode - }[] - onCard: (card: string) => void - type: string - lineWidth: number +function ByComponent({ title, rows, lineWidth, onCard, type }: { + title: string + rows: { + label: string + progress: number + value: string + icon: React.ReactNode + }[] + onCard: (card: string) => void + type: string + lineWidth: number }) { - const _rows = rows.map((r) => ({ - ...r, - name: r.label, - sessionCount: r.value, - })).slice(0, 4) - return ( - -
- null}/> - - {/*{rows.map((r) => (*/} - {/* */} - {/*
{r.icon}
*/} - {/*
{r.label}
*/} - {/* */} - {/* */} - {/* */} - {/*
*/} - {/*
{r.value}
*/} - {/*
*/} - {/*))}*/} -
- - ) + const _rows = rows.map((r) => ({ + ...r, + name: r.label, + displayName: r.label, + sessionCount: r.value + })).slice(0, 4); + return ( + +
+ null} /> +
+
+ ); } -export default ByComponent +export default ByComponent; diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/SlowestDomains.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/SlowestDomains.tsx new file mode 100644 index 000000000..428c04bb3 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsBy/SlowestDomains.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import ByComponent from './Component'; +import { LinkOutlined } from '@ant-design/icons'; + +function SlowestDomains(props: any) { + const rows = [ + { + label: 'res.cloudinary.com', + value: '500', + progress: 75, + icon: + }, + { + label: 'mintbase.vercel.app', + value: '306', + progress: 60, + icon: + }, + { + label: 'downloads.intercomcdn.com', + value: '198', + progress: 30, + icon: + }, + { + label: 'static.intercomassets.com', + value: '47', + progress: 15, + icon: + }, + { + label: 'mozbar.moz.com', + value: '5', + progress: 5, + icon: + } + ]; + + const lineWidth = 200; + return ( + + ); +} + +export default SlowestDomains; diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsPerBrowserExample.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsPerBrowserExample.tsx new file mode 100644 index 000000000..f5710b759 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SessionsPerBrowserExample.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import ExCard from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard'; +import InsightsCard from 'Components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard'; +import { InsightIssue } from 'App/mstore/types/widget'; +import SessionsPerBrowser from 'Components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser'; + +interface Props { + title: string; + type: string; + onCard: (card: string) => void; +} + +function SessionsPerBrowserExample(props: Props) { + const data = { + chart: [ + { + 'browser': 'Chrome', + 'count': 1524, + '126.0.0': 1157, + '125.0.0': 224 + }, + { + 'browser': 'Edge', + 'count': 159, + '126.0.0': 145 + } + ] + }; + return ( + + + + ); +} + +export default SessionsPerBrowserExample; diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SlowestDomain.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SlowestDomain.tsx index d39062eb2..baded8f84 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SlowestDomain.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SlowestDomain.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { Circle } from './Count'; import ExCard from './ExCard'; +// TODO - delete this function SlowestDomain(props: any) { const rows = [ { diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample.tsx new file mode 100644 index 000000000..98a88eef7 --- /dev/null +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/Examples/SpeedIndexByLocationExample.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import ExCard from 'Components/Dashboard/components/DashboardList/NewDashModal/Examples/ExCard'; +import InsightsCard from 'Components/Dashboard/Widgets/CustomMetricsWidgets/InsightsCard'; +import { InsightIssue } from 'App/mstore/types/widget'; +import SessionsPerBrowser from 'Components/Dashboard/Widgets/PredefinedWidgets/SessionsPerBrowser'; +import SpeedIndexByLocation from 'Components/Dashboard/Widgets/PredefinedWidgets/SpeedIndexByLocation'; + +interface Props { + title: string; + type: string; + onCard: (card: string) => void; +} + +function SpeedIndexByLocationExample(props: Props) { + const data = { + 'value': 1480, + 'chart': [ + { + 'userCountry': 'AT', + 'value': 415 + }, + { + 'userCountry': 'PL', + 'value': 433.1666666666667 + }, + { + 'userCountry': 'FR', + 'value': 502 + }, + { + 'userCountry': 'IT', + 'value': 540.4117647058823 + }, + { + 'userCountry': 'TH', + 'value': 662.0 + }, + { + 'userCountry': 'ES', + 'value': 740.5454545454545 + }, + { + 'userCountry': 'SG', + 'value': 889.6666666666666 + }, + { + 'userCountry': 'TW', + 'value': 1008.0 + }, + { + 'userCountry': 'HU', + 'value': 1027.0 + }, + { + 'userCountry': 'DE', + 'value': 1054.4583333333333 + }, + { + 'userCountry': 'BE', + 'value': 1126.0 + }, + { + 'userCountry': 'TR', + 'value': 1174.0 + }, + { + 'userCountry': 'US', + 'value': 1273.3015873015872 + }, + { + 'userCountry': 'GB', + 'value': 1353.8095238095239 + }, + { + 'userCountry': 'VN', + 'value': 1473.8181818181818 + }, + { + 'userCountry': 'HK', + 'value': 1654.6666666666667 + }, + ], + 'unit': 'ms' + }; + return ( + + + + ); +} + +export default SpeedIndexByLocationExample; diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/NewDashboardModal.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/NewDashboardModal.tsx index 5c975a9d1..c974e8bf3 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/NewDashboardModal.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/NewDashboardModal.tsx @@ -1,60 +1,70 @@ -import React, {useEffect} from 'react'; -import {Modal} from 'antd'; +import React, { useEffect } from 'react'; +import { Modal } from 'antd'; import SelectCard from './SelectCard'; -import CreateCard from "Components/Dashboard/components/DashboardList/NewDashModal/CreateCard"; -import colors from "tailwindcss/colors"; +import CreateCard from 'Components/Dashboard/components/DashboardList/NewDashModal/CreateCard'; +import colors from 'tailwindcss/colors'; +import { connect } from 'react-redux'; interface NewDashboardModalProps { - onClose: () => void; - open: boolean; - isAddingFromLibrary?: boolean; + onClose: () => void; + open: boolean; + isAddingFromLibrary?: boolean; + isEnterprise?: boolean; } const NewDashboardModal: React.FC = ({ - onClose, - open, - isAddingFromLibrary = false, + onClose, + open, + isAddingFromLibrary = false, + isEnterprise = false }) => { - const [step, setStep] = React.useState(0); - const [selectedCategory, setSelectedCategory] = React.useState('product-analytics'); + const [step, setStep] = React.useState(0); + const [selectedCategory, setSelectedCategory] = React.useState('product-analytics'); - useEffect(() => { - return () => { - setStep(0); - } - }, [open]); + useEffect(() => { + return () => { + setStep(0); + }; + }, [open]); - return ( - <> - -
- {step === 0 && setStep(step + 1)} - isLibrary={isAddingFromLibrary}/>} - {step === 1 && setStep(0)}/>} -
-
- - ) - ; + return ( + <> + +
+ {step === 0 && setStep(step + 1)} + isLibrary={isAddingFromLibrary} + isEnterprise={isEnterprise} />} + {step === 1 && setStep(0)} />} +
+
+ + ) + ; }; -export default NewDashboardModal; +const mapStateToProps = (state: any) => ({ + isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee' || + state.getIn(['user', 'account', 'edition']) === 'msaas' +}); + +export default connect(mapStateToProps)(NewDashboardModal); diff --git a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/SelectCard.tsx b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/SelectCard.tsx index 8a1ea4a35..c200c4a82 100644 --- a/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/SelectCard.tsx +++ b/frontend/app/components/Dashboard/components/DashboardList/NewDashModal/SelectCard.tsx @@ -1,137 +1,139 @@ -import React, {useMemo} from 'react'; -import {Button, Input, Segmented, Space} from 'antd'; -import {CARD_LIST, CARD_CATEGORIES, CardType} from './ExampleCards'; -import {useStore} from 'App/mstore'; +import React, { useMemo } from 'react'; +import { Button, Input, Segmented, Space } from 'antd'; +import { CARD_LIST, CARD_CATEGORIES, CardType } from './ExampleCards'; +import { useStore } from 'App/mstore'; import Option from './Option'; -import CardsLibrary from "Components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary"; -import {FUNNEL} from "App/constants/card"; +import CardsLibrary from 'Components/Dashboard/components/DashboardList/NewDashModal/CardsLibrary'; +import { FUNNEL } from 'App/constants/card'; interface SelectCardProps { - onClose: (refresh?: boolean) => void; - onCard: () => void; - isLibrary?: boolean; - selected?: string; - setSelectedCategory?: React.Dispatch>; + onClose: (refresh?: boolean) => void; + onCard: () => void; + isLibrary?: boolean; + selected?: string; + setSelectedCategory?: React.Dispatch>; + isEnterprise?: boolean; } const SelectCard: React.FC = (props: SelectCardProps) => { - const {onCard, isLibrary = false, selected, setSelectedCategory} = props; - const [selectedCards, setSelectedCards] = React.useState([]); - const {metricStore, dashboardStore} = useStore(); - const dashboardId = window.location.pathname.split('/')[3]; - const [libraryQuery, setLibraryQuery] = React.useState(''); + const { onCard, isLibrary = false, selected, setSelectedCategory, isEnterprise } = props; + const [selectedCards, setSelectedCards] = React.useState([]); + const { metricStore, dashboardStore } = useStore(); + const dashboardId = window.location.pathname.split('/')[3]; + const [libraryQuery, setLibraryQuery] = React.useState(''); - const handleCardSelection = (card: string) => { - metricStore.init(); - const selectedCard = CARD_LIST.find((c) => c.key === card) as CardType; + const handleCardSelection = (card: string) => { + metricStore.init(); + const selectedCard = CARD_LIST.find((c) => c.key === card) as CardType; - const cardData: any = { - metricType: selectedCard.cardType, - name: selectedCard.title, - metricOf: selectedCard.metricOf, - }; - - if (selectedCard.cardType === FUNNEL) { - cardData.series = [] - cardData.series.filter = [] - } - - metricStore.merge(cardData); - metricStore.instance.resetDefaults(); - onCard(); + const cardData: any = { + metricType: selectedCard.cardType, + name: selectedCard.title, + metricOf: selectedCard.metricOf }; - const cardItems = useMemo(() => { - return CARD_LIST.filter((card) => card.category === selected).map((card) => ( -
- -
- )); - }, [selected]); - - const onCardClick = (cardId: number) => { - if (selectedCards.includes(cardId)) { - setSelectedCards(selectedCards.filter((id) => id !== cardId)); - } else { - setSelectedCards([...selectedCards, cardId]); - } + if (selectedCard.cardType === FUNNEL) { + cardData.series = []; + cardData.series.filter = []; } - const onAddSelected = () => { - const dashboard = dashboardStore.getDashboard(dashboardId); - dashboardStore.addWidgetToDashboard(dashboard!, selectedCards).finally(() => { - dashboardStore.fetch(dashboardId); - props.onClose(true); - }); + metricStore.merge(cardData); + metricStore.instance.resetDefaults(); + onCard(); + }; + + const cardItems = useMemo(() => { + return CARD_LIST.filter((card) => card.category === selected && (!card.isEnterprise || card.isEnterprise && isEnterprise)) + .map((card) => ( +
+ +
+ )); + }, [selected]); + + const onCardClick = (cardId: number) => { + if (selectedCards.includes(cardId)) { + setSelectedCards(selectedCards.filter((id) => id !== cardId)); + } else { + setSelectedCards([...selectedCards, cardId]); } + }; - return ( - <> - -
- {dashboardId ? (isLibrary ? "Add Card" : "Create Card") : "Select a template to create a card"} -
- {isLibrary && ( - - {selectedCards.length > 0 ? ( - - ) : ''} + const onAddSelected = () => { + const dashboard = dashboardStore.getDashboard(dashboardId); + dashboardStore.addWidgetToDashboard(dashboard!, selectedCards).finally(() => { + dashboardStore.fetch(dashboardId); + props.onClose(true); + }); + }; - setLibraryQuery(value.target.value)} - style={{width: 200}} - /> - - )} -
+ return ( + <> + +
+ {dashboardId ? (isLibrary ? 'Your Library' : 'Create Card') : 'Select a template to create a card'} +
+ {isLibrary && ( + + {selectedCards.length > 0 ? ( + + ) : ''} - {!isLibrary && } + setLibraryQuery(value.target.value)} + style={{ width: 200 }} + /> + + )} +
- {isLibrary ? - : - } - - ); + {!isLibrary && } + + {isLibrary ? + : + } + + ); }; interface CategorySelectorProps { - setSelected?: React.Dispatch>; - selected?: string; + setSelected?: React.Dispatch>; + selected?: string; } -const CategorySelector: React.FC = ({setSelected, selected}) => ( - ({ - label:
+ 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. +
+
+
+ } + > +
+ { + list?.map((item: any, index: any) => ( + + + dashboard?.swapWidgetPosition(dragIndex, hoverIndex) } - > -
{smallWidgets.length > 0 ? ( - <> -
- - Web Vitals -
- - {smallWidgets && - smallWidgets.map((item: any, index: any) => ( - - - dashboard?.swapWidgetPosition(dragIndex, hoverIndex) - - } dashboardId={dashboardId} - siteId={siteId} - isSaved={true} - grid="vitals" - /> - - ))} - - - ) : 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} - isSaved={true} - grid="other" - /> - - ))} -
- - - ) - )); + dashboardId={dashboardId} + siteId={siteId} + isWidget={false} + 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 index cc3c08f92..72b4dece6 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/AddStepButton.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/AddStepButton.tsx @@ -23,8 +23,8 @@ function AddStepButton({series, excludeFilterKeys}: Props) { onFilterClick={onAddFilter} excludeFilterKeys={excludeFilterKeys} > - ); diff --git a/frontend/app/components/Dashboard/components/FilterSeries/ExcludeFilters.tsx b/frontend/app/components/Dashboard/components/FilterSeries/ExcludeFilters.tsx index ad893528a..d7ae15483 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/ExcludeFilters.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/ExcludeFilters.tsx @@ -6,7 +6,7 @@ import React from 'react'; import FilterItem from 'Shared/Filters/FilterItem'; import cn from 'classnames'; -import { Button } from 'UI'; +import { Button } from 'antd'; interface Props { filter: Filter; @@ -47,7 +47,7 @@ function ExcludeFilters(props: Props) { ))} ) : ( - )} diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx index e87a4dd54..61c8ddb3a 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -85,7 +85,7 @@ function FilterSeries(props: Props) { }, canDelete, hideHeader = false, - emptyMessage = 'Add user event or filter to define the series by clicking Add Step.', + emptyMessage = 'Add an event or filter step to define the series.', supportsEmpty = true, excludeFilterKeys = [], canExclude = false, diff --git a/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/SeriesName.tsx b/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/SeriesName.tsx index d6a69c73d..54f94aff9 100644 --- a/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/SeriesName.tsx +++ b/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/SeriesName.tsx @@ -1,5 +1,6 @@ import React, { useState, useRef, useEffect } from 'react'; import { Icon } from 'UI'; +import {Input, Tooltip} from 'antd'; interface Props { name: string; @@ -35,21 +36,26 @@ function SeriesName(props: Props) { return (
{ editing ? ( - setEditing(true)} + className='bg-white' /> ) : (
{name && name.trim() === '' ? 'Series ' + (seriesIndex + 1) : name }
)} -
setEditing(true)}>
+ +
setEditing(true)}> + + + +
); } diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx index e0513d416..28ad4274e 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx @@ -57,11 +57,11 @@ function FunnelIssues() { }, [stages.length, drillDownPeriod, filter.filters, depsString, metricStore.sessionsPage]); return useObserver(() => ( -
+

Most significant issues identified in this funnel

-
+
diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx index 2dade061d..988b5a876 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx @@ -2,6 +2,8 @@ import React, { useEffect } from 'react'; import Select from 'Shared/Select' import { components } from 'react-select'; import { Icon } from 'UI'; +import { Button } from 'antd'; +import { FunnelPlotOutlined } from '@ant-design/icons'; import FunnelIssuesSelectedFilters from '../FunnelIssuesSelectedFilters'; import { useStore } from 'App/mstore'; import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv'; @@ -59,7 +61,7 @@ function FunnelIssuesDropdown() { } return ( -
+
*/} +
diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index 577f66732..4be56311e 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -253,8 +253,8 @@ function WidgetChart(props: Props) { return
Unknown metric type
; }; return ( - -
{renderChart()}
+ +
{renderChart()}
); } diff --git a/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx b/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx index 2527b1f34..d795c45bc 100644 --- a/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx +++ b/frontend/app/components/Dashboard/components/WidgetDateRange/WidgetDateRange.tsx @@ -27,6 +27,8 @@ function WidgetDateRange({ period={period} onChange={onChangePeriod} right={true} + isAnt={true} + useButtonStyle={true} /> ); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/CardBuilder.tsx b/frontend/app/components/Dashboard/components/WidgetForm/CardBuilder.tsx index b7066b5d8..e7cfea94f 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/CardBuilder.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/CardBuilder.tsx @@ -5,7 +5,7 @@ import {metricOf, issueOptions, issueCategories} from 'App/constants/filterOptio 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 {Card, Input, Space, Button, Segmented, Alert} from 'antd'; import {AudioWaveform} from "lucide-react"; import FilterSeries from '../FilterSeries'; import Select from 'Shared/Select'; @@ -28,16 +28,13 @@ const AIInput = ({value, setValue, placeholder, onEnter}) => ( placeholder={placeholder} value={value} onChange={(e) => setValue(e.target.value)} - className='w-full mb-2' + className='w-full mb-2 bg-white' 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) => { @@ -185,7 +182,7 @@ const SeriesList = observer(() => { emptyMessage={ metric.metricType === TABLE ? '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.' + : 'Add an event or filter step to define the series.' } />
diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index f85f0cce1..7019f0826 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -4,6 +4,7 @@ import {FilterKey} from 'Types/filter/filterType'; import {useStore} from 'App/mstore'; import {observer} from 'mobx-react-lite'; import {Button, Icon, confirm, Tooltip} from 'UI'; +import {Input, Alert} from 'antd' import FilterSeries from '../FilterSeries'; import Select from 'Shared/Select'; import {withSiteId, dashboardMetricDetails, metricDetails} from 'App/routes'; @@ -26,7 +27,6 @@ import {eventKeys} from 'App/types/filter/newFilter'; import {renderClickmapThumbnail} from './renderMap'; import Widget from 'App/mstore/types/widget'; import FilterItem from 'Shared/Filters/FilterItem'; -import {Input} from 'antd' interface Props { history: any; @@ -261,12 +261,7 @@ function WidgetForm(props: Props) { )} {isPredefined && ( -
- -
- Filtering and drill-downs will be supported soon for this card type. -
-
+ )} {testingKey ?
diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx index 014d0f6bb..8e317f8ba 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetFormNew.tsx @@ -1,179 +1,172 @@ import React from 'react'; -import {Card, Space, Typography, Button} from "antd"; -import {useStore} from "App/mstore"; -import {eventKeys} from "Types/filter/newFilter"; +import { Card, Space, Typography, Button, Alert } 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"; + 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 { 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); + 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 && ( - - )} - - ); + return isPredefined ? : ( + + + {!hasFilters && ()} + {hasFilters && ()} + + ); } export default observer(WidgetFormNew); -function DefineSteps({metric, excludeFilterKeys}: any) { - return ( - - Define Steps - - - ); +function DefineSteps({ metric, excludeFilterKeys }: any) { + return ( + +
+ Filter + +
+
+ ); } -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 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 isSingleSeries = isTable || isFunnel || isClickMap || isInsights || isRetention; - // const onAddFilter = (filter: any) => { - // metric.series[0].filter.addFilter(filter); - // metric.updateKey('hasChanged', true) - // } + // 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} - /> -
- )) - } + 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 an event or filter step to define the series.' + } + expandable={isSingleSeries} + /> +
+ )) + } - {!isSingleSeries && canAddSeries && ( - - - - )} - - ); -}) + }} + disabled={!canAddSeries} + size="small" + > + + + New Chart Series + + + + )} + + ); +}); -const PathAnalysisFilter = observer(({metric}: any) => ( - -
- metric.updateStartPoint(val)} - onRemoveFilter={() => { - }} - /> -
-
+const PathAnalysisFilter = observer(({ metric }: any) => ( + +
+ metric.updateStartPoint(val)} + onRemoveFilter={() => { + }} + /> +
+
)); const AdditionalFilters = observer(() => { - const {metricStore, dashboardStore, aiFiltersStore} = useStore(); - const metric: any = metricStore.instance; + const { metricStore, dashboardStore, aiFiltersStore } = useStore(); + const metric: any = metricStore.instance; - return ( - - {metric.metricType === USER_PATH && } - - ) + 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/WidgetName/WidgetName.tsx b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx index 00abe60ec..2db526d7a 100644 --- a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx +++ b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx @@ -1,5 +1,6 @@ import React, { useState, useRef, useEffect } from 'react'; import { Icon, Tooltip } from 'UI'; +import { Input } from 'antd'; import cn from 'classnames'; interface Props { @@ -53,10 +54,9 @@ function WidgetName(props: Props) { return (
{ editing ? ( - onBlur()} @@ -80,7 +80,11 @@ function WidgetName(props: Props) { )} - { canEdit &&
setEditing(true)}>
} + { canEdit &&
setEditing(true)}> + + + +
}
); } diff --git a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx index a7ae663d2..174bc93f1 100644 --- a/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPredefinedChart/WidgetPredefinedChart.tsx @@ -60,7 +60,7 @@ function WidgetPredefinedChart(props: Props) { case FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION: return case FilterKey.SPEED_LOCATION: - return + return case FilterKey.CPU: return case FilterKey.CRASHES: @@ -76,9 +76,9 @@ function WidgetPredefinedChart(props: Props) { case FilterKey.RESOURCES_VS_VISUALLY_COMPLETE: return case FilterKey.SESSIONS_PER_BROWSER: - return + return case FilterKey.SLOWEST_DOMAINS: - return + return case FilterKey.TIME_TO_RENDER: return diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index 78f735502..2420319a4 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -1,65 +1,137 @@ -import { Space, Switch } from 'antd'; -import cn from 'classnames'; -import { observer } from 'mobx-react-lite'; import React from 'react'; - -import { CLICKMAP, USER_PATH } from 'App/constants/card'; -import { useStore } from 'App/mstore'; -import ClickMapRagePicker from 'Components/Dashboard/components/ClickMapRagePicker'; - +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 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 AddToDashboardButton from "Components/Dashboard/components/AddToDashboardButton"; interface Props { - className?: string; - name: string; - isEditing?: boolean; + className?: string; + name: string; + isEditing?: boolean; } function WidgetPreview(props: Props) { - const { className = '' } = props; - const { metricStore, dashboardStore } = useStore(); - const metric: any = metricStore.instance; + 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}); + // } - return ( - <> -
-
-

{props.name}

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

+ {props.name} +

+
+ {metric.metricType === USER_PATH && ( + { + e.preventDefault(); + metric.update({hideExcess: !metric.hideExcess}); + }} + > + + + Hide Minor Paths + + + )} -
- {metric.metricType === CLICKMAP ? : null} -
-
-
- -
-
- - ); + {/*{isTimeSeries && (*/} + {/* <>*/} + {/* Visualization*/} + {/* */} + {/* */} + {/*)}*/} + + {/*{!disableVisualization && isTable && (*/} + {/* <>*/} + {/* Visualization*/} + {/* */} + {/* */} + {/*)}*/} + + {/*{isRetention && (*/} + {/* <>*/} + {/* Visualization*/} + {/* */} + {/**/} + {/*)}*/} + +
+ {metric.metricType === CLICKMAP ? ( + + ) : null} + + + {/* add to dashboard */} + {/*{metric.exists() && (*/} + {/* */} + {/*)}*/} +
+
+
+ +
+
+ + ); } export default observer(WidgetPreview); diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx index f08cfff7c..a23d6ea10 100644 --- a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -115,7 +115,7 @@ function WidgetSessions(props: Props) { }; return ( -
+

{metricStore.clickMapSearch ? 'Clicks' : 'Sessions'}

diff --git a/frontend/app/components/Dashboard/components/WidgetView/CardViewMenu.tsx b/frontend/app/components/Dashboard/components/WidgetView/CardViewMenu.tsx index a90ff63f1..ff422216a 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/CardViewMenu.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/CardViewMenu.tsx @@ -1,7 +1,7 @@ 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 {Button, Dropdown, MenuProps, message, Modal} from "antd"; import {BellIcon, EllipsisVertical, TrashIcon} from "lucide-react"; import {toast} from "react-toastify"; import React from "react"; @@ -36,8 +36,7 @@ const CardViewMenu = () => { }, { key: 'remove', - danger: true, - label: 'Remove', + label: 'Delete', icon: , onClick: () => { Modal.confirm({ diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index 31516da65..8202016ca 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -1,183 +1,174 @@ -import { FilterKey } from 'Types/filter/filterType'; -import { Space } from 'antd'; -import { useObserver } from 'mobx-react-lite'; -import React, { useState } from 'react'; -import { Prompt, useHistory } from 'react-router'; - -import { - CLICKMAP, - FUNNEL, - INSIGHTS, - RETENTION, - TABLE, - TIMESERIES, - USER_PATH, -} from 'App/constants/card'; -import { useStore } from 'App/mstore'; -import Widget from 'App/mstore/types/widget'; -import { dashboardMetricDetails, metricDetails, withSiteId } from 'App/routes'; -import WidgetFormNew from 'Components/Dashboard/components/WidgetForm/WidgetFormNew'; -import { renderClickmapThumbnail } from 'Components/Dashboard/components/WidgetForm/renderMap'; -import WidgetViewHeader from 'Components/Dashboard/components/WidgetView/WidgetViewHeader'; -import { Icon, Loader, NoContent } from 'UI'; - -import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; -import Breadcrumb from 'Shared/Breadcrumb'; - -import CardIssues from '../CardIssues'; -import CardUserList from '../CardUserList/CardUserList'; -import FunnelIssues from '../Funnels/FunnelIssues/FunnelIssues'; +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 {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, useHistory} from 'react-router'; +import AnimatedSVG, {ICONS} from 'Shared/AnimatedSVG/AnimatedSVG'; +import { + TIMESERIES, + TABLE, + CLICKMAP, + FUNNEL, + INSIGHTS, + USER_PATH, + RETENTION, +} 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; + history: any; + match: any; + siteId: any; } function WidgetView(props: Props) { - const { - match: { - params: { siteId, dashboardId, metricId }, - }, - } = props; - // 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; + const { + match: { + params: {siteId, dashboardId, metricId}, + }, + } = props; + // 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.response.status === 404 || e.response.status === 422) { - setMetricNotFound(true); + React.useEffect(() => { + if (metricId && metricId !== 'create') { + metricStore.fetch(metricId, dashboardStore.period).catch((e) => { + if (e.response.status === 404 || e.response.status === 422) { + setMetricNotFound(true); + } + }); + } else { + metricStore.init(); } - }); - } else { - metricStore.init(); - } - }, []); + }, []); - // const onBackHandler = () => { - // props.history.goBack(); - // }; - // - // const openEdit = () => { - // if (expanded) return; - // setExpanded(true); - // }; + // 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 undoChanges = () => { + const w = new Widget(); + metricStore.merge(w.fromJson(initialInstance), false); + }; - const onSave = async () => { - const wasCreating = !widget.exists(); - if (isClickMap) { - try { - widget.sessionId = widget.data.sessionId; - 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) - ); - } - } - }; + 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(() => ( - - { - if ( - location.pathname.includes('/metrics/') || - location.pathname.includes('/metric/') - ) { - return true; - } - return 'You have unsaved changes. Are you sure you want to leave?'; - }} - /> + return useObserver(() => ( + + { + if (location.pathname.includes('/metrics/') || location.pathname.includes('/metric/')) { + return true; + } + return 'You have unsaved changes. Are you sure you want to leave?'; + }} + /> -
- - - -
Metric not found!
+
+ + + +
Metric not found!
+
+ } + > +
+
+ +
+ +
+ +
+ + {/*
*/} + {/* */} + {/*
*/} + +
+ + + {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 && } - - -
- - )); + + )); } export default WidgetView; diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx index 49a22e3a1..ea0cbf829 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetViewHeader.tsx @@ -26,7 +26,8 @@ function WidgetViewHeader({onClick, onSave, undoChanges}: Props) {

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

diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/CardMenu.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/CardMenu.tsx index c2d5494fb..90426a56c 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/CardMenu.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/CardMenu.tsx @@ -21,7 +21,7 @@ function CardMenu({card}: any) { }, { key: 'hide', - label: 'Hide', + label: 'Remove', icon: , }, ]; diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx index a740b0f2f..126e347b1 100644 --- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx +++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapperNew.tsx @@ -27,7 +27,7 @@ interface Props { active?: boolean; history?: any; onClick?: () => void; - isSaved?: boolean; + isWidget?: boolean; hideName?: boolean; grid?: string; isGridView?: boolean; @@ -36,7 +36,7 @@ interface Props { function WidgetWrapperNew(props: Props & RouteComponentProps) { const {dashboardStore} = useStore(); const { - isSaved = false, + isWidget = false, active = false, index = 0, moveListItem = null, @@ -75,7 +75,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) { }); const onChartClick = () => { - if (!isSaved || isPredefined) return; + // if (!isWidget || isPredefined) return; props.history.push( withSiteId(dashboardMetricDetails(dashboard?.dashboardId, widget.metricId), siteId) ); @@ -86,16 +86,16 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) { const addOverlay = isTemplate || (!isPredefined && - isSaved && + isWidget && widget.metricOf !== FilterKey.ERRORS && widget.metricOf !== FilterKey.SESSIONS); return ( null} id={`widget-${widget.widgetId}`} title={!props.hideName ? widget.name : null} - extra={isSaved ? [ + extra={isWidget ? [
{!isPredefined && isTimeSeries && !isGridView && ( @@ -131,7 +131,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) { }, }} > - {!isTemplate && isSaved && isPredefined && ( + {!isTemplate && isWidget && isPredefined && (
@@ -148,7 +148,7 @@ function WidgetWrapperNew(props: Props & RouteComponentProps) { isPreview={isPreview} metric={widget} isTemplate={isTemplate} - isSaved={isSaved} + isWidget={isWidget} />
diff --git a/frontend/app/components/ForgotPassword/ForgotPassword.tsx b/frontend/app/components/ForgotPassword/ForgotPassword.tsx index 2f962e94b..253c7283d 100644 --- a/frontend/app/components/ForgotPassword/ForgotPassword.tsx +++ b/frontend/app/components/ForgotPassword/ForgotPassword.tsx @@ -1,6 +1,7 @@ import Copyright from 'Shared/Copyright'; import React from 'react'; -import { Form, Input, Loader, Button, Link, Icon, Message } from 'UI'; +import { Form, Input, Loader, Link, Icon, Message } from 'UI'; +import {Button} from 'antd'; import { login as loginRoute } from 'App/routes'; import { connect } from 'react-redux'; import ResetPassword from './ResetPasswordRequest'; @@ -41,7 +42,7 @@ function ForgotPassword(props: Props) {
-
{'Back to Login'}
+
diff --git a/frontend/app/components/ForgotPassword/ResetPasswordRequest.tsx b/frontend/app/components/ForgotPassword/ResetPasswordRequest.tsx index 5c970457c..2be473d3b 100644 --- a/frontend/app/components/ForgotPassword/ResetPasswordRequest.tsx +++ b/frontend/app/components/ForgotPassword/ResetPasswordRequest.tsx @@ -75,8 +75,8 @@ function ResetPasswordRequest(props: Props) { required /> - )} diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx index c4a35fded..4006b7c32 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx @@ -1,148 +1,149 @@ -import {durationFormatted} from 'App/date'; +import { durationFormatted } from 'App/date'; import React from 'react'; import FunnelStepText from './FunnelStepText'; -import {Icon} from 'UI'; -import {Space} from "antd"; +import { Icon } from 'UI'; +import { Space } from 'antd'; +import { Styles } from 'Components/Dashboard/Widgets/common'; 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:opacity-75'} + /> +
+
+ {/* @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 - -
- ); + + 0 ? 'red' : 'gray-light'} size={16} /> + 0 ? 'color-red' : 'disabled')}>{filter.droppedCount} Sessions + 0 ? 'color-red' : 'disabled')}>({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 22f95d3c2..82a6e7e4b 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx @@ -5,6 +5,7 @@ import cn from 'classnames'; import stl from './FunnelWidget.module.css'; import { observer } from 'mobx-react-lite'; import { NoContent, Icon } from 'UI'; +import { Tag, Tooltip } from 'antd'; import { useModal } from 'App/components/Modal'; interface Props { @@ -90,19 +91,21 @@ function FunnelWidget(props: Props) {
- Lost conversion -
- {funnel.lostConversions} - ({funnel.lostConversionsPercentage}%) -
+ Lost conversion + + + {funnel.lostConversions} + +
- Total conversion -
- {funnel.totalConversions} - ({funnel.totalConversionsPercentage}%) -
+ Total conversion + + + {funnel.totalConversions} + +
{funnel.totalDropDueToIssues > 0 &&
{funnel.totalDropDueToIssues} sessions dropped due to issues.
} diff --git a/frontend/app/components/Login/Login.tsx b/frontend/app/components/Login/Login.tsx index 519f693c3..ca187b1d6 100644 --- a/frontend/app/components/Login/Login.tsx +++ b/frontend/app/components/Login/Login.tsx @@ -110,6 +110,7 @@ const Login: React.FC = ({errors, loading, authDetails, login, setJw onChange={(e) => setEmail(e.target.value)} required icon="envelope" + /> @@ -141,7 +142,7 @@ const Login: React.FC = ({errors, loading, authDetails, login, setJw
- +
); diff --git a/frontend/app/components/Session_/Player/Controls/components/KeyboardHelp.tsx b/frontend/app/components/Session_/Player/Controls/components/KeyboardHelp.tsx index bf77e4ca4..e33a73083 100644 --- a/frontend/app/components/Session_/Player/Controls/components/KeyboardHelp.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/KeyboardHelp.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Icon } from 'UI'; -import { Popover, Button } from 'antd'; +import {Keyboard} from 'lucide-react' +import { Button, Tooltip } from 'antd'; import { useModal } from "../../../../Modal"; const Key = ({ label }: { label: string }) =>
{label}
; @@ -35,7 +36,7 @@ function ShortcutGrid() { return (
Keyboard Shortcuts
-
+
@@ -62,6 +63,7 @@ function ShortcutGrid() { function KeyboardHelp() { const { showModal } = useModal(); return ( + + ); } diff --git a/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx b/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx index 8b3ad1b15..af18b5962 100644 --- a/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/ReadNote.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { Icon } from 'UI'; +import { Button, Tag } from 'antd'; +import { PlayCircleOutlined } from '@ant-design/icons'; import { tagProps, Note } from 'App/services/NotesService'; import { formatTimeOrDate } from 'App/date'; import { useStore } from 'App/mstore'; import { observer } from 'mobx-react-lite'; import { TeamBadge } from 'Shared/SessionsTabOverview/components/Notes'; -import { Tag } from 'antd' interface Props { note?: Note; @@ -21,7 +22,7 @@ function ReadNote(props: Props) { return (
@@ -50,8 +51,8 @@ function ReadNote(props: Props) { className="flex items-center justify-center" >
@@ -71,23 +72,32 @@ function ReadNote(props: Props) { {props.note.message}
-
+
+
{props.note.tag ? ( {props.note.tag} ) : null} + + {!props.note.isPublic ? null : } + -
- - Play Session
+ +
diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index e80c6030d..7a4ef9deb 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { useStore } from 'App/mstore'; import KeyboardHelp from 'Components/Session_/Player/Controls/components/KeyboardHelp'; -import { Icon, Tooltip } from 'UI'; +import { Icon } from 'UI'; import QueueControls from './QueueControls'; import Bookmark from 'Shared/Bookmark'; import SharePopup from '../shared/SharePopup/SharePopup'; @@ -13,7 +13,7 @@ import { connect } from 'react-redux'; import SessionTabs from 'Components/Session/Player/SharedComponents/SessionTabs'; import { IFRAME } from 'App/constants/storageKeys'; import cn from 'classnames'; -import { Switch, Button as AntButton, Popover } from 'antd'; +import { Switch, Button as AntButton, Popover, Tooltip } from 'antd'; import { ShareAltOutlined } from '@ant-design/icons'; import { checkParam } from 'App/utils'; @@ -116,11 +116,11 @@ function SubHeader(props) { showCopyLink={true} trigger={
- + - +
} /> diff --git a/frontend/app/components/Session_/components/NotePopup.tsx b/frontend/app/components/Session_/components/NotePopup.tsx index ae45fc926..cedeec750 100644 --- a/frontend/app/components/Session_/components/NotePopup.tsx +++ b/frontend/app/components/Session_/components/NotePopup.tsx @@ -2,7 +2,7 @@ import CreateNote from 'Components/Session_/Player/Controls/components/CreateNot import React from 'react'; import { connect } from 'react-redux'; import { PlayerContext } from 'App/components/Session/playerContext'; -import { Button, Popover } from 'antd'; +import { Button, Tooltip } from 'antd'; import { MessageOutlined } from '@ant-design/icons'; import { useModal } from 'App/components/Modal'; @@ -22,7 +22,7 @@ function NotePopup({ tooltipActive }: { tooltipActive: boolean }) { }; return ( - + - + ); } diff --git a/frontend/app/components/Signup/SignupForm/SignupForm.tsx b/frontend/app/components/Signup/SignupForm/SignupForm.tsx index 0a387e0e2..2a5981573 100644 --- a/frontend/app/components/Signup/SignupForm/SignupForm.tsx +++ b/frontend/app/components/Signup/SignupForm/SignupForm.tsx @@ -97,7 +97,7 @@ const SignupForm: React.FC = ({ tenants, errors, loading, signu
Logo
-
+

Create Account @@ -138,6 +138,7 @@ const SignupForm: React.FC = ({ tenants, errors, loading, signu onChange={write} required={true} icon='envelope' + className='rounded-lg' /> @@ -150,6 +151,7 @@ const SignupForm: React.FC = ({ tenants, errors, loading, signu onChange={write} required={true} icon='key' + className='rounded-lg' /> @@ -161,6 +163,7 @@ const SignupForm: React.FC = ({ tenants, errors, loading, signu onChange={write} required={true} icon='user-alt' + className='rounded-lg' /> @@ -172,13 +175,14 @@ const SignupForm: React.FC = ({ tenants, errors, loading, signu onChange={write} required={true} icon='buildings' + className='rounded-lg' /> {passwordError && ( // = ({ tenants, errors, loading, signu )} {errors && errors.length && ( )} -
diff --git a/frontend/app/components/UsabilityTesting/UsabilityTesting.tsx b/frontend/app/components/UsabilityTesting/UsabilityTesting.tsx index 9c5d374d9..063592c6d 100644 --- a/frontend/app/components/UsabilityTesting/UsabilityTesting.tsx +++ b/frontend/app/components/UsabilityTesting/UsabilityTesting.tsx @@ -8,7 +8,7 @@ import AnimatedSVG from 'Shared/AnimatedSVG'; import { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG'; import { Loader, NoContent, Pagination, Link, Icon } from 'UI'; import { checkForRecent, getDateFromMill } from 'App/date'; -import { ArrowRightOutlined } from '@ant-design/icons'; +import { ArrowRightOutlined, PlusOutlined } from '@ant-design/icons'; import { useHistory, useParams } from 'react-router-dom'; import { withSiteId, usabilityTestingEdit, usabilityTestingView } from 'App/routes'; import { debounce } from 'App/utils'; @@ -110,9 +110,10 @@ function TestsTable() { Usability Tests

- + - + - +
); } diff --git a/frontend/app/components/shared/Breadcrumb/BackButton.tsx b/frontend/app/components/shared/Breadcrumb/BackButton.tsx new file mode 100644 index 000000000..a93ad705f --- /dev/null +++ b/frontend/app/components/shared/Breadcrumb/BackButton.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Button } from 'antd'; +import { useHistory } from 'react-router-dom'; +import { LeftOutlined } from '@ant-design/icons'; + +function BackButton() { + const history = useHistory(); + const siteId = location.pathname.split('/')[1]; + + const handleBackClick = () => { + history.push(`/${siteId}/dashboard`); + }; + + return ( + + ); +} + +export default BackButton; diff --git a/frontend/app/components/shared/DateRangeDropdown/dateRangePopup.module.css b/frontend/app/components/shared/DateRangeDropdown/dateRangePopup.module.css index 98b2bf7f4..154d056bf 100644 --- a/frontend/app/components/shared/DateRangeDropdown/dateRangePopup.module.css +++ b/frontend/app/components/shared/DateRangeDropdown/dateRangePopup.module.css @@ -1,6 +1,8 @@ .wrapper { - background-color: white; - outline: solid thin #CCC; + background-color: #FFF; + outline: solid thin #EEE; + border-radius: .5rem; + box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); & .body { display: flex; border-bottom: solid thin $gray-light; @@ -11,7 +13,7 @@ .preSelections { width: 130px; background-color: white; - border-right: solid thin $gray-light; + border-right: solid thin #EEE; & > div { padding: 8px 10px; width: 100%; diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index 72cd8bbf0..c02d49d00 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -147,7 +147,7 @@ function FilterList(props: Props) { width: 'calc(100% + 2.5rem)', }} className={ - 'hover:bg-active-blue px-5 gap-2 items-center flex z-10' + 'hover:bg-active-blue px-5 gap-2 items-center flex' } id={`${filter.key}-${filterIndex}`} onDragOver={(e) => handleDragOverEv(e, filterIndex)} diff --git a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx index c8eb01438..b8de307bd 100644 --- a/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx +++ b/frontend/app/components/shared/Filters/FilterSelection/FilterSelection.tsx @@ -47,7 +47,7 @@ function FilterSelection(props: Props) { }) ) : (
setShowModal(true)} > diff --git a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx index 5f67e292d..03ff62f8a 100644 --- a/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx +++ b/frontend/app/components/shared/LiveSessionList/LiveSessionList.tsx @@ -41,7 +41,7 @@ function LiveSessionList(props: Props) { var timeoutId: any; const { filters } = filter; const hasUserFilter = filters.map((i: any) => i.key).includes(KEYS.USERID); - const sortOptions = [{ label: 'Newest', value: 'timestamp' }].concat( + const sortOptions = [{ label: 'Freshness', value: 'timestamp' }].concat( metaList .map((i: any) => ({ label: capitalize(i), @@ -105,6 +105,7 @@ function LiveSessionList(props: Props) { onChange={onSortChange} value={sortOptions.find((i: any) => i.value === filter.sort) || sortOptions[0]} /> +
props.applyFilter({ order: state })} @@ -204,3 +205,4 @@ export default withPermissions(['ASSIST_LIVE'])( } )(LiveSessionList) ); + diff --git a/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js b/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js index a24daa891..2dec6fc5d 100644 --- a/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js +++ b/frontend/app/components/shared/NoSessionsMessage/NoSessionsMessage.js @@ -1,11 +1,16 @@ import React from 'react'; -import { Icon, Button } from 'UI'; +import { Alert, Space, Button } from 'antd'; import { connect } from 'react-redux'; import { onboarding as onboardingRoute } from 'App/routes'; import { withRouter } from 'react-router-dom'; import * as routes from '../../../routes'; +import { indigo } from 'tailwindcss/colors'; +import { SquareArrowOutUpRight } from 'lucide-react'; + const withSiteId = routes.withSiteId; +const indigoWithOpacity = `rgba(${parseInt(indigo[500].slice(1, 3), 16)}, ${parseInt(indigo[500].slice(3, 5), 16)}, ${parseInt(indigo[500].slice(5, 7), 16)}, 0.1)`; // 0.5 is the opacity level + const NoSessionsMessage = (props) => { const { @@ -19,32 +24,35 @@ const NoSessionsMessage = (props) => { return ( <> {showNoSessions && ( -
-
-
-
- -
-
- It might take a few minutes for first recording to appear. - - Troubleshoot - - . -
- -
+
+ + + + + + } + /> +
-
)} ); diff --git a/frontend/app/components/shared/ReloadButton/ReloadButton.tsx b/frontend/app/components/shared/ReloadButton/ReloadButton.tsx index 774d427fe..03285c749 100644 --- a/frontend/app/components/shared/ReloadButton/ReloadButton.tsx +++ b/frontend/app/components/shared/ReloadButton/ReloadButton.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { CircularLoader, Icon, Tooltip, Button } from 'UI'; +import {Button, Tooltip} from 'antd'; +import { ListRestart } from 'lucide-react'; import cn from 'classnames'; interface Props { @@ -12,8 +13,9 @@ interface Props { export default function ReloadButton(props: Props) { const { loading, onClick, iconSize = '20', iconName = 'arrow-repeat', className = '' } = props; return ( - - ); diff --git a/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx b/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx index c4ac866cf..0f0520295 100644 --- a/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx +++ b/frontend/app/components/shared/SessionSearchField/SessionSearchField.tsx @@ -54,7 +54,7 @@ function SessionSearchField(props: Props) { id="search" type="search" autoComplete="off" - className="hover:border-gray-medium text-lg placeholder-lg" + className="hover:border-gray-medium text-lg placeholder-lg h-9 shadow-sm" /> {showModal && ( diff --git a/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteItem.tsx b/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteItem.tsx index 86cbb74de..0c365f833 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteItem.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/Notes/NoteItem.tsx @@ -41,35 +41,43 @@ function NoteItem(props: Props) { } }; const menuItems = [ - { icon: 'link-45deg', text: 'Copy Note URL', onClick: onCopy }, + { icon: 'link-45deg', text: 'Copy Link', onClick: onCopy }, { icon: 'trash', text: 'Delete', onClick: onDelete }, ]; const safeStrMessage = props.note.message.length > 150 ? props.note.message.slice(0, 150) + '...' : props.note.message; return ( -
+
0 - ? `?jumpto=${props.note.timestamp}¬e=${props.note.noteId}` - : `?note=${props.note.noteId}`) - } - > -
-
{safeStrMessage}
-
- {props.note.tag ? ( + session(props.note.sessionId) + + (props.note.timestamp > 0 + ? `?jumpto=${props.note.timestamp}¬e=${props.note.noteId}` + : `?note=${props.note.noteId}`) + } + > +
+
+ + {props.note.tag ? ( {props.note.tag} ) : null} -
- By + +
+ {safeStrMessage} +
+
+
+ +
+ By {props.note.userName},{' '} {formatTimeOrDate(props.note.createdAt as unknown as number, timezone)}
diff --git a/frontend/app/components/shared/SessionsTabOverview/components/Notes/TeamBadge.tsx b/frontend/app/components/shared/SessionsTabOverview/components/Notes/TeamBadge.tsx index 8f509b5eb..9affbc7e1 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/Notes/TeamBadge.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/Notes/TeamBadge.tsx @@ -5,7 +5,7 @@ export default function TeamBadge() { return (
- Team + Team
) } \ No newline at end of file diff --git a/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx b/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx index 08549dba9..dfd2a8504 100644 --- a/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx +++ b/frontend/app/components/shared/SessionsTabOverview/components/SessionList/SessionList.tsx @@ -215,7 +215,7 @@ function SessionList(props: Props) {
-
+
{NO_CONTENT.message}
diff --git a/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx b/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx index e408d683d..e0cd8a82b 100644 --- a/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx +++ b/frontend/app/components/shared/SharePopup/SessionCopyLink/SessionCopyLink.tsx @@ -1,5 +1,7 @@ import React from 'react'; -import { Button, Icon } from 'UI'; +import { Button } from 'antd'; +import { LinkOutlined } from '@ant-design/icons'; + import copy from 'copy-to-clipboard'; function SessionCopyLink({ time }: { time: number }) { @@ -20,11 +22,8 @@ function SessionCopyLink({ time }: { time: number }) { return (
- {copied &&
Copied
}
diff --git a/frontend/app/components/shared/SharePopup/SharePopup.tsx b/frontend/app/components/shared/SharePopup/SharePopup.tsx index 896168c49..ce7955924 100644 --- a/frontend/app/components/shared/SharePopup/SharePopup.tsx +++ b/frontend/app/components/shared/SharePopup/SharePopup.tsx @@ -175,7 +175,7 @@ function ShareModalComp({
-
+
Share via
{hasBoth ? ( @@ -214,7 +214,7 @@ function ShareModalComp({
-
Select a channel or individual
+
Select a channel or individual
{shareTo === 'slack' ? (