feat(ui) - dashboard improvements - wip

This commit is contained in:
Shekar Siri 2022-12-07 14:37:34 +05:30 committed by sylenien
parent 8e9a09d6d3
commit 7bd6ee3065
24 changed files with 591 additions and 133 deletions

View file

@ -2,12 +2,16 @@ import Modal from 'App/components/Modal/Modal';
import React from 'react';
import MetricTypeList from '../MetricTypeList';
function AddCardModal() {
interface Props {
siteId: string;
dashboardId: string;
}
function AddCardModal(props: Props) {
return (
<>
<Modal.Header title="Add Card" />
<Modal.Content className="p-0">
<MetricTypeList />
<MetricTypeList siteId={props.siteId} dashboardId={props.dashboardId} />
</Modal.Content>
</>
);

View file

@ -14,6 +14,7 @@ import DashboardEditModal from '../DashboardEditModal';
import AddCardModal from '../AddCardModal';
interface IProps {
dashboardId: string;
siteId: string;
renderReport?: any;
}
@ -21,10 +22,9 @@ interface IProps {
type Props = IProps & RouteComponentProps;
function DashboardHeader(props: Props) {
const { siteId } = props;
const { siteId, dashboardId } = props;
const { dashboardStore } = useStore();
const { showModal } = useModal();
// const [showTooltip, setShowTooltip] = React.useState(false);
const [focusTitle, setFocusedInput] = React.useState(true);
const [showEditModal, setShowEditModal] = React.useState(false);
const period = dashboardStore.period;
@ -82,7 +82,9 @@ function DashboardHeader(props: Props) {
<div className="flex items-center" style={{ flex: 1, justifyContent: 'end' }}>
<Button
variant="primary"
onClick={() => showModal(<AddCardModal />, { right: true })}
onClick={() =>
showModal(<AddCardModal dashboardId={dashboardId} siteId={siteId} />, { right: true })
}
icon="plus"
>
Add Card

View file

@ -38,7 +38,7 @@ function DashboardSideMenu(props: Props) {
<SideMenuitem
active={isMetric}
id="menu-manage-alerts"
title="Metrics"
title="Cards"
iconName="bar-chart-line"
onClick={() => redirect(withSiteId(metrics(), siteId))}
/>

View file

@ -49,7 +49,7 @@ function DashboardWidgetGrid(props: Props) {
</div>
</div>
<div className="grid grid-cols-4 p-8 gap-2">
<MetricTypeList />
<MetricTypeList dashboardId={dashboardId} siteId={siteId} />
</div>
</div>
}

View file

@ -4,7 +4,7 @@ import { Icon } from 'UI';
export interface MetricType {
title: string;
icon: IconNames;
icon?: IconNames;
description: string;
slug: string;
}

View file

@ -2,90 +2,37 @@ import { useModal } from 'App/components/Modal';
import React from 'react';
import MetricsLibraryModal from '../MetricsLibraryModal';
import MetricTypeItem, { MetricType } from '../MetricTypeItem/MetricTypeItem';
import { TYPES, LIBRARY } from 'App/constants/card';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { dashboardMetricCreate, withSiteId } from 'App/routes';
const METRIC_TYPES: MetricType[] = [
{
title: 'Add From Library',
icon: 'grid',
description: 'Select a pre existing card from card library',
slug: 'library',
},
{
title: 'Timeseries',
icon: 'graph-up',
description: 'Trend of sessions count in over the time.',
slug: 'timeseries',
},
{
title: 'Table',
icon: 'list-alt',
description: 'See list of Users, Sessions, Errors, Issues, etc.,',
slug: 'table',
},
{
title: 'Funnel',
icon: 'funnel',
description: 'Uncover the issues impacting user journeys.',
slug: 'funnel',
},
{
title: 'Errors Tracking',
icon: 'exclamation-circle',
description: 'Discover user journeys between 2 points.',
slug: 'errors',
},
{
title: 'Performance Monitoring',
icon: 'speedometer2',
description: 'Retention graph of users / features over a period of time.',
slug: 'performance',
},
{
title: 'Resource Monitoring',
icon: 'files',
description: 'Find the adoption of your all features in your app.',
slug: 'resource-monitoring',
},
{
title: 'Web Vitals',
icon: 'activity',
description: 'Find the adoption of your all features in your app.',
slug: 'web-vitals',
},
{
title: 'User Path',
icon: 'signpost-split',
description: 'Discover user journeys between 2 points.',
slug: 'user-path',
},
{
title: 'Retention',
icon: 'arrow-repeat',
description: 'Retension graph of users / features over a period of time.',
slug: 'retention',
},
{
title: 'Feature Adoption',
icon: 'card-checklist',
description: 'Find the adoption of your all features in your app.',
slug: 'feature-adoption',
},
];
interface Props extends RouteComponentProps {
dashboardId: number;
siteId: string;
}
function MetricTypeList(props: Props) {
const { dashboardId, siteId, history } = props;
const { hideModal } = useModal();
function MetricTypeList() {
const { showModal } = useModal();
const onClick = ({ slug }: MetricType) => {
if (slug === 'library') {
showModal(<MetricsLibraryModal />, { right: true, width: 700 });
hideModal();
if (slug === LIBRARY) {
return showModal(<MetricsLibraryModal siteId={siteId} dashboardId={dashboardId} />, { right: true, width: 800 });
}
// TODO redirect to card builder with metricType query param
const path = withSiteId(dashboardMetricCreate(dashboardId + ''), siteId);
history.push(path);
};
return (
<>
{METRIC_TYPES.map((metric: MetricType) => (
{TYPES.map((metric: MetricType) => (
<MetricTypeItem metric={metric} onClick={() => onClick(metric)} />
))}
</>
);
}
export default MetricTypeList;
export default withRouter(MetricTypeList);

View file

@ -1,13 +1,65 @@
import Modal from 'App/components/Modal/Modal';
import React from 'react';
import React, { useMemo, useState } from 'react';
import MetricsList from '../MetricsList';
import { Button } from 'UI';
import { useModal } from 'App/components/Modal';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
interface Props {
dashboardId: number;
siteId: string;
}
function MetricsLibraryModal(props: Props) {
const { siteId, dashboardId } = props;
const [selectedList, setSelectedList] = useState([]);
console.log('dashboardId', dashboardId);
const onSelectionChange = (list: any) => {
setSelectedList(list);
};
function MetricsLibraryModal() {
return (
<>
<Modal.Header title="Cards Library" />
<Modal.Content>Hello</Modal.Content>
<Modal.Content>
<div className="border">
<MetricsList siteId={siteId} onSelectionChange={onSelectionChange} />
</div>
{/* TODO should show the dynamic values */}
<SelectedContent dashboardId={dashboardId} selected={selectedList} />
</Modal.Content>
</>
);
}
export default MetricsLibraryModal;
function SelectedContent({ dashboardId, selected }: any) {
const { hideModal } = useModal();
const { metricStore, dashboardStore } = useStore();
const total = useObserver(() => metricStore.sortedWidgets.length);
const dashboard = useMemo(() => dashboardStore.getDashboard(dashboardId), [dashboardId]);
const addSelectedToDashboard = () => {
dashboardStore.addWidgetToDashboard(dashboard, selected).then(hideModal);
};
return (
<div className="flex items-center rounded border bg-gray-light-shade absolute justify-between p-3 left-4 right-4 bottom-4">
<div>
Selected <span className="font-medium">{selected.length}</span> of{' '}
<span className="font-medium">{total}</span>
</div>
<div className="flex items-center">
<Button variant="text-primary" className="mr-2" onClick={hideModal}>
Cancel
</Button>
<Button disabled={selected.length === 0} variant="primary" onClick={addSelectedToDashboard}>
Add Selected to Dashboard
</Button>
</div>
</div>
);
}

View file

@ -7,18 +7,31 @@ import MetricListItem from '../MetricListItem';
import { sliceListPerPage } from 'App/utils';
import Widget from 'App/mstore/types/widget';
function MetricsList({ siteId }: { siteId: string }) {
function MetricsList({
siteId,
onSelectionChange = () => {},
}: {
siteId: string;
onSelectionChange?: (selected: any[]) => void;
}) {
const { metricStore } = useStore();
const metrics = metricStore.sortedWidgets;
const metricsSearch = metricStore.metricsSearch;
const [selectedMetrics, setSelectedMetrics] = useState([]);
const [selectedMetrics, setSelectedMetrics] = useState<any>([]);
useEffect(() => {
metricStore.fetchList();
}, []);
useEffect(() => {
onSelectionChange(selectedMetrics);
}, [selectedMetrics]);
const toggleMetricSelection = (id: any) => {
console.log('id', id);
if (selectedMetrics.includes(id)) {
selectedMetrics.splice(selectedMetrics.indexOf(id), 1);
setSelectedMetrics(selectedMetrics.filter((i: number) => i !== id));
} else {
selectedMetrics.push(id);
setSelectedMetrics([...selectedMetrics, id]);
}
};
@ -48,7 +61,7 @@ function MetricsList({ siteId }: { siteId: string }) {
</div>
}
>
<div className="mt-3 border-b rounded bg-white">
<div className="mt-3 rounded bg-white">
<div className="grid grid-cols-12 py-2 font-medium px-6">
<div className="col-span-4 flex items-center">
<Checkbox
@ -70,7 +83,7 @@ function MetricsList({ siteId }: { siteId: string }) {
<MetricListItem
metric={metric}
siteId={siteId}
selected={selectedMetrics[parseInt(metric.metricId)]}
selected={selectedMetrics.includes(parseInt(metric.metricId))}
toggleSelection={(e: any) => {
e.stopPropagation();
toggleMetricSelection(parseInt(metric.metricId));
@ -80,7 +93,7 @@ function MetricsList({ siteId }: { siteId: string }) {
))}
</div>
<div className="w-full flex items-center justify-between pt-4 px-6">
<div className="w-full flex items-center justify-between py-4 px-6 border-t">
<div className="text-disabled-text">
Showing{' '}
<span className="font-semibold">{Math.min(list.length, metricStore.pageSize)}</span> out

View file

@ -1,7 +1,6 @@
import React from 'react';
import withPageTitle from 'HOCs/withPageTitle';
import MetricsList from '../MetricsList';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import MetricViewHeader from '../MetricViewHeader';
@ -9,14 +8,8 @@ interface Props {
siteId: string;
}
function MetricsView({ siteId }: Props) {
const { metricStore } = useStore();
React.useEffect(() => {
metricStore.fetchList();
}, []);
return useObserver(() => (
<div style={{ maxWidth: '1300px', margin: 'auto' }} className="bg-white rounded py-4 border">
<div style={{ maxWidth: '1300px', margin: 'auto' }} className="bg-white rounded pt-4 border">
<MetricViewHeader />
<MetricsList siteId={siteId} />
</div>

View file

@ -8,6 +8,8 @@ import FilterSeries from '../FilterSeries';
import { confirm, Tooltip } from 'UI';
import Select from 'Shared/Select'
import { withSiteId, dashboardMetricDetails, metricDetails } from 'App/routes'
import MetricTypeDropdown from './components/MetricTypeDropdown';
import MetricSubtypeDropdown from './components/MetricSubtypeDropdown';
interface Props {
history: any;
@ -103,18 +105,10 @@ function WidgetForm(props: Props) {
<div className="form-group">
<label className="font-medium">Metric Type</label>
<div className="flex items-center">
<SegmentSelection
icons
outline
name="metricType"
className="my-3"
onSelect={ onSelect }
value={metricTypes.find((i) => i.value === metric.metricType) || metricTypes[0]}
// @ts-ignore
list={metricTypes.map((i) => ({ value: i.value, name: i.label, icon: metricIcons[i.value] }))}
/>
<MetricTypeDropdown onSelect={writeOption} />
<MetricSubtypeDropdown onSelect={writeOption} />
{metric.metricType === 'timeseries' && (
{/* {metric.metricType === 'timeseries' && (
<>
<span className="mx-3">of</span>
<Select
@ -124,9 +118,9 @@ function WidgetForm(props: Props) {
onChange={ writeOption }
/>
</>
)}
)} */}
{metric.metricType === 'table' && (
{/* {metric.metricType === 'table' && (
<>
<span className="mx-3">of</span>
<Select
@ -136,7 +130,7 @@ function WidgetForm(props: Props) {
onChange={ writeOption }
/>
</>
)}
)} */}
{metric.metricOf === FilterKey.ISSUE && (
<>
@ -177,7 +171,7 @@ function WidgetForm(props: Props) {
variant="text-primary"
onClick={() => metric.addSeries()}
disabled={!canAddSeries}
>Add Series</Button>
>ADD</Button>
)}
</div>

View file

@ -0,0 +1,59 @@
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import { TYPES } from 'App/constants/card';
import { MetricType } from 'App/components/Dashboard/components/MetricTypeItem/MetricTypeItem';
import React from 'react';
import Select from 'Shared/Select';
import { components } from 'react-select';
import CustomDropdownOption from 'Shared/CustomDropdownOption';
interface Props {
onSelect: any;
}
function MetricSubtypeDropdown(props: Props) {
const { metricStore } = useStore();
const metric: any = useObserver(() => metricStore.instance);
const options = React.useMemo(() => {
const type = TYPES.find((i: MetricType) => i.slug === metric.metricType);
if (type && type.subTypes) {
const options = type.subTypes.map((i: MetricType) => ({
label: i.title,
icon: i.icon,
value: i.slug,
description: i.description,
}));
return options;
}
return false;
}, [metric.metricType]);
return options ? (
<>
<div className="mx-3">of</div>
<Select
name="metricOf"
placeholder="Select Card Type"
options={options}
value={options.find((i: any) => i.value === metric.metricOf)}
onChange={props.onSelect}
// className="mx-2"
components={{
MenuList: ({ children, ...props }: any) => {
return (
<components.MenuList {...props} className="!p-3">
{children}
</components.MenuList>
);
},
Option: ({ children, ...props }: any) => {
const { data } = props;
return <CustomDropdownOption children={children} {...props} {...data} />;
},
}}
/>
</>
) : null;
}
export default MetricSubtypeDropdown;

View file

@ -0,0 +1 @@
export { default } from './MetricSubtypeDropdown';

View file

@ -0,0 +1,11 @@
import React from 'react';
import MetricTypeDropdown from './';
export default {
title: 'Dashboad/Cards/Form/MetricTypeDropdown',
component: MetricTypeDropdown,
};
const Template = (args: any) => <MetricTypeDropdown {...args} />;
export const Simple = Template.bind({});

View file

@ -0,0 +1,54 @@
import React, { useMemo } from 'react';
import { TYPES, LIBRARY } from 'App/constants/card';
import Select from 'Shared/Select';
import { MetricType } from 'App/components/Dashboard/components/MetricTypeItem/MetricTypeItem';
import { components } from 'react-select';
import CustomDropdownOption from 'Shared/CustomDropdownOption';
import { useObserver } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
interface Props {
onSelect: any;
}
function MetricTypeDropdown(props: Props) {
const { metricStore } = useStore();
const metric: any = useObserver(() => metricStore.instance);
const options: any = useMemo(() => {
// TYPES.shift(); // remove "Add from library" item
return TYPES.filter((i: MetricType) => i.slug !== LIBRARY).map((i: MetricType) => ({
label: i.title,
icon: i.icon,
value: i.slug,
description: i.description,
}));
}, []);
const onSelect = (_: any, option: Record<string, any>) =>
props.onSelect({ value: { value: option.value }, name: option.name });
return (
<Select
name="metricType"
placeholder="Select Card Type"
options={options}
value={options.find((i: any) => i.value === metric.metricType) || options[0]}
onChange={props.onSelect}
// onSelect={onSelect}
components={{
MenuList: ({ children, ...props }: any) => {
return (
<components.MenuList {...props} className="!p-3">
{children}
</components.MenuList>
);
},
Option: ({ children, ...props }: any) => {
const { data } = props;
return <CustomDropdownOption children={children} {...props} {...data} />;
},
}}
/>
);
}
export default MetricTypeDropdown;

View file

@ -0,0 +1 @@
export { default } from './MetricTypeDropdown';

View file

@ -100,7 +100,7 @@ function WidgetView(props: Props) {
</h1>
<div className="text-gray-600 w-full cursor-pointer" onClick={() => setExpanded(!expanded)}>
<div className="flex items-center select-none w-fit ml-auto">
<span className="mr-2 color-teal">{expanded ? 'Close' : 'Edit'}</span>
<span className="mr-2 color-teal">{expanded ? 'Collapse' : 'Edit'}</span>
<Icon name={expanded ? 'chevron-up' : 'chevron-down'} size="16" color="teal" />
</div>
</div>

View file

@ -47,7 +47,7 @@ Modal.Header = ({ title }: { title: string }) => {
};
Modal.Content = ({ children, className = 'p-4' }: { children: any; className?: string }) => {
return <div className={cn('h-screen overflow-y-auto', className)}>{children}</div>;
return <div className={cn('overflow-y-auto relative', className)} style={{ height: 'calc(100vh - 52px)'}}>{children}</div>;
};
export default Modal;

View file

@ -0,0 +1,38 @@
import React from 'react';
import { components, OptionProps } from 'react-select';
import { Icon } from 'UI';
import cn from 'classnames';
export interface Props extends OptionProps {
icon?: string;
label: string;
description: string;
}
function CustomDropdownOption(props: Props) {
const { icon = '', label, description, isSelected, isFocused } = props;
return (
<components.Option {...props} className="!p-0 mb-2">
<div
className={cn(
'group p-2 flex item-start border border-transparent rounded hover:border-teal hover:!bg-active-blue !leading-0'
)}
>
{icon && (
<Icon
// @ts-ignore
name={icon}
className="pt-2 mr-3"
size={18}
color={isSelected || isFocused ? 'teal' : 'gray-dark'}
/>
)}
<div className={cn('flex flex-col', { '!color-teal': isFocused || isSelected })}>
<div className="font-medium leading-0">{label}</div>
<div className="text-sm color-gray-dark">{description}</div>
</div>
</div>
</components.Option>
);
}
export default CustomDropdownOption;

View file

@ -0,0 +1 @@
export { default } from './CustomDropdownOption';

View file

@ -1,13 +1,13 @@
import { storiesOf } from '@storybook/react';
import SankeyChart from './SankeyChart';
import SankeyChart, { SankeyChartData } from './SankeyChart';
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
const data = {
const data: SankeyChartData = {
nodes: [
{ name: 'Home Page' },
{ name: 'Dashboard' },
{ name: 'Preferences' },
{ name: 'Billing' },
],
links: [
{ source: 0, target: 1, value: 100 },
@ -17,4 +17,12 @@ const data = {
],
};
storiesOf('SankeyChart', module).add('Pure', () => <SankeyChart data={data} />);
export default {
title: 'Dashboad/Cards/SankeyChart',
component: SankeyChart,
} as ComponentMeta<typeof SankeyChart>;
const Template: ComponentStory<typeof SankeyChart> = (args: any) => <SankeyChart {...args} />;
export const Simple = Template.bind({});
Simple.args = { data };

View file

@ -1,11 +1,27 @@
import React from 'react';
import { Sankey, Tooltip, Rectangle, Layer, ResponsiveContainer } from 'recharts';
type Node = {
name: string;
}
type Link = {
source: number;
target: number;
value: number;
}
export interface SankeyChartData {
links: Link[];
nodes: Node[];
}
interface Props {
data: any;
data: SankeyChartData;
nodePadding?: number;
nodeWidth?: number;
}
function SankeyChart(props: Props) {
const { data } = props;
const { data, nodePadding = 50, nodeWidth = 10 } = props;
return (
<div className="rounded border shadow">
<div className="text-lg p-3 border-b bg-gray-lightest">Sankey Chart</div>
@ -17,8 +33,8 @@ function SankeyChart(props: Props) {
data={data}
// node={{ stroke: '#77c878', strokeWidth: 0 }}
node={<CustomNodeComponent />}
nodePadding={50}
nodeWidth={10}
nodePadding={nodePadding}
nodeWidth={nodeWidth}
margin={{
left: 10,
right: 100,
@ -33,7 +49,7 @@ function SankeyChart(props: Props) {
<stop offset="100%" stopColor="rgba(0, 197, 159, 0.3)" />
</linearGradient>
</defs>
<Tooltip content={<CustomTooltip /> }/>
<Tooltip content={<CustomTooltip />} />
</Sankey>
</ResponsiveContainer>
</div>
@ -44,10 +60,7 @@ function SankeyChart(props: Props) {
export default SankeyChart;
const CustomTooltip = (props: any) => {
console.log('props', props);
return (
<div className="rounded bg-white border p-0 px-1 text-sm">test</div>
)
return <div className="rounded bg-white border p-0 px-1 text-sm">test</div>;
// if (active && payload && payload.length) {
// return (
// <div className="custom-tooltip">

View file

@ -0,0 +1,213 @@
import { IconNames } from 'App/components/ui/SVG';
import { FilterKey, IssueType } from 'Types/filter/filterType';
export interface CardType {
title: string;
icon?: IconNames;
description: string;
slug: string;
subTypes?: CardType[];
}
export const LIBRARY = 'library';
export const TIMESERIES = 'timeseries';
export const TABLE = 'table';
export const TYPES: CardType[] = [
{
title: 'Add From Library',
icon: 'grid',
description: 'Select a pre existing card from card library',
slug: LIBRARY,
},
{
title: 'Timeseries',
icon: 'graph-up',
description: 'Trend of sessions count in over the time.',
slug: TIMESERIES,
subTypes: [{ title: 'Session Count', slug: 'sessionCount', description: '' }],
},
{
title: 'Table',
icon: 'list-alt',
description: 'See list of Users, Sessions, Errors, Issues, etc.,',
slug: TABLE,
subTypes: [
{ title: 'Users', slug: FilterKey.USERID, description: '' },
{ title: 'Sessions', slug: FilterKey.SESSIONS, description: '' },
{ title: 'JS Errors', slug: FilterKey.ERRORS, description: '' },
{ title: 'Issues', slug: FilterKey.ISSUE, description: '' },
{ title: 'Browser', slug: FilterKey.USER_BROWSER, description: '' },
{ title: 'Devices', slug: FilterKey.USER_DEVICE, description: '' },
{ title: 'Countries', slug: FilterKey.USER_COUNTRY, description: '' },
{ title: 'URLs', slug: FilterKey.LOCATION, description: '' },
],
},
{
title: 'Funnel',
icon: 'funnel',
description: 'Uncover the issues impacting user journeys.',
slug: 'funnel',
},
{
title: 'Errors Tracking',
icon: 'exclamation-circle',
description: 'Discover user journeys between 2 points.',
slug: 'errors',
subTypes: [
{ title: 'Resources by Party', slug: FilterKey.RESOURCES_BY_PARTY, description: '' },
{ title: 'Errors per Domains', slug: FilterKey.ERRORS_PER_DOMAINS, description: '' },
{ title: 'Errors per type', slug: FilterKey.ERRORS_PER_TYPE, description: '' },
{ title: 'Calls_Errors', slug: FilterKey.CALLS_ERRORS, description: '' },
{ title: 'Domains_Errors_4xx', slug: FilterKey.DOMAINS_ERRORS_4XX, description: '' },
{ title: 'Domains_Errors_5xx', slug: FilterKey.DOMAINS_ERRORS_5XX, description: '' },
{
title: 'Impacted_Sessions_By_Js_Errors',
slug: FilterKey.IMPACTED_SESSIONS_BY_JS_ERRORS,
description: '',
},
],
},
{
title: 'Performance Monitoring',
icon: 'speedometer2',
description: 'Retention graph of users / features over a period of time.',
slug: 'performance',
subTypes: [
{ title: 'Cpu', slug: FilterKey.CPU, description: '' },
{ title: 'Crashes', slug: FilterKey.CRASHES, description: '' },
{ title: 'Fps', slug: FilterKey.FPS, description: '' },
{ title: 'Pages_Dom_Build_Time', slug: FilterKey.PAGES_DOM_BUILD_TIME, description: '' },
{ title: 'Memory_Consumption', slug: FilterKey.MEMORY_CONSUMPTION, description: '' },
{ title: 'Pages_Response_Time', slug: FilterKey.PAGES_RESPONSE_TIME, description: '' },
{
title: 'Pages_Response_Time_Distribution',
slug: FilterKey.PAGES_RESPONSE_TIME_DISTRIBUTION,
description: '',
},
{
title: 'Resources_Vs_Visually_Complete',
slug: FilterKey.RESOURCES_VS_VISUALLY_COMPLETE,
description: '',
},
{ title: 'Sessions_Per_Browser', slug: FilterKey.SESSIONS_PER_BROWSER, description: '' },
{ title: 'Slowest_Domains', slug: FilterKey.SLOWEST_DOMAINS, description: '' },
{ title: 'Speed_Location', slug: FilterKey.SPEED_LOCATION, description: '' },
{ title: 'Time_To_Render', slug: FilterKey.TIME_TO_RENDER, description: '' },
{
title: 'Impacted_Sessions_By_Slow_Pages',
slug: FilterKey.IMPACTED_SESSIONS_BY_SLOW_PAGES,
description: '',
},
],
},
{
title: 'Resource Monitoring',
icon: 'files',
description: 'Find the adoption of your all features in your app.',
slug: 'resource-monitoring',
subTypes: [
{
title: 'Breakdown_Of_Loaded_Resources',
slug: FilterKey.BREAKDOWN_OF_LOADED_RESOURCES,
description: '',
},
{ title: 'Missing_Resources', slug: FilterKey.MISSING_RESOURCES, description: '' },
{
title: 'Resource_Type_Vs_Response_End',
slug: FilterKey.RESOURCE_TYPE_VS_RESPONSE_END,
description: '',
},
{ title: 'Resource_Fetch_Time', slug: FilterKey.RESOURCE_FETCH_TIME, description: '' },
{ title: 'Slowest_Resources', slug: FilterKey.SLOWEST_RESOURCES, description: '' },
],
},
{
title: 'Web Vitals',
icon: 'activity',
description: 'Find the adoption of your all features in your app.',
slug: 'web-vitals',
subTypes: [
{
title: 'Resources_Count_By_Type',
slug: FilterKey.RESOURCES_COUNT_BY_TYPE,
description: '',
},
{ title: 'Resources_Loading_Time', slug: FilterKey.RESOURCES_LOADING_TIME, description: '' },
{
title: 'CPU Load',
slug: FilterKey.AVG_CPU,
description: 'Uncover the issues impacting user journeys',
},
{
title: 'DOM Build Time',
slug: FilterKey.AVG_DOM_CONTENT_LOADED,
description: 'Keep a close eye on errors and track their type, origin and domain.',
},
{
title: 'DOM Content Loaded Start',
slug: FilterKey.AVG_DOM_CONTENT_LOAD_START,
description:
'FInd out which resources are missing and those that may be slowign your web app.',
},
{
title: 'DOM Content Loaded',
slug: FilterKey.AVG_FIRST_CONTENTFUL_PIXEL,
description:
"Optimize your app's performance by tracking slow domains, page resposne times, memory consumption, CPU usage and more.",
},
{
title: 'First Paint',
slug: FilterKey.AVG_FIRST_PAINT,
description:
'Find out which resources are missing and those that may be slowing your web app.',
},
{ title: 'Frame Rate', slug: FilterKey.AVG_FPS, description: '' },
{
title: 'Image Load Time',
slug: FilterKey.AVG_IMAGE_LOAD_TIME,
description:
'Find out which resources are missing and those that may be slowing your web app.',
},
{ title: 'Page Load Time', slug: FilterKey.AVG_PAGE_LOAD_TIME, description: '' },
{ title: 'DOM Build Time', slug: FilterKey.AVG_PAGES_DOM_BUILD_TIME, description: '' },
{ title: 'Pages Response Time', slug: FilterKey.AVG_PAGES_RESPONSE_TIME, description: '' },
{ title: 'Request Load Time', slug: FilterKey.AVG_REQUEST_LOADT_IME, description: '' },
{ title: 'Response Time ', slug: FilterKey.AVG_RESPONSE_TIME, description: '' },
{ title: 'Session Dueration', slug: FilterKey.AVG_SESSION_DURATION, description: '' },
{ title: 'Time Till First Byte', slug: FilterKey.AVG_TILL_FIRST_BYTE, description: '' },
{ title: 'Time to be Interactive', slug: FilterKey.AVG_TIME_TO_INTERACTIVE, description: '' },
{ title: 'Time to Render', slug: FilterKey.AVG_TIME_TO_RENDER, description: '' },
{ title: 'JS Heap Size', slug: FilterKey.AVG_USED_JS_HEAP_SIZE, description: '' },
{ title: 'Visited Pages', slug: FilterKey.AVG_VISITED_PAGES, description: '' },
{
title: 'Captured Requests',
slug: FilterKey.COUNT_REQUESTS,
description: 'Trend of sessions count in over the time.',
},
{
title: 'Captured Sessions',
slug: FilterKey.COUNT_SESSIONS,
description: 'See list of users, sessions, errors, issues, etc.,',
},
],
},
{
title: 'User Path',
icon: 'signpost-split',
description: 'Discover user journeys between 2 points.',
slug: 'user-path',
},
{
title: 'Retention',
icon: 'arrow-repeat',
description: 'Retension graph of users / features over a period of time.',
slug: 'retention',
},
{
title: 'Feature Adoption',
icon: 'card-checklist',
description: 'Find the adoption of your all features in your app.',
slug: 'feature-adoption',
},
];

View file

@ -277,9 +277,9 @@ export default class DashboardStore {
);
}
getDashboard(dashboardId: string): Dashboard | null {
getDashboard(dashboardId: string|number): Dashboard | null {
return (
this.dashboards.find((d) => d.dashboardId === dashboardId) || null
this.dashboards.find((d) => d.dashboardId == dashboardId) || null
);
}

View file

@ -220,4 +220,58 @@ export enum FilterKey {
SESSIONS = 'SESSIONS',
ERRORS = 'js_exception',
RESOURCES_COUNT_BY_TYPE = 'resourcesCountByType',
RESOURCES_LOADING_TIME = 'resourcesLoadingTime',
AVG_CPU = 'avgCpu',
AVG_DOM_CONTENT_LOADED = 'avgDomContentLoaded',
AVG_DOM_CONTENT_LOAD_START = 'avgDomContentLoadStart',
AVG_FIRST_CONTENTFUL_PIXEL = 'avgFirstContentfulPixel',
AVG_FIRST_PAINT = 'avgFirstPaint',
AVG_FPS = 'avgFps',
AVG_IMAGE_LOAD_TIME = 'avgImageLoadTime',
AVG_PAGE_LOAD_TIME = 'avgPageLoadTime',
AVG_PAGES_DOM_BUILD_TIME = 'avgPagesDomBuildtime',
AVG_PAGES_RESPONSE_TIME = 'avgPagesResponseTime',
AVG_REQUEST_LOADT_IME = 'avgRequestLoadTime',
AVG_RESPONSE_TIME = 'avgResponseTime',
AVG_SESSION_DURATION = 'avgSessionDuration',
AVG_TILL_FIRST_BYTE = 'avgTillFirstByte',
AVG_TIME_TO_INTERACTIVE = 'avgTimeToInteractive',
AVG_TIME_TO_RENDER = 'avgTimeToRender',
AVG_USED_JS_HEAP_SIZE = 'avgUsedJsHeapSize',
AVG_VISITED_PAGES = 'avgVisitedPages',
COUNT_REQUESTS = 'countRequests',
COUNT_SESSIONS = 'countSessions',
// Errors
RESOURCES_BY_PARTY = 'resourcesByParty',
ERRORS_PER_DOMAINS = 'errorsPerDomains',
ERRORS_PER_TYPE = 'errorsPerType',
CALLS_ERRORS = 'callsErrors',
DOMAINS_ERRORS_4XX = 'domainsErrors4Xx',
DOMAINS_ERRORS_5XX = 'domainsErrors5Xx',
IMPACTED_SESSIONS_BY_JS_ERRORS = 'impactedSessionsByJsErrors',
// Performance
CPU = 'cpu',
CRASHES = 'crashes',
FPS = 'fps',
PAGES_DOM_BUILD_TIME = 'pagesDomBuildtime',
MEMORY_CONSUMPTION = 'memoryConsumption',
PAGES_RESPONSE_TIME = 'pagesResponseTime',
PAGES_RESPONSE_TIME_DISTRIBUTION = 'pagesResponseTimeDistribution',
RESOURCES_VS_VISUALLY_COMPLETE = 'resourcesVsVisuallyComplete',
SESSIONS_PER_BROWSER = 'sessionsPerBrowser',
SLOWEST_DOMAINS = 'slowestDomains',
SPEED_LOCATION = 'speedLocation',
TIME_TO_RENDER = 'timeToRender',
IMPACTED_SESSIONS_BY_SLOW_PAGES = 'impactedSessionsBySlowPages',
// Resources
BREAKDOWN_OF_LOADED_RESOURCES = 'breakdownOfLoadedResources',
MISSING_RESOURCES = 'missingResources',
RESOURCE_TYPE_VS_RESPONSE_END = 'resourceTypeVsResponseEnd',
RESOURCE_FETCH_TIME = 'resourceFetchTime',
SLOWEST_RESOURCES = 'slowestResources',
}