- {widgetCategories.map((category, index) =>
+ {activeCategory && widgetCategories.map((category, index) =>
)}
@@ -108,9 +90,9 @@ function DashboardMetricSelection(props) {
{activeCategory && activeCategory.widgets.map((widget: any) => (
toggleWidgetSelection(widget)}
+ key={widget.metricId}
+ className={cn("border rounded cursor-pointer", { 'border-color-teal' : selectedWidgetIds.includes(widget.metricId) })}
+ onClick={() => dashboardStore.toggleWidgetSelection(widget)}
>
diff --git a/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx b/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx
index 4e3a014c8..8c102763b 100644
--- a/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx
+++ b/frontend/app/components/Dashboard/components/DashboardModal/DashboardModal.tsx
@@ -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%'}}
>
-
-
Create Dashboard
+
+
+
+ { dashboard.exists() ? "Add metric(s) to dashboard" : "Create Dashboard" }
+
+
+
+ Create New
+
-
-
Create new dashboard by choosing from the range of predefined metrics that you care about. You can always add your custom metrics later.
+ { !dashboard.exists() && (
+ <>
+
+
Create new dashboard by choosing from the range of predefined metrics that you care about. You can always add your custom metrics later.
+ >
+ )}
- Add Selected to Dashboard
+ { dashboard.exists() ? "Add Selected to Dashboard" : "Create and Add to Dashboard" }
diff --git a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx
index 82ab00a0e..171a36164 100644
--- a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx
+++ b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx
@@ -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) {
+
+ <>Nothing...>
+
+
-
+
diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx
index c53a7a378..087ae7e07 100644
--- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx
+++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx
@@ -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 && (
showModal( , {})}
+ onClick={() => showModal( , {})}
>
{remainingDashboardsCount} More
diff --git a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx
index 4ac3380c6..3c0f96a92 100644
--- a/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx
+++ b/frontend/app/components/Dashboard/components/DashboardView/DashboardView.tsx
@@ -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(
, {})
+ }
+
+ 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 (
-
-
-
+
+
- Right
+
+
+
+ {/*
Add Metric */}
+
Add Metric
+
+
+ Remove
+
+
+
-
-
-
+
+
)
}
-export default withModal(observer(DashboardView));
\ No newline at end of file
+export default withRouter(withModal(observer(DashboardView)));
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx
index c43e1b97a..337aa1a8a 100644
--- a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx
+++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx
@@ -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) {
- props.updateSeries(seriesIndex, { name }) } />
+ series.update('name', name) } />
@@ -80,6 +83,7 @@ function FilterSeries(props: Props) {
onUpdateFilter={onUpdateFilter}
onRemoveFilter={onRemoveFilter}
onChangeEventsOrder={onChangeEventsOrder}
+ observeChanges={observeChanges}
/>
): (
{emptyMessage}
diff --git a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx
index cafcf53d8..b8e623370 100644
--- a/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx
+++ b/frontend/app/components/Dashboard/components/MetricListItem/MetricListItem.tsx
@@ -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) {
{metric.metricType}
{metric.owner}
- {/*
-
- {metric.isPublic ? 'Team' : 'Private'}
-
*/}
-
Last Modified
+
+
+
+ {metric.isPublic ? 'Team' : 'Private'}
+
+
+
{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}
);
}
diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx
index 9ef809261..729844590 100644
--- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx
+++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx
@@ -24,7 +24,7 @@ function MetricsList(props: Props) {
Type
Dashboards
Owner
- {/*
Visibility & Edit Access
*/}
+
Visibility & Edit Access
Last Modified
diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx
index 53f15faf5..2dd27bc9e 100644
--- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx
@@ -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
({ chart: [{}] })
+ const [seriesMap, setSeriesMap] = useState([]);
+ 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();
+
+ 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 Chart
;
+ if (viewType === 'lineChart') {
+ return (
+
+ )
+ } else if (viewType === 'progress') {
+ return (
+
+ )
+ }
}
if (metricType === 'table') {
- return Table
;
+ if (viewType === 'table') {
+ return ;
+ } else if (viewType === 'pieChart') {
+ return (
+
+ )
+ }
}
return Unknown
;
}
return (
-
+
{renderChart()}
-
+
);
}
-export default WidgetChart;
\ No newline at end of file
+export default observer(WidgetChart);
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
index 373c399c9..05a2ec910 100644
--- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
@@ -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(() => (
@@ -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}
/>
))}
diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx
index 11b9e326c..a227fce93 100644
--- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx
@@ -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';
diff --git a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx
index 92c3f42fd..6a43577a2 100644
--- a/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetWrapper/WidgetWrapper.tsx
@@ -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(() => (
-
- );
+ ));
}
export default WidgetWrapper;
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/store/dashboard.ts b/frontend/app/components/Dashboard/store/dashboard.ts
deleted file mode 100644
index dd253a6d3..000000000
--- a/frontend/app/components/Dashboard/store/dashboard.ts
+++ /dev/null
@@ -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
- }
- })
- }
-}
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/store/dashboardStore.ts b/frontend/app/components/Dashboard/store/dashboardStore.ts
deleted file mode 100644
index 65dbf861b..000000000
--- a/frontend/app/components/Dashboard/store/dashboardStore.ts
+++ /dev/null
@@ -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
- 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
-
- saveMetric(metric: IWidget, dashboardId?: string): Promise
-}
-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 {
- 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 {
- 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 => {
- 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),
-]
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/store/filter.ts b/frontend/app/components/Dashboard/store/filter.ts
deleted file mode 100644
index c9bdd3452..000000000
--- a/frontend/app/components/Dashboard/store/filter.ts
+++ /dev/null
@@ -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)
- }
-}
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/store/filterSeries.ts b/frontend/app/components/Dashboard/store/filterSeries.ts
deleted file mode 100644
index 284e50230..000000000
--- a/frontend/app/components/Dashboard/store/filterSeries.ts
+++ /dev/null
@@ -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
- }
-}
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/store/index.ts b/frontend/app/components/Dashboard/store/index.ts
deleted file mode 100644
index 6baa3c043..000000000
--- a/frontend/app/components/Dashboard/store/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as DashboardStore } from './dashboardStore';
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/store/widget.ts b/frontend/app/components/Dashboard/store/widget.ts
deleted file mode 100644
index 5e624dd54..000000000
--- a/frontend/app/components/Dashboard/store/widget.ts
+++ /dev/null
@@ -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)
- })
- }
-}
\ No newline at end of file
diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx
index 110702ac7..5aab22bec 100644
--- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx
+++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx
@@ -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);
}
diff --git a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx
index ba7b8650e..2fd2fc132 100644
--- a/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx
+++ b/frontend/app/components/shared/Filters/FilterValue/FilterValue.tsx
@@ -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;
diff --git a/frontend/app/initialize.js b/frontend/app/initialize.js
index 9e73942f7..dcfd01058 100644
--- a/frontend/app/initialize.js
+++ b/frontend/app/initialize.js
@@ -17,10 +17,10 @@ document.addEventListener('DOMContentLoaded', () => {
-
-
+ {/* */}
+ {/* */}
-
+ {/* */}
diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts
index 28e7197fb..96d7b22e0 100644
--- a/frontend/app/mstore/dashboardStore.ts
+++ b/frontend/app/mstore/dashboardStore.ts
@@ -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
- fetch(dashboardId: string)
+ fetch(dashboardId: string): Promise
save(dashboard: IDashboard): Promise
saveDashboardWidget(dashboard: Dashboard, widget: Widget)
- delete(dashboard: IDashboard)
+ deleteDashboard(dashboard: IDashboard): Promise
toJson(): void
fromJson(json: any): void
initDashboard(dashboard: IDashboard): void
@@ -43,14 +50,15 @@ export interface IDashboardSotre {
selectDefaultDashboard(): Promise
saveMetric(metric: IWidget, dashboardId?: string): Promise
+ fetchTemplates(): Promise
}
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 {
+ 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 {
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 {
+ 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 {
+ 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)
+ })
+ }
})
}
}
diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts
index e312ee58c..1e3746b9e 100644
--- a/frontend/app/mstore/metricStore.ts
+++ b/frontend/app/mstore/metricStore.ts
@@ -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
+ })
+ }
}
\ No newline at end of file
diff --git a/frontend/app/mstore/types/dashboard.ts b/frontend/app/mstore/types/dashboard.ts
index 7f3095744..46973f9ff 100644
--- a/frontend/app/mstore/types/dashboard.ts
+++ b/frontend/app/mstore/types/dashboard.ts
@@ -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
+ }
}
\ No newline at end of file
diff --git a/frontend/app/mstore/types/filter.ts b/frontend/app/mstore/types/filter.ts
index 5a7dcc5e9..bbe303e78 100644
--- a/frontend/app/mstore/types/filter.ts
+++ b/frontend/app/mstore/types/filter.ts
@@ -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,
diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts
index ea56020c5..606767b2b 100644
--- a/frontend/app/mstore/types/filterItem.ts
+++ b/frontend/app/mstore/types/filterItem.ts
@@ -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)
diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts
index c55ce2e97..4e6881b58 100644
--- a/frontend/app/mstore/types/widget.ts
+++ b/frontend/app/mstore/types/widget.ts
@@ -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
}
diff --git a/frontend/app/services/DashboardService.ts b/frontend/app/services/DashboardService.ts
index 1e5e8974f..a9d49a3bd 100644
--- a/frontend/app/services/DashboardService.ts
+++ b/frontend/app/services/DashboardService.ts
@@ -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)
diff --git a/frontend/app/services/MetricService.ts b/frontend/app/services/MetricService.ts
index 99c935d47..f2fab0a83 100644
--- a/frontend/app/services/MetricService.ts
+++ b/frontend/app/services/MetricService.ts
@@ -10,6 +10,7 @@ export interface IMetricService {
deleteMetric(metricId: string): Promise;
getTemplates(): Promise;
+ getMetricChartData(metric: IWidget): Promise;
}
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 {
+ const path = metric.metricId ? `/metrics/${metric.metricId}/chart` : `/custom_metrics/try`;
+ return this.client.get(path)
+ .then(response => response.json())
+ .then(response => response.data || {});
+ }
}
\ No newline at end of file