fix(ui): change dashboards overview

This commit is contained in:
sylenien 2022-08-10 10:51:49 +02:00 committed by Delirium
parent 0f3f883a27
commit d8129ab8e9
15 changed files with 261 additions and 122 deletions

View file

@ -0,0 +1,73 @@
import { observer } from 'mobx-react-lite';
import React from 'react';
import { NoContent, Pagination } from 'UI';
import { useStore } from 'App/mstore';
import { getRE } from 'App/utils';
import { sliceListPerPage } from 'App/utils';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import DashboardListItem from './DashboardListItem';
const filterList = <T extends Record<string, any>>(list: T[], searchQuery: string): T[] => {
const filterRE = getRE(searchQuery, 'i');
console.log(filterRE)
let _list = list.filter((w: T) => {
console.log(w.name, w.owner, w.description, filterRE.test(w.name))
return filterRE.test(w.name) || filterRE.test(w.owner) || filterRE.test(w.description);
});
return _list
}
function DashboardList() {
const { dashboardStore } = useStore();
const [shownDashboards, setDashboards] = React.useState([]);
const dashboards = dashboardStore.dashboards;
const dashboardsSearch = dashboardStore.dashboardsSearch;
React.useEffect(() => {
setDashboards(filterList(dashboards, dashboardsSearch))
}, [dashboardsSearch])
const list = dashboardsSearch !== '' ? shownDashboards : dashboards;
const lenth = list.length;
return (
<NoContent
show={lenth === 0}
title={
<div className="flex flex-col items-center justify-center">
<AnimatedSVG name={ICONS.NO_RESULTS} size={170} />
<div className="mt-6 text-2xl">No data available.</div>
</div>
}
>
<div className="mt-3 border-b">
<div className="grid grid-cols-12 p-4 font-medium">
<div className="col-span-8">Title</div>
<div className="col-span-2">Visibility</div>
<div className="col-span-2 text-right">Created</div>
</div>
{sliceListPerPage(list, dashboardStore.page - 1, dashboardStore.pageSize).map((dashboard: any) => (
<React.Fragment key={dashboard.dashboardId}>
<DashboardListItem dashboard={dashboard} />
</React.Fragment>
))}
</div>
<div className="w-full flex items-center justify-between pt-4">
<div className="text-disabled-text">
Showing <span className="font-semibold">{Math.min(list.length, dashboardStore.pageSize)}</span> out of <span className="font-semibold">{list.length}</span> Dashboards
</div>
<Pagination
page={dashboardStore.page}
totalPages={Math.ceil(lenth / dashboardStore.pageSize)}
onPageChange={(page) => dashboardStore.updateKey('page', page)}
limit={dashboardStore.pageSize}
debounceRequest={100}
/>
</div>
</NoContent>
);
}
export default observer(DashboardList);

View file

@ -0,0 +1,55 @@
import React from 'react';
import { Icon } from 'UI';
import { connect } from 'react-redux';
import{ IDashboard } from "App/mstore/types/dashboard";
import { checkForRecent } from 'App/date';
import { withSiteId, dashboardSelected } from 'App/routes';
import { useStore } from 'App/mstore';
import { withRouter, RouteComponentProps } from 'react-router-dom';
interface Props extends RouteComponentProps {
dashboard: IDashboard;
siteId: string;
}
function DashboardListItem(props: Props) {
const { dashboard, siteId, history } = props;
const { dashboardStore } = useStore();
const onItemClick = () => {
dashboardStore.selectDashboardById(dashboard.dashboardId);
const path = withSiteId(dashboardSelected(dashboard.dashboardId), siteId);
history.push(path);
};
return (
<>
<div className="grid grid-cols-12 p-4 border-t select-none">
<div className="col-span-8 flex items-start">
<div className="flex items-center link capitalize-first" onClick={onItemClick}>
<div className="w-9 h-9 rounded-full bg-tealx-lightest flex items-center justify-center mr-2">
<Icon name="columns-gap" size="16" color="tealx" />
</div>
<div>
{dashboard.name}
</div>
</div>
</div>
{/* <div><Label className="capitalize">{metric.metricType}</Label></div> */}
<div className="col-span-2">
<div className="flex items-center">
<Icon name={dashboard.isPublic ? "user-friends" : "person-fill"} className="mr-2" />
<span>{dashboard.isPublic ? 'Team' : 'Private'}</span>
</div>
</div>
<div className="col-span-2 text-right">
{checkForRecent(dashboard.createdAt, 'LLL dd, yyyy, hh:mm a')}
</div>
</div>
<div className="text-disabled-text px-4 pb-2">
{dashboard.description}
</div>
</>
);
}
export default connect(state => ({ siteId: state.getIn([ 'site', 'siteId' ]) }))(withRouter(DashboardListItem))

View file

@ -0,0 +1,36 @@
import React, { useEffect, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import { Icon } from 'UI';
import { debounce } from 'App/utils';
let debounceUpdate: any = () => {}
function DashboardSearch() {
const { dashboardStore } = useStore();
const [query, setQuery] = useState(dashboardStore.dashboardsSearch);
useEffect(() => {
debounceUpdate = debounce((key: string, value: any) => dashboardStore.updateKey(key, value), 500);
}, [])
// @ts-ignore
const write = ({ target: { value } }) => {
setQuery(value);
debounceUpdate('dashboardsSearch', value);
}
return (
<div className="relative">
<Icon name="search" className="absolute top-0 bottom-0 ml-2 m-auto" size="16" />
<input
value={query}
name="dashboardsSearch"
className="bg-white p-2 border border-borderColor-gray-light-shade rounded w-full pl-10"
placeholder="Filter by title or description"
onChange={write}
/>
</div>
);
}
export default observer(DashboardSearch);

View file

@ -0,0 +1,28 @@
import React from 'react';
import { Button, PageTitle, Icon, Link } from 'UI';
import withPageTitle from 'HOCs/withPageTitle';
import DashboardList from './DashboardList';
import DashboardSearch from './DashboardSearch';
function DashboardsView() {
return (
<div style={{ maxWidth: '1300px', margin: 'auto'}} className="bg-white rounded p-4">
<div className="flex items-center mb-4 justify-between px-4">
<div className="flex items-baseline mr-3">
<PageTitle title="Dashboards" className="" />
</div>
<Link to={'/metrics/create'}><Button variant="primary">Create Dashboard</Button></Link>
<div className="ml-auto w-1/4">
<DashboardSearch />
</div>
</div>
<div className="text-xl text-disabled-text flex items-center pl-4">
<Icon name="info-circle-fill" className="mr-2" size={18} />
A dashboard is a custom visualization using your OpenReplay data.
</div>
<DashboardList />
</div>
);
}
export default withPageTitle('Dashboards - OpenReplay')(DashboardsView);

View file

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

View file

@ -16,6 +16,7 @@ import DashboardView from '../DashboardView';
import MetricsView from '../MetricsView';
import WidgetView from '../WidgetView';
import WidgetSubDetailsView from '../WidgetSubDetailsView';
import DashboardsView from '../DashboardList';
function DashboardViewSelected({ siteId, dashboardId }) {
return (
@ -44,8 +45,8 @@ function DashboardRouter(props: Props) {
<WidgetSubDetailsView siteId={siteId} {...props} />
</Route>
<Route exact strict path={withSiteId(dashboard(), siteId)}>
<DashboardView siteId={siteId} dashboardId={dashboardId} />
<Route exact path={withSiteId(dashboard(), siteId)}>
<DashboardsView />
</Route>
<Route exact strict path={withSiteId(dashboardMetricDetails(dashboardId), siteId)}>

View file

@ -1,113 +1,40 @@
//@ts-nocheck
import { useObserver } from 'mobx-react-lite';
import React from 'react';
import { SideMenuitem, SideMenuHeader, Icon, Popup, Button } from 'UI';
import { useStore } from 'App/mstore';
import { withRouter } from 'react-router-dom';
import { withSiteId, dashboardSelected, metrics } from 'App/routes';
import { useModal } from 'App/components/Modal';
import DashbaordListModal from '../DashbaordListModal';
import DashboardModal from '../DashboardModal';
import cn from 'classnames';
import { SideMenuitem, SideMenuHeader } from 'UI';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { withSiteId, metrics, dashboard } from 'App/routes';
import { connect } from 'react-redux';
import { compose } from 'redux'
import { setShowAlerts } from 'Duck/dashboard';
// import stl from 'Shared/MainSearchBar/mainSearchBar.module.css';
const SHOW_COUNT = 8;
interface Props {
interface Props extends RouteComponentProps {
siteId: string
history: any
setShowAlerts: (show: boolean) => void
}
function DashboardSideMenu(props: RouteComponentProps<Props>) {
function DashboardSideMenu(props: Props) {
const { history, siteId, setShowAlerts } = props;
const { hideModal, showModal } = useModal();
const { dashboardStore } = useStore();
const dashboardId = useObserver(() => dashboardStore.selectedDashboard?.dashboardId);
const dashboardsPicked = useObserver(() => dashboardStore.dashboards.slice(0, SHOW_COUNT));
const remainingDashboardsCount = dashboardStore.dashboards.length - SHOW_COUNT;
const isMetric = history.location.pathname.includes('metrics');
const isDashboards = history.location.pathname.includes('dashboard');
const redirect = (path) => {
const redirect = (path: string) => {
history.push(path);
}
const onItemClick = (dashboard) => {
dashboardStore.selectDashboardById(dashboard.dashboardId);
const path = withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(siteId));
history.push(path);
};
const onAddDashboardClick = (e) => {
dashboardStore.initDashboard();
showModal(<DashboardModal siteId={siteId} />, { right: true })
}
const togglePinned = (dashboard, e) => {
e.stopPropagation();
dashboardStore.updatePinned(dashboard.dashboardId);
}
return useObserver(() => (
return (
<div>
<SideMenuHeader
className="mb-4 flex items-center"
text="DASHBOARDS"
button={
<Button onClick={onAddDashboardClick} variant="text-primary">
<>
<Icon name="plus" size="16" color="main" />
<span className="ml-1" style={{ textTransform: 'none' }}>Create</span>
</>
</Button>
}
text="Preferences"
/>
{dashboardsPicked.map((item: any) => (
<SideMenuitem
key={ item.dashboardId }
active={item.dashboardId === dashboardId && !isMetric}
title={ item.name }
iconName={ item.icon }
onClick={() => onItemClick(item)}
className="group"
leading = {(
<div className="ml-2 flex items-center cursor-default">
{item.isPublic && (
<Popup delay={500} content="Visible to the team" hideOnClick>
<div className="p-1"><Icon name="user-friends" color="gray-light" size="16" /></div>
</Popup>
)}
{item.isPinned && <div className="p-1 pointer-events-none"><Icon name="pin-fill" size="16" /></div>}
{!item.isPinned && (
<Popup
delay={500}
content="Set as default dashboard"
hideOnClick={true}
>
<div
className={cn("p-1 invisible group-hover:visible cursor-pointer")}
onClick={(e) => togglePinned(item, e)}
>
<Icon name="pin-fill" size="16" color="gray-light" />
</div>
</Popup>
)}
</div>
)}
/>
))}
<div>
{remainingDashboardsCount > 0 && (
<div
className="my-2 py-2 color-teal cursor-pointer"
onClick={() => showModal(<DashbaordListModal siteId={siteId} />, {})}
>
{remainingDashboardsCount} More
</div>
)}
</div>
<div className="w-full">
<SideMenuitem
active={isDashboards}
id="menu-manage-alerts"
title="Dashboards"
iconName="columns-gap"
onClick={() => redirect(withSiteId(dashboard(), siteId))}
/>
</div>
<div className="border-t w-full my-2" />
<div className="w-full">
<SideMenuitem
@ -128,10 +55,10 @@ function DashboardSideMenu(props: RouteComponentProps<Props>) {
/>
</div>
</div>
));
);
}
export default compose(
withRouter,
connect(null, { setShowAlerts }),
)(DashboardSideMenu) as React.FunctionComponent<RouteComponentProps<Props>>
)(DashboardSideMenu)

View file

@ -15,8 +15,10 @@ import withPageTitle from "HOCs/withPageTitle";
import withReport from "App/components/hocs/withReport";
import DashboardOptions from "../DashboardOptions";
import SelectDateRange from "Shared/SelectDateRange";
// @ts-ignore
import DashboardIcon from "../../../../svg/dashboard-icn.svg";
import { Tooltip } from "react-tippy";
import Breadcrumb from 'Shared/Breadcrumb';
interface IProps {
siteId: string;
@ -69,6 +71,10 @@ function DashboardView(props: Props) {
dashboardStore.selectDefaultDashboard();
}, [siteId])
useEffect(() => {
dashboardStore.selectDashboardById(dashboardId);
}, [dashboardId])
const onAddWidgets = () => {
dashboardStore.initDashboard(dashboard);
showModal(
@ -138,15 +144,6 @@ function DashboardView(props: Props) {
</div>
}
size="small"
subtext={
<Button
variant="primary"
size="small"
onClick={onAddDashboardClick}
>
+ Create Dashboard
</Button>
}
>
<div style={{ maxWidth: "1300px", margin: "auto" }}>
<DashboardEditModal
@ -154,6 +151,15 @@ function DashboardView(props: Props) {
closeHandler={() => setShowEditModal(false)}
focusTitle={focusTitle}
/>
<Breadcrumb
items={[
{
label: 'Dashboards',
to: withSiteId('/dashboard', siteId),
},
{ label: dashboard && dashboard.name || '' },
]}
/>
<div className="flex items-center mb-4 justify-between">
<div className="flex items-center" style={{ flex: 3 }}>
<PageTitle

View file

@ -7,33 +7,31 @@ import MetricListItem from '../MetricListItem';
import { sliceListPerPage } from 'App/utils';
import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
interface Props { }
function MetricsList(props: Props) {
function MetricsList() {
const { metricStore } = useStore();
const metrics = useObserver(() => metricStore.metrics);
const metricsSearch = useObserver(() => metricStore.metricsSearch);
const filterList = (list) => {
const filterList = <T extends Record<string, any>>(list: T[]): T[] => {
const filterRE = getRE(metricsSearch, 'i');
let _list = list.filter(w => {
const dashbaordNames = w.dashboards.map(d => d.name).join(' ');
let _list = list.filter((w: T) => {
const dashbaordNames = w.dashboards.map((d: any) => d.name).join(' ');
return filterRE.test(w.name) || filterRE.test(w.metricType) || filterRE.test(w.owner) || filterRE.test(dashbaordNames);
});
return _list
}
const list: any = metricsSearch !== '' ? filterList(metrics) : metrics;
const list = metricsSearch !== '' ? filterList(metrics) : metrics;
const lenth = list.length;
useEffect(() => {
metricStore.updateKey('sessionsPage', 1);
}, [])
console.log(list, list.length)
return useObserver(() => (
<NoContent
show={lenth === 0}
title={
<div className="flex flex-col items-center justify-center">
<AnimatedSVG name={ICONS.NO_RESULTS} size="170" />
<AnimatedSVG name={ICONS.NO_RESULTS} size={170} />
<div className="mt-6 text-2xl">No data available.</div>
</div>
}
@ -41,7 +39,6 @@ function MetricsList(props: Props) {
<div className="mt-3 border-b rounded bg-white">
<div className="grid grid-cols-12 p-4 font-medium">
<div className="col-span-3">Title</div>
{/* <div>Type</div> */}
<div className="col-span-3">Owner</div>
<div className="col-span-4">Visibility</div>
<div className="col-span-2 text-right">Last Modified</div>

View file

@ -12,7 +12,7 @@ function MetricsSearch(props) {
debounceUpdate = debounce((key, value) => metricStore.updateKey(key, value), 500);
}, [])
const write = ({ target: { name, value } }) => {
const write = ({ target: { value } }) => {
setQuery(value);
debounceUpdate('metricsSearch', value);
}

View file

@ -7,7 +7,7 @@ interface Props {
subtext?: any;
icon?: string;
iconSize?: number;
size?: number;
size?: string;
show?: boolean;
children?: any;
image?: any;

View file

@ -7,13 +7,13 @@ function SideMenuitem({
iconBg = false,
iconColor = "gray-dark",
iconSize = 18,
className,
className = '',
iconName = null,
title,
active = false,
disabled = false,
onClick,
deleteHandler,
deleteHandler = null,
leading = null,
...props
}) {

View file

@ -46,6 +46,11 @@ export interface IDashboardSotre {
showAlertModal: boolean;
page: number
pageSize: number
dashboardsSearch: string
sort: any
selectWidgetsByCategory: (category: string) => void;
toggleAllSelectedWidgets: (isSelected: boolean) => void;
removeSelectedWidgetByCategory(category: string): void;
@ -115,6 +120,12 @@ export default class DashboardStore implements IDashboardSotre {
sessionsLoading: boolean = false;
showAlertModal: boolean = false;
// Pagination
page: number = 1
pageSize: number = 15
dashboardsSearch: string = ''
sort: any = {}
constructor() {
makeAutoObservable(this);
@ -183,6 +194,7 @@ export default class DashboardStore implements IDashboardSotre {
}
updateKey(key: any, value: any) {
console.log(key, value)
this[key] = value;
}

View file

@ -2,6 +2,7 @@ import { makeAutoObservable, observable, action, runInAction } from "mobx"
import Widget, { IWidget } from "./widget"
import { dashboardService } from "App/services"
import { toast } from 'react-toastify';
import { DateTime } from 'luxon';
export interface IDashboard {
dashboardId: any
@ -14,6 +15,7 @@ export interface IDashboard {
isPinned: boolean
currentWidget: IWidget
config: any
createdAt: Date
update(data: any): void
toJson(): any
@ -44,6 +46,7 @@ export default class Dashboard implements IDashboard {
isPinned: boolean = false
currentWidget: IWidget = new Widget()
config: any = {}
createdAt: Date = new Date()
constructor() {
makeAutoObservable(this)
@ -63,8 +66,7 @@ export default class Dashboard implements IDashboard {
dashboardId: this.dashboardId,
name: this.name,
isPublic: this.isPublic,
// widgets: this.widgets.map(w => w.toJson())
// widgets: this.widgets
createdAt: this.createdAt,
metrics: this.metrics,
description: this.description,
}
@ -77,7 +79,8 @@ export default class Dashboard implements IDashboard {
this.description = json.description
this.isPublic = json.isPublic
this.isPinned = json.isPinned
this.widgets = json.widgets ? json.widgets.map(w => new Widget().fromJson(w)).sort((a, b) => a.position - b.position) : []
this.createdAt = DateTime.fromMillis(new Date(json.createdAt).getTime())
this.widgets = json.widgets ? json.widgets.map((w: Widget) => new Widget().fromJson(w)).sort((a: Widget, b: Widget) => a.position - b.position) : []
})
return this
}

View file

@ -239,10 +239,10 @@ export const isGreaterOrEqualVersion = (version, compareTo) => {
return major > majorC || (major === majorC && minor > minorC) || (major === majorC && minor === minorC && patch >= patchC);
};
export const sliceListPerPage = (list, page, perPage = 10) => {
export const sliceListPerPage = <T extends Array<any>>(list: T, page: number, perPage = 10): T => {
const start = page * perPage;
const end = start + perPage;
return list.slice(start, end);
return list.slice(start, end) as T;
};
export const positionOfTheNumber = (min, max, value, length) => {