feat(ui) - dashboard - wip
This commit is contained in:
parent
1549c554a7
commit
f4f543ec60
32 changed files with 471 additions and 903 deletions
|
|
@ -36,6 +36,8 @@ import { OB_DEFAULT_TAB } from 'App/routes';
|
|||
import Signup from './components/Signup/Signup';
|
||||
import { fetchTenants } from 'Duck/user';
|
||||
import { setSessionPath } from 'Duck/sessions';
|
||||
import { ModalProvider } from './components/Modal';
|
||||
import ModalRoot from './components/Modal/ModalRoot';
|
||||
|
||||
const BugFinder = withSiteIdUpdater(BugFinderPure);
|
||||
const Dashboard = withSiteIdUpdater(DashboardPure);
|
||||
|
|
@ -169,6 +171,8 @@ class Router extends React.Component {
|
|||
<Notification />
|
||||
|
||||
<Suspense fallback={<Loader loading={true} className="flex-1" />}>
|
||||
<ModalProvider>
|
||||
<ModalRoot />
|
||||
<Switch key="content" >
|
||||
<Route path={ CLIENT_PATH } component={ Client } />
|
||||
<Route path={ withSiteId(ONBOARDING_PATH, siteIdList)} component={ Onboarding } />
|
||||
|
|
@ -225,6 +229,7 @@ class Router extends React.Component {
|
|||
)) }
|
||||
<Redirect to={ withSiteId(SESSIONS_PATH, siteId) } />
|
||||
</Switch>
|
||||
</ModalProvider>
|
||||
</Suspense>
|
||||
</Loader>
|
||||
:
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ function NewDashboard(props) {
|
|||
|
||||
useEffect(() => {
|
||||
dashboardStore.fetchList().then((resp) => {
|
||||
if (dashboardId) {
|
||||
if (parseInt(dashboardId) > 0) {
|
||||
dashboardStore.selectDashboardById(dashboardId);
|
||||
} else {
|
||||
dashboardStore.selectDefaultDashboard().then((b) => {
|
||||
dashboardStore.selectDefaultDashboard().then(({ dashboardId }) => {
|
||||
if (!history.location.pathname.includes('/metrics')) {
|
||||
history.push(withSiteId(dashboardSelected(b.dashboardId), siteId));
|
||||
history.push(withSiteId(dashboardSelected(dashboardId), siteId));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,45 @@
|
|||
import React from 'react';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { SideMenuitem, SideMenuHeader, Icon, Button } from 'UI';
|
||||
import { withSiteId, dashboardSelected, metrics } from 'App/routes';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
|
||||
function DashbaordListModal(props) {
|
||||
interface Props {
|
||||
siteId: string
|
||||
history: any
|
||||
}
|
||||
function DashbaordListModal(props: Props) {
|
||||
const { dashboardStore } = useStore();
|
||||
const { hideModal } = useModal();
|
||||
const dashboards = dashboardStore.dashboards;
|
||||
const activeDashboardId = dashboardStore.selectedDashboard?.dashboardId;
|
||||
|
||||
const onItemClick = (dashboard) => {
|
||||
dashboardStore.selectDashboardById(dashboard.dashboardId);
|
||||
const path = withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(props.siteId));
|
||||
props.history.push(path);
|
||||
hideModal();
|
||||
};
|
||||
return (
|
||||
<div className="bg-white h-screen" style={{ width: '300px'}}>
|
||||
<div className="color-gray-medium uppercase p-4 text-lg">Dashboards</div>
|
||||
<div>
|
||||
{dashboards.map((item: any) => (
|
||||
<div key={ item.dashboardId } className="px-4">
|
||||
<SideMenuitem
|
||||
key={ item.dashboardId }
|
||||
active={item.dashboardId === activeDashboardId}
|
||||
title={ item.name }
|
||||
iconName={ item.icon }
|
||||
// onClick={() => onItemClick(item)} // TODO add click handler
|
||||
leading = {(
|
||||
<div className="ml-2 flex items-center">
|
||||
{item.isPublic && <div className="p-1"><Icon name="user-friends" color="gray-light" size="16" /></div>}
|
||||
{item.isPinned && <div className="p-1"><Icon name="pin-fill" size="16" /></div>}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<SideMenuitem
|
||||
key={ item.dashboardId }
|
||||
active={item.dashboardId === activeDashboardId}
|
||||
title={ item.name }
|
||||
iconName={ item.icon }
|
||||
onClick={() => onItemClick(item)} // TODO add click handler
|
||||
leading = {(
|
||||
<div className="ml-2 flex items-center">
|
||||
{item.isPublic && <div className="p-1"><Icon name="user-friends" color="gray-light" size="16" /></div>}
|
||||
{item.isPinned && <div className="p-1"><Icon name="pin-fill" size="16" /></div>}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -32,4 +47,4 @@ function DashbaordListModal(props) {
|
|||
);
|
||||
}
|
||||
|
||||
export default DashbaordListModal;
|
||||
export default withRouter(DashbaordListModal);
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import WidgetWrapper from '../WidgetWrapper';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
import cn from 'classnames';
|
||||
|
|
@ -6,14 +6,14 @@ import { useStore } from 'App/mstore';
|
|||
|
||||
function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds, unSelectCategory }) {
|
||||
const selectedCategoryWidgetsCount = useObserver(() => {
|
||||
return category.widgets.filter(widget => selectedWidgetIds.includes(widget.widgetId)).length;
|
||||
return category.widgets.filter(widget => selectedWidgetIds.includes(widget.metricId)).length;
|
||||
});
|
||||
return (
|
||||
<div
|
||||
className={cn("rounded p-4 shadow border cursor-pointer", { 'bg-active-blue border-color-teal':isSelected, 'bg-white': !isSelected })}
|
||||
onClick={() => onClick(category)}
|
||||
>
|
||||
<div className="font-medium text-lg mb-2">{category.name}</div>
|
||||
<div className="font-medium text-lg mb-2 capitalize">{category.name}</div>
|
||||
<div className="mb-2">{category.description}</div>
|
||||
{selectedCategoryWidgetsCount > 0 && (
|
||||
<div className="flex items-center">
|
||||
|
|
@ -27,40 +27,22 @@ function WidgetCategoryItem({ category, isSelected, onClick, selectedWidgetIds,
|
|||
|
||||
function DashboardMetricSelection(props) {
|
||||
const { dashboardStore } = useStore();
|
||||
const widgetCategories = dashboardStore?.widgetCategories;
|
||||
const [activeCategory, setActiveCategory] = React.useState<any>(widgetCategories[0]);
|
||||
const [selectedWidgets, setSelectedWidgets] = React.useState<any>([]);
|
||||
const selectedWidgetIds = selectedWidgets.map((widget: any) => widget.widgetId);
|
||||
let widgetCategories: any[] = useObserver(() => dashboardStore.widgetCategories);
|
||||
const [activeCategory, setActiveCategory] = React.useState<any>();
|
||||
const selectedWidgetIds = useObserver(() => dashboardStore.selectedWidgets.map((widget: any) => widget.metricId));
|
||||
|
||||
const removeSelectedWidgetByCategory = (category: any) => {
|
||||
const categoryWidgetIds = category.widgets.map((widget: any) => widget.widgetId);
|
||||
const newSelectedWidgets = selectedWidgets.filter((widget: any) => !categoryWidgetIds.includes(widget.widgetId));
|
||||
setSelectedWidgets(newSelectedWidgets);
|
||||
};
|
||||
|
||||
const toggleWidgetSelection = (widget: any) => {
|
||||
console.log('toggleWidgetSelection', widget.widgetId);
|
||||
if (selectedWidgetIds.includes(widget.widgetId)) {
|
||||
setSelectedWidgets(selectedWidgets.filter((w: any) => w.widgetId !== widget.widgetId));
|
||||
} else {
|
||||
setSelectedWidgets(selectedWidgets.concat(widget));
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
dashboardStore?.fetchTemplates().then(templates => {
|
||||
setActiveCategory(dashboardStore.widgetCategories[0]);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleWidgetCategoryClick = (category: any) => {
|
||||
setActiveCategory(category);
|
||||
};
|
||||
|
||||
const toggleAllWidgets = ({ target: { checked }}) => {
|
||||
if (checked == true) {
|
||||
const allWidgets = widgetCategories.reduce((acc, category) => {
|
||||
return acc.concat(category.widgets);
|
||||
}, []);
|
||||
|
||||
setSelectedWidgets(allWidgets);
|
||||
} else {
|
||||
setSelectedWidgets([]);
|
||||
}
|
||||
dashboardStore.toggleAllSelectedWidgets(checked);
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
|
|
@ -74,7 +56,7 @@ function DashboardMetricSelection(props) {
|
|||
{activeCategory && (
|
||||
<>
|
||||
<div className="flex items-baseline">
|
||||
<h2 className="text-2xl">{activeCategory.name}</h2>
|
||||
<h2 className="text-2xl capitalize">{activeCategory.name}</h2>
|
||||
<span className="text-2xl color-gray-medium ml-2">{activeCategory.widgets.length}</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -92,14 +74,14 @@ function DashboardMetricSelection(props) {
|
|||
<div className="grid grid-cols-12 gap-4">
|
||||
<div className="col-span-3">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{widgetCategories.map((category, index) =>
|
||||
{activeCategory && widgetCategories.map((category, index) =>
|
||||
<WidgetCategoryItem
|
||||
key={category.categoryId}
|
||||
key={category.name}
|
||||
onClick={handleWidgetCategoryClick}
|
||||
category={category}
|
||||
isSelected={activeCategory.categoryId === category.categoryId}
|
||||
isSelected={activeCategory.name === category.name}
|
||||
selectedWidgetIds={selectedWidgetIds}
|
||||
unSelectCategory={removeSelectedWidgetByCategory}
|
||||
unSelectCategory={dashboardStore.removeSelectedWidgetByCategory}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -108,9 +90,9 @@ function DashboardMetricSelection(props) {
|
|||
<div className="grid grid-cols-2 gap-4 -mx-4 px-4 lg:grid-cols-2 sm:grid-cols-1">
|
||||
{activeCategory && activeCategory.widgets.map((widget: any) => (
|
||||
<div
|
||||
key={widget.widgetId}
|
||||
className={cn("border rounded cursor-pointer", { 'border-color-teal' : selectedWidgetIds.includes(widget.widgetId) })}
|
||||
onClick={() => toggleWidgetSelection(widget)}
|
||||
key={widget.metricId}
|
||||
className={cn("border rounded cursor-pointer", { 'border-color-teal' : selectedWidgetIds.includes(widget.metricId) })}
|
||||
onClick={() => dashboardStore.toggleWidgetSelection(widget)}
|
||||
>
|
||||
<WidgetWrapper widget={widget} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,11 +10,15 @@ import { useModal } from 'App/components/Modal';
|
|||
function DashboardModal(props) {
|
||||
const { dashboardStore } = useStore();
|
||||
const { hideModal } = useModal();
|
||||
const dashbaord = useObserver(() => dashboardStore.dashboardInstance);
|
||||
const dashboard = useObserver(() => dashboardStore.dashboardInstance);
|
||||
const loading = useObserver(() => dashboardStore.isSaving);
|
||||
|
||||
const onSave = () => {
|
||||
dashboardStore.save(dashbaord).then(hideModal)
|
||||
dashboardStore.save(dashboard).then(hideModal)
|
||||
}
|
||||
|
||||
const handleCreateNew = () => {
|
||||
hideModal();
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
|
|
@ -22,21 +26,32 @@ function DashboardModal(props) {
|
|||
className="fixed border-r shadow p-4 h-screen"
|
||||
style={{ backgroundColor: '#FAFAFA', zIndex: '9999', width: '80%'}}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<h1 className="text-2xl">Create Dashboard</h1>
|
||||
<div className="mb-6 flex items-end justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl">
|
||||
{ dashboard.exists() ? "Add metric(s) to dashboard" : "Create Dashboard" }
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<Button outline size="small" onClick={handleCreateNew}>Create New</Button>
|
||||
</div>
|
||||
</div>
|
||||
<DashboardForm />
|
||||
<p>Create new dashboard by choosing from the range of predefined metrics that you care about. You can always add your custom metrics later.</p>
|
||||
{ !dashboard.exists() && (
|
||||
<>
|
||||
<DashboardForm />
|
||||
<p>Create new dashboard by choosing from the range of predefined metrics that you care about. You can always add your custom metrics later.</p>
|
||||
</>
|
||||
)}
|
||||
<DashboardMetricSelection />
|
||||
|
||||
<div className="flex absolute bottom-0 left-0 right-0 bg-white border-t p-3">
|
||||
<Button
|
||||
primary
|
||||
className=""
|
||||
disabled={!dashbaord.isValid || loading}
|
||||
disabled={!dashboard.isValid || loading}
|
||||
onClick={onSave}
|
||||
>
|
||||
Add Selected to Dashboard
|
||||
{ dashboard.exists() ? "Add Selected to Dashboard" : "Create and Add to Dashboard" }
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
dashboardSelected,
|
||||
dashboardMetricCreate,
|
||||
withSiteId,
|
||||
dashboard,
|
||||
} from 'App/routes';
|
||||
import DashboardView from '../DashboardView';
|
||||
import MetricsView from '../MetricsView';
|
||||
|
|
@ -34,8 +35,12 @@ function DashboardRouter(props: Props) {
|
|||
<WidgetView siteId={siteId} {...props} />
|
||||
</Route>
|
||||
|
||||
<Route exact strict path={withSiteId(dashboard(''), siteId)}>
|
||||
<>Nothing...</>
|
||||
</Route>
|
||||
|
||||
<Route exact strict path={withSiteId(dashboardSelected(dashboardId), siteId)}>
|
||||
<DashboardView siteId={siteId} />
|
||||
<DashboardView siteId={siteId} dashboardId={dashboardId} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ function DashboardSideMenu(props: Props) {
|
|||
const { hideModal, showModal } = useModal();
|
||||
const { dashboardStore } = useStore();
|
||||
const dashboardId = dashboardStore.selectedDashboard?.dashboardId;
|
||||
const dashboardsPicked = dashboardStore.dashboards.slice(0, SHOW_COUNT);
|
||||
const dashboardsPicked = useObserver(() => dashboardStore.dashboards.slice(0, SHOW_COUNT));
|
||||
const remainingDashboardsCount = dashboardStore.dashboards.length - SHOW_COUNT;
|
||||
|
||||
const redirect = (path) => {
|
||||
|
|
@ -58,7 +58,7 @@ function DashboardSideMenu(props: Props) {
|
|||
{remainingDashboardsCount > 0 && (
|
||||
<div
|
||||
className="my-2 py-2 color-teal cursor-pointer"
|
||||
onClick={() => showModal(<DashbaordListModal />, {})}
|
||||
onClick={() => showModal(<DashbaordListModal siteId={siteId} />, {})}
|
||||
>
|
||||
{remainingDashboardsCount} More
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,74 @@
|
|||
import React from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { useEffect } from 'react';
|
||||
import { observer, useObserver } from 'mobx-react-lite';
|
||||
import { useStore } from 'App/mstore';
|
||||
import { Button, PageTitle, Link } from 'UI';
|
||||
import { withSiteId, dashboardMetricCreate } from 'App/routes';
|
||||
import { Button, PageTitle, Link, Loader, NoContent } from 'UI';
|
||||
import { withSiteId, dashboardMetricCreate, dashboardSelected, dashboard } from 'App/routes';
|
||||
import withModal from 'App/components/Modal/withModal';
|
||||
import DashboardWidgetGrid from '../DashboardWidgetGrid';
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import DashboardModal from '../DashboardModal';
|
||||
|
||||
interface Props {
|
||||
siteId: number;
|
||||
history: any
|
||||
match: any
|
||||
dashboardId: any
|
||||
}
|
||||
function DashboardView(props: Props) {
|
||||
const { siteId } = props;
|
||||
const { siteId, dashboardId } = props;
|
||||
const { dashboardStore } = useStore();
|
||||
const { hideModal, showModal } = useModal();
|
||||
const loading = useObserver(() => dashboardStore.fetchingDashboard);
|
||||
const dashboard: any = dashboardStore.selectedDashboard
|
||||
|
||||
useEffect(() => {
|
||||
dashboardStore.fetch(dashboardId)
|
||||
}, []);
|
||||
|
||||
const onEditHandler = () => {
|
||||
dashboardStore.initDashboard(dashboard)
|
||||
showModal(<DashboardModal />, {})
|
||||
}
|
||||
|
||||
const onDelete = async () => {
|
||||
if (await confirm({
|
||||
header: 'Confirm',
|
||||
confirmButton: 'Yes, Delete',
|
||||
confirmation: `Are you sure you want to permanently delete this Dashboard?`
|
||||
})) {
|
||||
dashboardStore.deleteDashboard(dashboard).then(() => {
|
||||
dashboardStore.selectDefaultDashboard().then(({ dashboardId }) => {
|
||||
props.history.push(withSiteId(dashboard(), siteId));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center mb-4 justify-between">
|
||||
<div className="flex items-center">
|
||||
<PageTitle title={dashboard.name} className="mr-3" />
|
||||
<Link to={withSiteId(dashboardMetricCreate(dashboard.dashboardId), siteId)}><Button primary size="small">Add Metric</Button></Link>
|
||||
</div>
|
||||
<Loader loading={loading}>
|
||||
<NoContent
|
||||
show={!dashboard || !dashboard.dashboardId}
|
||||
title="No data available."
|
||||
size="small"
|
||||
>
|
||||
<div>
|
||||
Right
|
||||
<div className="flex items-center mb-4 justify-between">
|
||||
<div className="flex items-center">
|
||||
<PageTitle title={dashboard?.name} className="mr-3" />
|
||||
{/* <Link to={withSiteId(dashboardMetricCreate(dashboard?.dashboardId), siteId)}><Button primary size="small">Add Metric</Button></Link> */}
|
||||
<Button primary size="small" onClick={onEditHandler}>Add Metric</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button onClick={onDelete}>Remove</Button>
|
||||
</div>
|
||||
</div>
|
||||
<DashboardWidgetGrid />
|
||||
</div>
|
||||
</div>
|
||||
<DashboardWidgetGrid />
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
)
|
||||
}
|
||||
|
||||
export default withModal(observer(DashboardView));
|
||||
export default withRouter(withModal(observer(DashboardView)));
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import FilterList from 'Shared/Filters/FilterList';
|
||||
import {
|
||||
edit,
|
||||
|
|
@ -29,13 +29,16 @@ interface Props {
|
|||
removeSeriesFilterFilter: typeof removeSeriesFilterFilter;
|
||||
hideHeader?: boolean;
|
||||
emptyMessage?: any;
|
||||
observeChanges?: () => void;
|
||||
}
|
||||
|
||||
function FilterSeries(props: Props) {
|
||||
const { canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props;
|
||||
const { observeChanges = () => {}, canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props;
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const { series, seriesIndex } = props;
|
||||
|
||||
useEffect(observeChanges, [series])
|
||||
|
||||
const onAddFilter = (filter) => {
|
||||
series.filter.addFilter(filter)
|
||||
}
|
||||
|
|
@ -58,7 +61,7 @@ function FilterSeries(props: Props) {
|
|||
<div className="border rounded bg-white">
|
||||
<div className={cn("border-b px-5 h-12 flex items-center relative", { 'hidden': hideHeader })}>
|
||||
<div className="mr-auto">
|
||||
<SeriesName seriesIndex={seriesIndex} name={series.name} onUpdate={(name) => props.updateSeries(seriesIndex, { name }) } />
|
||||
<SeriesName seriesIndex={seriesIndex} name={series.name} onUpdate={(name) => series.update('name', name) } />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center cursor-pointer">
|
||||
|
|
@ -80,6 +83,7 @@ function FilterSeries(props: Props) {
|
|||
onUpdateFilter={onUpdateFilter}
|
||||
onRemoveFilter={onRemoveFilter}
|
||||
onChangeEventsOrder={onChangeEventsOrder}
|
||||
observeChanges={observeChanges}
|
||||
/>
|
||||
): (
|
||||
<div className="color-gray-medium">{emptyMessage}</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Icon, NoContent, Label, Link, Pagination } from 'UI';
|
||||
import { checkForRecent, formatDateTimeDefault, convertTimestampToUtcTimestamp } from 'App/date';
|
||||
|
||||
interface Props {
|
||||
metric: any;
|
||||
|
|
@ -30,17 +31,15 @@ function MetricListItem(props: Props) {
|
|||
<div><Label className="capitalize">{metric.metricType}</Label></div>
|
||||
<div>
|
||||
<DashboardLink dashboards={metric.dashboards} />
|
||||
{/* <div className="link flex items-center">
|
||||
<div className="mr-2 text-4xl">·</div>
|
||||
<span>Dashboards</span>
|
||||
</div> */}
|
||||
</div>
|
||||
<div>{metric.owner}</div>
|
||||
{/* <div className="flex items-center">
|
||||
<Icon name={metric.isPublic ? "user-friends" : "person-fill"} className="mr-2" />
|
||||
<span>{metric.isPublic ? 'Team' : 'Private'}</span>
|
||||
</div> */}
|
||||
<div>Last Modified</div>
|
||||
<div>
|
||||
<div className="flex items-center">
|
||||
<Icon name={metric.isPublic ? "user-friends" : "person-fill"} className="mr-2" />
|
||||
<span>{metric.isPublic ? 'Team' : 'Private'}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ function MetricsList(props: Props) {
|
|||
<div>Type</div>
|
||||
<div>Dashboards</div>
|
||||
<div>Owner</div>
|
||||
{/* <div>Visibility & Edit Access</div> */}
|
||||
<div>Visibility & Edit Access</div>
|
||||
<div>Last Modified</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,110 @@
|
|||
import React from 'react';
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Period, { LAST_24_HOURS, LAST_30_MINUTES, YESTERDAY, LAST_7_DAYS } from 'Types/app/period';
|
||||
import CustomMetriLineChart from '../../Widgets/CustomMetricsWidgets/CustomMetriLineChart';
|
||||
import CustomMetricPercentage from '../../Widgets/CustomMetricsWidgets/CustomMetricPercentage';
|
||||
import CustomMetricTable from '../../Widgets/CustomMetricsWidgets/CustomMetricTable';
|
||||
import CustomMetricPieChart from '../../Widgets/CustomMetricsWidgets/CustomMetricPieChart';
|
||||
import APIClient from 'App/api_client';
|
||||
import { Styles } from '../../Widgets/common';
|
||||
import { getChartFormatter } from 'Types/dashboard/helper';
|
||||
import { observer, useObserver } from 'mobx-react-lite';
|
||||
import { Loader } from 'UI';
|
||||
import { useStore } from 'App/mstore';
|
||||
interface Props {
|
||||
metric: any;
|
||||
// metric: any;
|
||||
}
|
||||
function WidgetChart(props: Props) {
|
||||
const { metric } = props;
|
||||
// const metric = useObserver(() => props.metric);
|
||||
const { metricStore } = useStore();
|
||||
const metric: any = useObserver(() => metricStore.instance);
|
||||
const series = useObserver(() => metric.series);
|
||||
const colors = Styles.customMetricColors;
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState<any>({ chart: [{}] })
|
||||
const [seriesMap, setSeriesMap] = useState<any>([]);
|
||||
const [period, setPeriod] = useState(Period({ rangeName: metric.rangeName, startDate: metric.startDate, endDate: metric.endDate }));
|
||||
const params = { density: 70 }
|
||||
const metricParams = { ...params, metricId: metric.metricId, viewType: 'lineChart' }
|
||||
const prevMetricRef = useRef<any>();
|
||||
|
||||
useEffect(() => {
|
||||
// Check for title change
|
||||
if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) {
|
||||
prevMetricRef.current = metric;
|
||||
return
|
||||
};
|
||||
prevMetricRef.current = metric;
|
||||
setLoading(true);
|
||||
|
||||
// fetch new data for the widget preview
|
||||
new APIClient()['post']('/custom_metrics/try', { ...metricParams, ...metric.toJson() })
|
||||
.then(response => response.json())
|
||||
.then(({ errors, data }) => {
|
||||
if (errors) {
|
||||
console.log('err', errors)
|
||||
} else {
|
||||
const namesMap = data
|
||||
.map(i => Object.keys(i))
|
||||
.flat()
|
||||
.filter(i => i !== 'time' && i !== 'timestamp')
|
||||
.reduce((unique: any, item: any) => {
|
||||
if (!unique.includes(item)) {
|
||||
unique.push(item);
|
||||
}
|
||||
return unique;
|
||||
}, []);
|
||||
|
||||
setSeriesMap(namesMap);
|
||||
setData(getChartFormatter(period)(data));
|
||||
}
|
||||
}).finally(() => setLoading(false));
|
||||
}, [metric.data]);
|
||||
|
||||
const renderChart = () => {
|
||||
const { metricType } = metric;
|
||||
const { metricType, viewType } = metric;
|
||||
if (metricType === 'timeseries') {
|
||||
return <div>Chart</div>;
|
||||
if (viewType === 'lineChart') {
|
||||
return (
|
||||
<CustomMetriLineChart
|
||||
data={data}
|
||||
seriesMap={seriesMap}
|
||||
colors={colors}
|
||||
params={params}
|
||||
/>
|
||||
)
|
||||
} else if (viewType === 'progress') {
|
||||
return (
|
||||
<CustomMetricPercentage
|
||||
data={data[0]}
|
||||
colors={colors}
|
||||
params={params}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (metricType === 'table') {
|
||||
return <div>Table</div>;
|
||||
if (viewType === 'table') {
|
||||
return <CustomMetricTable metric={metric} data={data[0]} />;
|
||||
} else if (viewType === 'pieChart') {
|
||||
return (
|
||||
<CustomMetricPieChart
|
||||
metric={metric}
|
||||
data={data[0]}
|
||||
colors={colors}
|
||||
params={params}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return <div>Unknown</div>;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Loader loading={loading}>
|
||||
{renderChart()}
|
||||
</div>
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
||||
export default WidgetChart;
|
||||
export default observer(WidgetChart);
|
||||
|
|
@ -17,9 +17,8 @@ interface Props {
|
|||
|
||||
function WidgetForm(props: Props) {
|
||||
const { history, match: { params: { siteId, dashboardId, metricId } } } = props;
|
||||
console.log('WidgetForm params', props.match.params);
|
||||
const { metricStore } = useStore();
|
||||
const metric: any = metricStore.instance;
|
||||
const metric: any = useObserver(() => metricStore.instance);
|
||||
|
||||
const timeseriesOptions = metricOf.filter(i => i.type === 'timeseries');
|
||||
const tableOptions = metricOf.filter(i => i.type === 'table');
|
||||
|
|
@ -31,23 +30,23 @@ function WidgetForm(props: Props) {
|
|||
const writeOption = (e, { value, name }) => {
|
||||
metricStore.merge({ [ name ]: value });
|
||||
|
||||
if (name === 'metricValue') {
|
||||
metricStore.merge({ metricValue: [value] });
|
||||
}
|
||||
|
||||
if (name === 'metricOf') {
|
||||
if (value === FilterKey.ISSUE) {
|
||||
metricStore.merge({ metricValue: ['all'] });
|
||||
if (name === 'metricValue') {
|
||||
metricStore.merge({ metricValue: [value] });
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'metricType') {
|
||||
if (value === 'timeseries') {
|
||||
metricStore.merge({ metricOf: timeseriesOptions[0].value, viewType: 'lineChart' });
|
||||
} else if (value === 'table') {
|
||||
metricStore.merge({ metricOf: tableOptions[0].value, viewType: 'table' });
|
||||
|
||||
if (name === 'metricOf') {
|
||||
if (value === FilterKey.ISSUE) {
|
||||
metricStore.merge({ metricValue: ['all'] });
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'metricType') {
|
||||
if (value === 'timeseries') {
|
||||
metricStore.merge({ metricOf: timeseriesOptions[0].value, viewType: 'lineChart' });
|
||||
} else if (value === 'table') {
|
||||
metricStore.merge({ metricOf: tableOptions[0].value, viewType: 'table' });
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
|
|
@ -61,11 +60,13 @@ function WidgetForm(props: Props) {
|
|||
confirmation: `Are you sure you want to permanently delete this metric?`
|
||||
})) {
|
||||
metricStore.delete(metric).then(props.onDelete);
|
||||
// props.remove(instance.alertId).then(() => {
|
||||
// toggleForm(null, false);
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
const onObserveChanges = () => {
|
||||
console.log('observe changes');
|
||||
// metricStore.fetchMetricChartData(metric);
|
||||
}
|
||||
|
||||
return useObserver(() => (
|
||||
<div className="p-4">
|
||||
|
|
@ -156,6 +157,7 @@ function WidgetForm(props: Props) {
|
|||
'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.'
|
||||
}
|
||||
observeChanges={onObserveChanges}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ interface Props {
|
|||
function WidgetPreview(props: Props) {
|
||||
const { className = '' } = props;
|
||||
const { metricStore } = useStore();
|
||||
const metric: any = metricStore.instance;
|
||||
const metric: any = useObserver(() => metricStore.instance);
|
||||
const isTimeSeries = metric.metricType === 'timeseries';
|
||||
const isTable = metric.metricType === 'table';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import React, { useRef } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import cn from 'classnames';
|
||||
import { ItemMenu } from 'UI';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import WidgetChart from '../WidgetChart';
|
||||
import { useObserver } from 'mobx-react-lite';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
|
|
@ -12,7 +13,7 @@ interface Props {
|
|||
isPreview?: boolean;
|
||||
}
|
||||
function WidgetWrapper(props: Props) {
|
||||
const { widget = {}, index = 0, moveListItem = null, isPreview = false } = props;
|
||||
const { widget, index = 0, moveListItem = null, isPreview = false } = props;
|
||||
|
||||
const [{ opacity, isDragging }, dragRef] = useDrag({
|
||||
type: 'item',
|
||||
|
|
@ -21,7 +22,6 @@ function WidgetWrapper(props: Props) {
|
|||
isDragging: monitor.isDragging(),
|
||||
opacity: monitor.isDragging() ? 0.5 : 1,
|
||||
}),
|
||||
|
||||
});
|
||||
|
||||
const [{ isOver, canDrop }, dropRef] = useDrop({
|
||||
|
|
@ -40,7 +40,7 @@ function WidgetWrapper(props: Props) {
|
|||
const ref: any = useRef(null)
|
||||
const dragDropRef: any = dragRef(dropRef(ref))
|
||||
|
||||
return (
|
||||
return useObserver(() => (
|
||||
<div
|
||||
className={cn("rounded bg-white", 'col-span-' + widget.colSpan, { 'border' : !isPreview })}
|
||||
style={{
|
||||
|
|
@ -69,12 +69,12 @@ function WidgetWrapper(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-40">
|
||||
<WidgetChart metric={props.widget} />
|
||||
<div className="">
|
||||
<WidgetChart />
|
||||
</div>
|
||||
{/* </Link> */}
|
||||
</div>
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
export default WidgetWrapper;
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
import { makeAutoObservable, observable, action, runInAction } from "mobx"
|
||||
import Widget, { IWidget } from "./widget"
|
||||
import { dashboardService } from 'App/services'
|
||||
|
||||
export interface IDashboard {
|
||||
dashboardId: any
|
||||
name: string
|
||||
isPublic: boolean
|
||||
widgets: IWidget[]
|
||||
isValid: boolean
|
||||
isPinned: boolean
|
||||
currentWidget: IWidget
|
||||
|
||||
update(data: any): void
|
||||
toJson(): any
|
||||
fromJson(json: any): void
|
||||
validate(): void
|
||||
addWidget(widget: IWidget): void
|
||||
removeWidget(widgetId: string): void
|
||||
updateWidget(widget: IWidget): void
|
||||
getWidget(widgetId: string): void
|
||||
getWidgetIndex(widgetId: string)
|
||||
getWidgetByIndex(index: number): void
|
||||
getWidgetCount(): void
|
||||
getWidgetIndexByWidgetId(widgetId: string): void
|
||||
swapWidgetPosition(positionA: number, positionB: number): void
|
||||
sortWidgets(): void
|
||||
}
|
||||
export default class Dashboard implements IDashboard {
|
||||
dashboardId: any = undefined
|
||||
name: string = "New Dashboard"
|
||||
isPublic: boolean = false
|
||||
widgets: IWidget[] = []
|
||||
isValid: boolean = false
|
||||
isPinned: boolean = false
|
||||
currentWidget: IWidget = new Widget()
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
name: observable,
|
||||
isPublic: observable,
|
||||
widgets: observable,
|
||||
isValid: observable,
|
||||
|
||||
toJson: action,
|
||||
fromJson: action,
|
||||
addWidget: action,
|
||||
removeWidget: action,
|
||||
updateWidget: action,
|
||||
getWidget: action,
|
||||
getWidgetIndex: action,
|
||||
getWidgetByIndex: action,
|
||||
getWidgetCount: action,
|
||||
getWidgetIndexByWidgetId: action,
|
||||
validate: action,
|
||||
sortWidgets: action,
|
||||
swapWidgetPosition: action,
|
||||
update: action,
|
||||
})
|
||||
|
||||
this.validate();
|
||||
}
|
||||
|
||||
update(data: any) {
|
||||
runInAction(() => {
|
||||
Object.assign(this, data)
|
||||
})
|
||||
this.validate()
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
dashboardId: this.dashboardId,
|
||||
name: this.name,
|
||||
isPrivate: this.isPublic,
|
||||
widgets: this.widgets.map(w => w.toJson())
|
||||
}
|
||||
}
|
||||
|
||||
fromJson(json: any) {
|
||||
runInAction(() => {
|
||||
this.dashboardId = json.dashboardId
|
||||
this.name = json.name
|
||||
this.isPublic = json.isPublic
|
||||
this.isPinned = json.isPinned
|
||||
this.widgets = json.widgets ? json.widgets.map(w => new Widget().fromJson(w)) : []
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
validate() {
|
||||
return this.isValid = this.name.length > 0
|
||||
}
|
||||
|
||||
addWidget(widget: IWidget) {
|
||||
this.widgets.push(widget)
|
||||
}
|
||||
|
||||
removeWidget(widgetId: string) {
|
||||
this.widgets = this.widgets.filter(w => w.widgetId !== widgetId)
|
||||
}
|
||||
|
||||
updateWidget(widget: IWidget) {
|
||||
const index = this.widgets.findIndex(w => w.widgetId === widget.widgetId)
|
||||
if (index >= 0) {
|
||||
this.widgets[index] = widget
|
||||
}
|
||||
}
|
||||
|
||||
getWidget(widgetId: string) {
|
||||
return this.widgets.find(w => w.widgetId === widgetId)
|
||||
}
|
||||
|
||||
getWidgetIndex(widgetId: string) {
|
||||
return this.widgets.findIndex(w => w.widgetId === widgetId)
|
||||
}
|
||||
|
||||
getWidgetByIndex(index: number) {
|
||||
return this.widgets[index]
|
||||
}
|
||||
|
||||
getWidgetCount() {
|
||||
return this.widgets.length
|
||||
}
|
||||
|
||||
getWidgetIndexByWidgetId(widgetId: string) {
|
||||
return this.widgets.findIndex(w => w.widgetId === widgetId)
|
||||
}
|
||||
|
||||
swapWidgetPosition(positionA, positionB) {
|
||||
console.log('swapWidgetPosition', positionA, positionB)
|
||||
const widgetA = this.widgets[positionA]
|
||||
const widgetB = this.widgets[positionB]
|
||||
this.widgets[positionA] = widgetB
|
||||
this.widgets[positionB] = widgetA
|
||||
|
||||
widgetA.position = positionB
|
||||
widgetB.position = positionA
|
||||
}
|
||||
|
||||
sortWidgets() {
|
||||
this.widgets = this.widgets.sort((a, b) => {
|
||||
if (a.position > b.position) {
|
||||
return 1
|
||||
} else if (a.position < b.position) {
|
||||
return -1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,362 +0,0 @@
|
|||
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
|
||||
import Dashboard, { IDashboard } from "./dashboard"
|
||||
import APIClient from 'App/api_client';
|
||||
import Widget, { IWidget } from "./widget";
|
||||
import { dashboardService } from "App/services";
|
||||
|
||||
export interface IDashboardSotre {
|
||||
dashboards: IDashboard[]
|
||||
widgetTemplates: any[]
|
||||
selectedDashboard: IDashboard | null
|
||||
dashboardInstance: IDashboard
|
||||
siteId: any
|
||||
currentWidget: Widget
|
||||
widgetCategories: any[]
|
||||
widgets: Widget[]
|
||||
metricsPage: number
|
||||
metricsPageSize: number
|
||||
metricsSearch: string
|
||||
|
||||
isLoading: boolean
|
||||
isSaving: boolean
|
||||
|
||||
initDashboard(dashboard?: IDashboard): void
|
||||
updateKey(key: string, value: any): void
|
||||
resetCurrentWidget(): void
|
||||
editWidget(widget: any): void
|
||||
fetchList(): void
|
||||
fetch(dashboardId: string)
|
||||
save(dashboard: IDashboard): Promise<any>
|
||||
saveDashboardWidget(dashboard: Dashboard, widget: Widget)
|
||||
delete(dashboard: IDashboard)
|
||||
toJson(): void
|
||||
fromJson(json: any): void
|
||||
initDashboard(dashboard: IDashboard): void
|
||||
addDashboard(dashboard: IDashboard): void
|
||||
removeDashboard(dashboard: IDashboard): void
|
||||
getDashboard(dashboardId: string): void
|
||||
getDashboardIndex(dashboardId: string): number
|
||||
getDashboardCount(): void
|
||||
getDashboardIndexByDashboardId(dashboardId: string): number
|
||||
updateDashboard(dashboard: IDashboard): void
|
||||
selectDashboardById(dashboardId: string): void
|
||||
setSiteId(siteId: any): void
|
||||
selectDefaultDashboard(): Promise<IDashboard>
|
||||
|
||||
saveMetric(metric: IWidget, dashboardId?: string): Promise<any>
|
||||
}
|
||||
export default class DashboardStore implements IDashboardSotre {
|
||||
siteId: any = null
|
||||
// Dashbaord / Widgets
|
||||
dashboards: Dashboard[] = []
|
||||
widgetTemplates: any[] = []
|
||||
selectedDashboard: Dashboard | null = new Dashboard()
|
||||
dashboardInstance: IDashboard = new Dashboard()
|
||||
currentWidget: Widget = new Widget()
|
||||
widgetCategories: any[] = []
|
||||
widgets: Widget[] = []
|
||||
|
||||
// Metrics
|
||||
metricsPage: number = 1
|
||||
metricsPageSize: number = 10
|
||||
metricsSearch: string = ''
|
||||
|
||||
// Loading states
|
||||
isLoading: boolean = false
|
||||
isSaving: boolean = false
|
||||
|
||||
private client = new APIClient()
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
resetCurrentWidget: action,
|
||||
addDashboard: action,
|
||||
removeDashboard: action,
|
||||
updateDashboard: action,
|
||||
getDashboard: action,
|
||||
getDashboardIndex: action,
|
||||
getDashboardByIndex: action,
|
||||
getDashboardCount: action,
|
||||
getDashboardIndexByDashboardId: action,
|
||||
selectDashboardById: action,
|
||||
selectDefaultDashboard: action,
|
||||
toJson: action,
|
||||
fromJson: action,
|
||||
setSiteId: action,
|
||||
editWidget: action,
|
||||
updateKey: action,
|
||||
})
|
||||
|
||||
|
||||
// TODO remove this sample data
|
||||
// this.dashboards = sampleDashboards
|
||||
|
||||
// for (let i = 0; i < 15; i++) {
|
||||
// const widget: any= {};
|
||||
// widget.widgetId = `${i}`
|
||||
// widget.name = `Widget ${i}`;
|
||||
// widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)];
|
||||
// this.widgets.push(widget)
|
||||
// }
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const cat: any = {
|
||||
name: `Category ${i + 1}`,
|
||||
categoryId: i,
|
||||
description: `Category ${i + 1} description`,
|
||||
widgets: []
|
||||
}
|
||||
|
||||
const randomNumber = Math.floor(Math.random() * (5 - 2 + 1)) + 2
|
||||
for (let j = 0; j < randomNumber; j++) {
|
||||
const widget: any= {};
|
||||
widget.widgetId = `${i}-${j}`
|
||||
widget.name = `Widget ${i}-${j}`;
|
||||
widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)];
|
||||
cat.widgets.push(widget);
|
||||
}
|
||||
|
||||
this.widgetCategories.push(cat)
|
||||
}
|
||||
}
|
||||
|
||||
initDashboard(dashboard: Dashboard) {
|
||||
this.dashboardInstance = dashboard || new Dashboard()
|
||||
}
|
||||
|
||||
updateKey(key: any, value: any) {
|
||||
this[key] = value
|
||||
}
|
||||
|
||||
resetCurrentWidget() {
|
||||
this.currentWidget = new Widget()
|
||||
}
|
||||
|
||||
editWidget(widget: any) {
|
||||
this.currentWidget.update(widget)
|
||||
}
|
||||
|
||||
fetchList() {
|
||||
this.isLoading = true
|
||||
|
||||
dashboardService.getDashboards()
|
||||
.then((list: any) => {
|
||||
runInAction(() => {
|
||||
this.dashboards = list.map(d => new Dashboard().fromJson(d))
|
||||
})
|
||||
}).finally(() => {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fetch(dashboardId: string) {
|
||||
this.isLoading = true
|
||||
dashboardService.getDashboard(dashboardId).then(response => {
|
||||
runInAction(() => {
|
||||
this.selectedDashboard = new Dashboard().fromJson(response)
|
||||
})
|
||||
}).finally(() => {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
save(dashboard: IDashboard): Promise<any> {
|
||||
this.isSaving = true
|
||||
const isCreating = !dashboard.dashboardId
|
||||
return dashboardService.saveDashboard(dashboard).then(_dashboard => {
|
||||
runInAction(() => {
|
||||
if (isCreating) {
|
||||
this.addDashboard(_dashboard)
|
||||
} else {
|
||||
this.updateDashboard(_dashboard)
|
||||
}
|
||||
})
|
||||
}).finally(() => {
|
||||
runInAction(() => {
|
||||
this.isSaving = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
saveMetric(metric: IWidget, dashboardId: string): Promise<any> {
|
||||
const isCreating = !metric.widgetId
|
||||
return dashboardService.saveMetric(metric, dashboardId).then(metric => {
|
||||
runInAction(() => {
|
||||
if (isCreating) {
|
||||
this.selectedDashboard?.widgets.push(metric)
|
||||
} else {
|
||||
this.selectedDashboard?.widgets.map(w => {
|
||||
if (w.widgetId === metric.widgetId) {
|
||||
w.update(metric)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
saveDashboardWidget(dashboard: Dashboard, widget: Widget) {
|
||||
widget.validate()
|
||||
if (widget.isValid) {
|
||||
this.isLoading = true
|
||||
if (widget.widgetId) {
|
||||
this.client.put(`/dashboards/${dashboard.dashboardId}/widgets/${widget.widgetId}`, widget.toJson())
|
||||
.then(response => {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
)
|
||||
} else {
|
||||
this.client.post(`/dashboards/${dashboard.dashboardId}/widgets`, widget.toJson())
|
||||
.then(response => {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(dashboard: Dashboard) {
|
||||
this.isLoading = true
|
||||
this.client.delete(`/dashboards/${dashboard.dashboardId}`)
|
||||
.then(response => {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
dashboards: this.dashboards.map(d => d.toJson())
|
||||
}
|
||||
}
|
||||
|
||||
fromJson(json: any) {
|
||||
runInAction(() => {
|
||||
this.dashboards = json.dashboards.map(d => new Dashboard().fromJson(d))
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
addDashboard(dashboard: Dashboard) {
|
||||
this.dashboards.push(dashboard)
|
||||
}
|
||||
|
||||
removeDashboard(dashboard: Dashboard) {
|
||||
this.dashboards = this.dashboards.filter(d => d !== dashboard)
|
||||
}
|
||||
|
||||
getDashboard(dashboardId: string) {
|
||||
return this.dashboards.find(d => d.dashboardId === dashboardId)
|
||||
}
|
||||
|
||||
getDashboardIndex(dashboardId: string) {
|
||||
return this.dashboards.findIndex(d => d.dashboardId === dashboardId)
|
||||
}
|
||||
|
||||
getDashboardByIndex(index: number) {
|
||||
return this.dashboards[index]
|
||||
}
|
||||
|
||||
getDashboardCount() {
|
||||
return this.dashboards.length
|
||||
}
|
||||
|
||||
getDashboardIndexByDashboardId(dashboardId: string) {
|
||||
return this.dashboards.findIndex(d => d.dashboardId === dashboardId)
|
||||
}
|
||||
|
||||
updateDashboard(dashboard: Dashboard) {
|
||||
const index = this.dashboards.findIndex(d => d.dashboardId === dashboard.dashboardId)
|
||||
if (index >= 0) {
|
||||
this.dashboards[index] = dashboard
|
||||
}
|
||||
}
|
||||
|
||||
selectDashboardById = (dashboardId: any) => {
|
||||
this.selectedDashboard = this.dashboards.find(d => d.dashboardId == dashboardId) || new Dashboard();
|
||||
if (this.selectedDashboard.dashboardId) {
|
||||
this.fetch(this.selectedDashboard.dashboardId)
|
||||
}
|
||||
}
|
||||
|
||||
setSiteId = (siteId: any) => {
|
||||
this.siteId = siteId
|
||||
}
|
||||
|
||||
selectDefaultDashboard = (): Promise<Dashboard> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.dashboards.length > 0) {
|
||||
const pinnedDashboard = this.dashboards.find(d => d.isPinned)
|
||||
if (pinnedDashboard) {
|
||||
this.selectedDashboard = pinnedDashboard
|
||||
} else {
|
||||
this.selectedDashboard = this.dashboards[0]
|
||||
}
|
||||
}
|
||||
if (this.selectedDashboard) {
|
||||
resolve(this.selectedDashboard)
|
||||
}
|
||||
|
||||
reject(new Error("No dashboards found"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getRandomWidget() {
|
||||
const widget = new Widget();
|
||||
widget.widgetId = Math.floor(Math.random() * 100);
|
||||
widget.name = randomMetricName();
|
||||
// widget.type = "random";
|
||||
widget.colSpan = Math.floor(Math.random() * 2) + 1;
|
||||
return widget;
|
||||
}
|
||||
|
||||
function generateRandomPlaceName() {
|
||||
const placeNames = [
|
||||
"New York",
|
||||
"Los Angeles",
|
||||
"Chicago",
|
||||
"Houston",
|
||||
"Philadelphia",
|
||||
"Phoenix",
|
||||
"San Antonio",
|
||||
"San Diego",
|
||||
]
|
||||
return placeNames[Math.floor(Math.random() * placeNames.length)]
|
||||
}
|
||||
|
||||
|
||||
function randomMetricName () {
|
||||
const metrics = ["Revenue", "Profit", "Expenses", "Sales", "Orders", "Revenue", "Profit", "Expenses", "Sales", "Orders", "Revenue", "Profit", "Expenses", "Sales", "Orders", "Revenue", "Profit", "Expenses", "Sales", "Orders"];
|
||||
return metrics[Math.floor(Math.random() * metrics.length)];
|
||||
}
|
||||
|
||||
function getRandomDashboard(id: any = null, isPinned = false) {
|
||||
const dashboard = new Dashboard();
|
||||
dashboard.name = generateRandomPlaceName();
|
||||
dashboard.dashboardId = id ? id : Math.floor(Math.random() * 10);
|
||||
dashboard.isPinned = isPinned;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const widget = getRandomWidget();
|
||||
widget.position = i;
|
||||
dashboard.addWidget(widget);
|
||||
}
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
const sampleDashboards = [
|
||||
getRandomDashboard(1, true),
|
||||
getRandomDashboard(2),
|
||||
getRandomDashboard(3),
|
||||
getRandomDashboard(4),
|
||||
]
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
|
||||
|
||||
export default class Filter {
|
||||
name: string = ''
|
||||
filters: any[] = []
|
||||
eventsOrder: string = 'then'
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
addFilter: action,
|
||||
removeFilter: action,
|
||||
updateKey: action,
|
||||
})
|
||||
}
|
||||
|
||||
addFilter(filter: any) {
|
||||
filter.value = [""]
|
||||
if (filter.hasOwnProperty('filters')) {
|
||||
filter.filters = filter.filters.map(i => ({ ...i, value: [""] }))
|
||||
}
|
||||
this.filters.push(filter)
|
||||
console.log('addFilter', this.filters)
|
||||
}
|
||||
|
||||
updateFilter(index: number, filter: any) {
|
||||
this.filters[index] = filter
|
||||
console.log('updateFilter', this.filters)
|
||||
}
|
||||
|
||||
updateKey(key, value) {
|
||||
this[key] = value
|
||||
}
|
||||
|
||||
removeFilter(index: number) {
|
||||
this.filters.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// import Filter from 'Types/filter';
|
||||
import Filter from './filter'
|
||||
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
|
||||
|
||||
export default class FilterSeries {
|
||||
seriesId?: any = undefined
|
||||
name: string = "Series 1"
|
||||
filter: Filter = new Filter()
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
name: observable,
|
||||
filter: observable,
|
||||
|
||||
update: action,
|
||||
})
|
||||
}
|
||||
|
||||
update(key, value) {
|
||||
this[key] = value
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default as DashboardStore } from './dashboardStore';
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
|
||||
import Filter from 'Types/filter';
|
||||
import FilterSeries from "./filterSeries";
|
||||
|
||||
export interface IWidget {
|
||||
widgetId: any
|
||||
name: string
|
||||
metricType: string
|
||||
metricOf: string
|
||||
metricValue: string
|
||||
viewType: string
|
||||
series: FilterSeries[]
|
||||
sessions: []
|
||||
isPublic: boolean
|
||||
owner: string
|
||||
lastModified: Date
|
||||
dashboardIds: any[]
|
||||
|
||||
position: number
|
||||
data: any
|
||||
isLoading: boolean
|
||||
isValid: boolean
|
||||
dashboardId: any
|
||||
colSpan: number
|
||||
|
||||
udpateKey(key: string, value: any): void
|
||||
removeSeries(index: number): void
|
||||
addSeries(): void
|
||||
fromJson(json: any): void
|
||||
toJson(): any
|
||||
validate(): void
|
||||
update(data: any): void
|
||||
}
|
||||
export default class Widget implements IWidget {
|
||||
public static get ID_KEY():string { return "widgetId" }
|
||||
widgetId: any = undefined
|
||||
name: string = "New Metric"
|
||||
metricType: string = "timeseries"
|
||||
metricOf: string = "sessionCount"
|
||||
metricValue: string = ""
|
||||
viewType: string = "lineChart"
|
||||
series: FilterSeries[] = []
|
||||
sessions: [] = []
|
||||
isPublic: boolean = false
|
||||
owner: string = ""
|
||||
lastModified: Date = new Date()
|
||||
dashboardIds: any[] = []
|
||||
|
||||
position: number = 0
|
||||
data: any = {}
|
||||
isLoading: boolean = false
|
||||
isValid: boolean = false
|
||||
dashboardId: any = undefined
|
||||
colSpan: number = 2
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
widgetId: observable,
|
||||
name: observable,
|
||||
metricType: observable,
|
||||
metricOf: observable,
|
||||
position: observable,
|
||||
data: observable,
|
||||
isLoading: observable,
|
||||
isValid: observable,
|
||||
dashboardId: observable,
|
||||
addSeries: action,
|
||||
colSpan: observable,
|
||||
|
||||
fromJson: action,
|
||||
toJson: action,
|
||||
validate: action,
|
||||
update: action,
|
||||
udpateKey: action,
|
||||
})
|
||||
|
||||
const filterSeries = new FilterSeries()
|
||||
this.series.push(filterSeries)
|
||||
}
|
||||
|
||||
udpateKey(key: string, value: any) {
|
||||
this[key] = value
|
||||
}
|
||||
|
||||
removeSeries(index: number) {
|
||||
this.series.splice(index, 1)
|
||||
}
|
||||
|
||||
addSeries() {
|
||||
const series = new FilterSeries()
|
||||
series.name = "Series " + (this.series.length + 1)
|
||||
this.series.push(series)
|
||||
}
|
||||
|
||||
|
||||
fromJson(json: any) {
|
||||
runInAction(() => {
|
||||
this.widgetId = json.widgetId
|
||||
this.name = json.name
|
||||
this.data = json.data
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
widgetId: this.widgetId,
|
||||
name: this.name,
|
||||
metricOf: this.metricOf,
|
||||
metricValue: this.metricValue,
|
||||
viewType: this.viewType,
|
||||
series: this.series,
|
||||
sessions: this.sessions,
|
||||
isPublic: this.isPublic,
|
||||
}
|
||||
}
|
||||
|
||||
validate() {
|
||||
this.isValid = this.name.length > 0
|
||||
}
|
||||
|
||||
update(data: any) {
|
||||
runInAction(() => {
|
||||
Object.assign(this, data)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState} from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import FilterItem from '../FilterItem';
|
||||
import { SegmentSelection, Popup } from 'UI';
|
||||
import { List } from 'immutable';
|
||||
|
|
@ -11,14 +11,17 @@ interface Props {
|
|||
onRemoveFilter: (filterIndex) => void;
|
||||
onChangeEventsOrder: (e, { name, value }) => void;
|
||||
hideEventsOrder?: boolean;
|
||||
observeChanges?: () => void;
|
||||
}
|
||||
function FilterList(props: Props) {
|
||||
const { filter, hideEventsOrder = false } = props;
|
||||
const { observeChanges = () => {}, filter, hideEventsOrder = false } = props;
|
||||
const filters = List(filter.filters);
|
||||
const hasEvents = filters.filter((i: any) => i.isEvent).size > 0;
|
||||
const hasFilters = filters.filter((i: any) => !i.isEvent).size > 0;
|
||||
let rowIndex = 0;
|
||||
|
||||
useEffect(observeChanges, [filters]);
|
||||
|
||||
const onRemoveFilter = (filterIndex) => {
|
||||
props.onRemoveFilter(filterIndex);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ interface Props {
|
|||
}
|
||||
function FilterValue(props: Props) {
|
||||
const { filter } = props;
|
||||
const [durationValues, setDurationValues] = useState({ minDuration: filter.value[0], maxDuration: filter.value[1] });
|
||||
const [durationValues, setDurationValues] = useState({ minDuration: filter.value[0], maxDuration: filter.value.length > 1 ? filter.value[1] : filter.value[0] });
|
||||
const showCloseButton = filter.value.length > 1;
|
||||
const lastIndex = filter.value.length - 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
<Provider store={ store }>
|
||||
<StoreProvider store={new RootStore()}>
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<ModalProvider>
|
||||
<ModalRoot />
|
||||
{/* <ModalProvider> */}
|
||||
{/* <ModalRoot /> */}
|
||||
<Router />
|
||||
</ModalProvider>
|
||||
{/* </ModalProvider> */}
|
||||
</DndProvider>
|
||||
</StoreProvider>
|
||||
</Provider>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
|
||||
import Dashboard, { IDashboard } from "./types/dashboard"
|
||||
import Widget, { IWidget } from "./types/widget";
|
||||
import { dashboardService } from "App/services";
|
||||
import { dashboardService, metricService } from "App/services";
|
||||
|
||||
export interface IDashboardSotre {
|
||||
dashboards: IDashboard[]
|
||||
widgetTemplates: any[]
|
||||
selectedDashboard: IDashboard | null
|
||||
dashboardInstance: IDashboard
|
||||
selectedWidgets: IWidget[]
|
||||
|
||||
siteId: any
|
||||
currentWidget: Widget
|
||||
widgetCategories: any[]
|
||||
|
|
@ -18,16 +19,22 @@ export interface IDashboardSotre {
|
|||
|
||||
isLoading: boolean
|
||||
isSaving: boolean
|
||||
isDeleting: boolean
|
||||
fetchingDashboard: boolean
|
||||
|
||||
toggleAllSelectedWidgets: (isSelected: boolean) => void
|
||||
removeSelectedWidgetByCategory(category: string): void
|
||||
toggleWidgetSelection(widget: IWidget): void
|
||||
|
||||
initDashboard(dashboard?: IDashboard): void
|
||||
updateKey(key: string, value: any): void
|
||||
resetCurrentWidget(): void
|
||||
editWidget(widget: any): void
|
||||
fetchList(): Promise<any>
|
||||
fetch(dashboardId: string)
|
||||
fetch(dashboardId: string): Promise<any>
|
||||
save(dashboard: IDashboard): Promise<any>
|
||||
saveDashboardWidget(dashboard: Dashboard, widget: Widget)
|
||||
delete(dashboard: IDashboard)
|
||||
deleteDashboard(dashboard: IDashboard): Promise<any>
|
||||
toJson(): void
|
||||
fromJson(json: any): void
|
||||
initDashboard(dashboard: IDashboard): void
|
||||
|
|
@ -43,14 +50,15 @@ export interface IDashboardSotre {
|
|||
selectDefaultDashboard(): Promise<IDashboard>
|
||||
|
||||
saveMetric(metric: IWidget, dashboardId?: string): Promise<any>
|
||||
fetchTemplates(): Promise<any>
|
||||
}
|
||||
export default class DashboardStore implements IDashboardSotre {
|
||||
siteId: any = null
|
||||
// Dashbaord / Widgets
|
||||
dashboards: Dashboard[] = []
|
||||
widgetTemplates: any[] = []
|
||||
selectedDashboard: Dashboard | null = new Dashboard()
|
||||
selectedDashboard: Dashboard | null = null
|
||||
dashboardInstance: IDashboard = new Dashboard()
|
||||
selectedWidgets: IWidget[] = [];
|
||||
currentWidget: Widget = new Widget()
|
||||
widgetCategories: any[] = []
|
||||
widgets: Widget[] = []
|
||||
|
|
@ -63,9 +71,13 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
// Loading states
|
||||
isLoading: boolean = true
|
||||
isSaving: boolean = false
|
||||
isDeleting: boolean = false
|
||||
fetchingDashboard: boolean = false
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
widgetCategories: observable.ref,
|
||||
|
||||
resetCurrentWidget: action,
|
||||
addDashboard: action,
|
||||
removeDashboard: action,
|
||||
|
|
@ -82,41 +94,65 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
setSiteId: action,
|
||||
editWidget: action,
|
||||
updateKey: action,
|
||||
|
||||
toggleAllSelectedWidgets: action,
|
||||
removeSelectedWidgetByCategory: action,
|
||||
toggleWidgetSelection: action,
|
||||
fetchTemplates: action,
|
||||
})
|
||||
|
||||
|
||||
// TODO remove this sample data
|
||||
// this.dashboards = sampleDashboards
|
||||
|
||||
// for (let i = 0; i < 15; i++) {
|
||||
// const widget: any= {};
|
||||
// widget.widgetId = `${i}`
|
||||
// widget.name = `Widget ${i}`;
|
||||
// widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)];
|
||||
// this.widgets.push(widget)
|
||||
// }
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const cat: any = {
|
||||
name: `Category ${i + 1}`,
|
||||
categoryId: i,
|
||||
description: `Category ${i + 1} description`,
|
||||
widgets: []
|
||||
}
|
||||
// for (let i = 0; i < 4; i++) {
|
||||
// const cat: any = {
|
||||
// name: `Category ${i + 1}`,
|
||||
// categoryId: i,
|
||||
// description: `Category ${i + 1} description`,
|
||||
// widgets: []
|
||||
// }
|
||||
|
||||
const randomNumber = Math.floor(Math.random() * (5 - 2 + 1)) + 2
|
||||
for (let j = 0; j < randomNumber; j++) {
|
||||
const widget: any= {};
|
||||
widget.widgetId = `${i}-${j}`
|
||||
widget.name = `Widget ${i}-${j}`;
|
||||
widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)];
|
||||
cat.widgets.push(widget);
|
||||
}
|
||||
// const randomNumber = Math.floor(Math.random() * (5 - 2 + 1)) + 2
|
||||
// for (let j = 0; j < randomNumber; j++) {
|
||||
// const widget: any= new Widget();
|
||||
// widget.widgetId = `${i}-${j}`
|
||||
// widget.viewType = 'lineChart'
|
||||
// widget.name = `Widget ${i}-${j}`;
|
||||
// // widget.metricType = ['timeseries', 'table'][Math.floor(Math.random() * 2)];
|
||||
// widget.metricType = 'timeseries';
|
||||
// cat.widgets.push(widget);
|
||||
// }
|
||||
|
||||
this.widgetCategories.push(cat)
|
||||
// this.widgetCategories.push(cat)
|
||||
// }
|
||||
}
|
||||
|
||||
toggleAllSelectedWidgets(isSelected: boolean) {
|
||||
if (isSelected) {
|
||||
const allWidgets = this.widgetCategories.reduce((acc, cat) => {
|
||||
return acc.concat(cat.widgets)
|
||||
}, [])
|
||||
|
||||
this.selectedWidgets = allWidgets
|
||||
} else {
|
||||
this.selectedWidgets = []
|
||||
}
|
||||
}
|
||||
|
||||
removeSelectedWidgetByCategory = (category: any) => {
|
||||
const categoryWidgetIds = category.widgets.map(w => w.metricId)
|
||||
this.selectedWidgets = this.selectedWidgets.filter((widget: any) => !categoryWidgetIds.includes(widget.metricId));
|
||||
}
|
||||
|
||||
toggleWidgetSelection = (widget: any) => {
|
||||
const selectedWidgetIds = this.selectedWidgets.map((widget: any) => widget.metricId);
|
||||
if (selectedWidgetIds.includes(widget.metricId)) {
|
||||
this.selectedWidgets = this.selectedWidgets.filter((w: any) => w.metricId !== widget.metricId);
|
||||
} else {
|
||||
this.selectedWidgets.push(widget);
|
||||
}
|
||||
};
|
||||
|
||||
findByIds(ids: string[]) {
|
||||
return this.dashboards.filter(d => ids.includes(d.dashboardId))
|
||||
}
|
||||
|
|
@ -152,22 +188,23 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
})
|
||||
}
|
||||
|
||||
fetch(dashboardId: string) {
|
||||
this.isLoading = true
|
||||
dashboardService.getDashboard(dashboardId).then(response => {
|
||||
runInAction(() => {
|
||||
this.selectedDashboard = new Dashboard().fromJson(response)
|
||||
})
|
||||
fetch(dashboardId: string): Promise<any> {
|
||||
this.fetchingDashboard = true
|
||||
return dashboardService.getDashboard(dashboardId).then(response => {
|
||||
this.selectedDashboard = new Dashboard().fromJson(response)
|
||||
}).finally(() => {
|
||||
runInAction(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
this.fetchingDashboard = false
|
||||
})
|
||||
}
|
||||
|
||||
save(dashboard: IDashboard): Promise<any> {
|
||||
this.isSaving = true
|
||||
const isCreating = !dashboard.dashboardId
|
||||
|
||||
if (isCreating) {
|
||||
dashboard.metrics = this.selectedWidgets.map(w => w.metricId)
|
||||
}
|
||||
|
||||
return dashboardService.saveDashboard(dashboard).then(_dashboard => {
|
||||
runInAction(() => {
|
||||
if (isCreating) {
|
||||
|
|
@ -207,8 +244,17 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
}
|
||||
}
|
||||
|
||||
delete(dashboard: Dashboard) {
|
||||
this.isLoading = true
|
||||
deleteDashboard(dashboard: Dashboard): Promise<any> {
|
||||
this.isDeleting = true
|
||||
return dashboardService.deleteDashboard(dashboard.dashboardId).then(() => {
|
||||
runInAction(() => {
|
||||
this.removeDashboard(dashboard)
|
||||
})
|
||||
}).finally(() => {
|
||||
runInAction(() => {
|
||||
this.isDeleting = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
toJson() {
|
||||
|
|
@ -229,7 +275,7 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
}
|
||||
|
||||
removeDashboard(dashboard: Dashboard) {
|
||||
this.dashboards = this.dashboards.filter(d => d !== dashboard)
|
||||
this.dashboards = this.dashboards.filter(d => d.dashboardId !== dashboard.dashboardId)
|
||||
}
|
||||
|
||||
getDashboard(dashboardId: string) {
|
||||
|
|
@ -275,18 +321,43 @@ export default class DashboardStore implements IDashboardSotre {
|
|||
if (this.dashboards.length > 0) {
|
||||
const pinnedDashboard = this.dashboards.find(d => d.isPinned)
|
||||
if (pinnedDashboard) {
|
||||
console.log('selecting pined dashboard')
|
||||
this.selectedDashboard = pinnedDashboard
|
||||
} else {
|
||||
console.log('selecting first dashboard')
|
||||
this.selectedDashboard = this.dashboards[0]
|
||||
}
|
||||
}
|
||||
if (this.selectedDashboard) {
|
||||
resolve(this.selectedDashboard)
|
||||
}
|
||||
// reject(new Error("No dashboards found"))
|
||||
})
|
||||
}
|
||||
|
||||
reject(new Error("No dashboards found"))
|
||||
fetchTemplates(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.widgetCategories.length > 0) {
|
||||
resolve(this.widgetCategories)
|
||||
} else {
|
||||
metricService.getTemplates().then(response => {
|
||||
const categories: any[] = []
|
||||
response.forEach(category => {
|
||||
const widgets: any[] = []
|
||||
category.widgets.forEach(widget => {
|
||||
const w = new Widget().fromJson(widget)
|
||||
widgets.push(w)
|
||||
})
|
||||
const c: any = {}
|
||||
c.widgets = widgets
|
||||
c.name = category.category
|
||||
c.description = category.description
|
||||
categories.push(c)
|
||||
})
|
||||
this.widgetCategories = categories
|
||||
resolve(this.widgetCategories)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export interface IMetricStore {
|
|||
fetchList(): void
|
||||
fetch(metricId: string)
|
||||
delete(metric: IWidget)
|
||||
// fetchMetricChartData(metric: IWidget)
|
||||
}
|
||||
|
||||
export default class MetricStore implements IMetricStore {
|
||||
|
|
@ -69,7 +70,6 @@ export default class MetricStore implements IMetricStore {
|
|||
fetch: action,
|
||||
delete: action,
|
||||
|
||||
|
||||
paginatedList: computed,
|
||||
})
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ export default class MetricStore implements IMetricStore {
|
|||
|
||||
// State Actions
|
||||
init(metric?: IWidget|null) {
|
||||
this.instance = metric || new Widget()
|
||||
this.instance.update(metric || new Widget())
|
||||
}
|
||||
|
||||
updateKey(key: string, value: any) {
|
||||
|
|
@ -149,7 +149,7 @@ export default class MetricStore implements IMetricStore {
|
|||
this.isLoading = true
|
||||
return metricService.getMetrics()
|
||||
.then(metrics => {
|
||||
this.metrics = metrics
|
||||
this.metrics = metrics.map(m => new Widget().fromJson(m))
|
||||
}).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
|
|
@ -174,4 +174,17 @@ export default class MetricStore implements IMetricStore {
|
|||
this.isSaving = false
|
||||
})
|
||||
}
|
||||
|
||||
fetchMetricChartData(metric: IWidget) {
|
||||
this.isLoading = true
|
||||
return metricService.getMetricChartData(metric)
|
||||
.then(data => {
|
||||
console.log('data', data)
|
||||
// runInAction(() => {
|
||||
// metric.data = data
|
||||
// })
|
||||
}).finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ export interface IDashboard {
|
|||
name: string
|
||||
isPublic: boolean
|
||||
widgets: IWidget[]
|
||||
metrics: any[]
|
||||
isValid: boolean
|
||||
isPinned: boolean
|
||||
currentWidget: IWidget
|
||||
|
|
@ -24,13 +25,15 @@ export interface IDashboard {
|
|||
getWidgetIndexByWidgetId(widgetId: string): void
|
||||
swapWidgetPosition(positionA: number, positionB: number): void
|
||||
sortWidgets(): void
|
||||
exists(): boolean
|
||||
}
|
||||
export default class Dashboard implements IDashboard {
|
||||
public static get ID_KEY():string { return "dashboardId" }
|
||||
dashboardId: any = undefined
|
||||
name: string = "New Dashboard X"
|
||||
name: string = "New Dashboard"
|
||||
isPublic: boolean = false
|
||||
widgets: IWidget[] = []
|
||||
metrics: any[] = []
|
||||
isValid: boolean = false
|
||||
isPinned: boolean = false
|
||||
currentWidget: IWidget = new Widget()
|
||||
|
|
@ -73,7 +76,9 @@ export default class Dashboard implements IDashboard {
|
|||
dashboardId: this.dashboardId,
|
||||
name: this.name,
|
||||
isPrivate: this.isPublic,
|
||||
widgets: this.widgets.map(w => w.toJson())
|
||||
// widgets: this.widgets.map(w => w.toJson())
|
||||
// widgets: this.widgets
|
||||
metrics: this.metrics
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,4 +154,8 @@ export default class Dashboard implements IDashboard {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
exists() {
|
||||
return this.dashboardId !== undefined
|
||||
}
|
||||
}
|
||||
|
|
@ -9,11 +9,14 @@ import FilterItem from "./filterItem"
|
|||
export default class Filter {
|
||||
public static get ID_KEY():string { return "filterId" }
|
||||
name: string = ''
|
||||
filters: any[] = []
|
||||
filters: FilterItem[] = []
|
||||
eventsOrder: string = 'then'
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
filters: observable,
|
||||
eventsOrder: observable,
|
||||
|
||||
addFilter: action,
|
||||
removeFilter: action,
|
||||
updateKey: action,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ export default class FilterItem {
|
|||
type: observable,
|
||||
key: observable,
|
||||
value: observable,
|
||||
operator: observable,
|
||||
source: observable,
|
||||
filters: observable,
|
||||
|
||||
merge: action
|
||||
})
|
||||
|
||||
this.merge(data)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx"
|
||||
import { makeAutoObservable, runInAction, observable, action, reaction, computed } from "mobx"
|
||||
import FilterSeries from "./filterSeries";
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export interface IWidget {
|
||||
metricId: string
|
||||
metricId: any
|
||||
widgetId: any
|
||||
name: string
|
||||
metricType: string
|
||||
|
|
@ -59,18 +60,20 @@ export default class Widget implements IWidget {
|
|||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
// data: observable,
|
||||
widgetId: observable,
|
||||
name: observable,
|
||||
metricType: observable,
|
||||
metricOf: observable,
|
||||
position: observable,
|
||||
data: observable,
|
||||
isLoading: observable,
|
||||
isValid: observable,
|
||||
dashboardId: observable,
|
||||
addSeries: action,
|
||||
colSpan: observable,
|
||||
|
||||
series: observable,
|
||||
|
||||
addSeries: action,
|
||||
removeSeries: action,
|
||||
fromJson: action,
|
||||
toJson: action,
|
||||
validate: action,
|
||||
|
|
@ -97,14 +100,17 @@ export default class Widget implements IWidget {
|
|||
}
|
||||
|
||||
fromJson(json: any) {
|
||||
console.log('json', json);
|
||||
runInAction(() => {
|
||||
this.metricId = json.metricId
|
||||
this.widgetId = json.widgetId
|
||||
this.metricValue = json.metricValue
|
||||
this.metricOf = json.metricOf
|
||||
this.metricType = json.metricType
|
||||
this.name = json.name
|
||||
this.data = json.data
|
||||
this.series = json.series.map((series: any) => new FilterSeries().fromJson(series)),
|
||||
this.series = json.series ? json.series.map((series: any) => new FilterSeries().fromJson(series)) : [],
|
||||
this.dashboards = json.dashboards
|
||||
this.owner = json.ownerEmail
|
||||
this.lastModified = DateTime.fromISO(json.editedAt || json.createdAt)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { IDashboard } from "App/components/Dashboard/store/dashboard";
|
||||
import { IDashboard } from "App/mstore/types/dashboard";
|
||||
import APIClient from 'App/api_client';
|
||||
import { IWidget } from "App/components/Dashboard/store/widget";
|
||||
import { IWidget } from "App/mstore/types/widget";
|
||||
|
||||
export interface IDashboardService {
|
||||
initClient(client?: APIClient)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export interface IMetricService {
|
|||
deleteMetric(metricId: string): Promise<any>;
|
||||
|
||||
getTemplates(): Promise<any>;
|
||||
getMetricChartData(metric: IWidget): Promise<any>;
|
||||
}
|
||||
|
||||
export default class MetricService implements IMetricService {
|
||||
|
|
@ -88,4 +89,11 @@ export default class MetricService implements IMetricService {
|
|||
.then(response => response.json())
|
||||
.then(response => response.data || []);
|
||||
}
|
||||
|
||||
getMetricChartData(metric: IWidget): Promise<any> {
|
||||
const path = metric.metricId ? `/metrics/${metric.metricId}/chart` : `/custom_metrics/try`;
|
||||
return this.client.get(path)
|
||||
.then(response => response.json())
|
||||
.then(response => response.data || {});
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue