diff --git a/frontend/app/components/Dashboard/components/DashboardSelectionModal/DashboardSelectionModal.tsx b/frontend/app/components/Dashboard/components/DashboardSelectionModal/DashboardSelectionModal.tsx
new file mode 100644
index 000000000..f7a2c46ac
--- /dev/null
+++ b/frontend/app/components/Dashboard/components/DashboardSelectionModal/DashboardSelectionModal.tsx
@@ -0,0 +1,73 @@
+import { useObserver } from 'mobx-react-lite';
+import React from 'react';
+import { Button, Modal, Form, Icon } from 'UI';
+import { useStore } from 'App/mstore'
+import DropdownPlain from 'Shared/DropdownPlain';
+
+interface Props {
+ metricId: string,
+ show: boolean;
+ closeHandler?: () => void;
+}
+function DashboardSelectionModal(props: Props) {
+ const { show, metricId, closeHandler } = props;
+ const { dashboardStore } = useStore();
+ const dashboardOptions = dashboardStore.dashboards.map((i: any) => ({
+ key: i.id,
+ text: i.name,
+ value: i.dashboardId,
+ }));
+ const [selectedId, setSelectedId] = React.useState(dashboardOptions[0].value);
+
+ const onSave = () => {
+ const dashboard = dashboardStore.getDashboard(selectedId)
+ if (dashboard) {
+ dashboardStore.addWidgetToDashboard(dashboard, [metricId]).then(closeHandler)
+ }
+ }
+
+ return useObserver(() => (
+
+
+ { 'Add to selected Dashboard' }
+
+
+
+
+
+
+
+ setSelectedId(value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ ));
+}
+
+export default DashboardSelectionModal;
\ No newline at end of file
diff --git a/frontend/app/components/Dashboard/components/DashboardSelectionModal/index.ts b/frontend/app/components/Dashboard/components/DashboardSelectionModal/index.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx
index dd1af190b..9bc01dd01 100644
--- a/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx
+++ b/frontend/app/components/Dashboard/components/MetricsList/MetricsList.tsx
@@ -13,7 +13,7 @@ function MetricsList(props: Props) {
const metricsSearch = useObserver(() => metricStore.metricsSearch);
const filterRE = getRE(metricsSearch, 'i');
- const list = metrics.filter(w => filterRE.test(w.name));
+ const list = useObserver(() => metrics.filter(w => filterRE.test(w.name)));
return useObserver(() => (
diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
index abe92223a..058be4d1c 100644
--- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
import DropdownPlain from 'Shared/DropdownPlain';
import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions';
import { FilterKey } from 'Types/filter/filterType';
@@ -9,6 +9,7 @@ import FilterSeries from '../FilterSeries';
import { withRouter } from 'react-router-dom';
import { confirm } from 'UI/Confirmation';
import { withSiteId, dashboardMetricDetails, metricDetails } from 'App/routes'
+import DashboardSelectionModal from '../DashboardSelectionModal/DashboardSelectionModal';
interface Props {
history: any;
@@ -17,8 +18,10 @@ interface Props {
}
function WidgetForm(props: Props) {
+ const [showDashboardSelectionModal, setShowDashboardSelectionModal] = useState(false);
const { history, match: { params: { siteId, dashboardId, metricId } } } = props;
const { metricStore } = useStore();
+ const isSaving = useObserver(() => metricStore.isSaving);
const metric: any = useObserver(() => metricStore.instance);
const timeseriesOptions = metricOf.filter(i => i.type === 'timeseries');
@@ -52,12 +55,14 @@ function WidgetForm(props: Props) {
const onSave = () => {
const wasCreating = !metric.exists()
- metricStore.save(metric, dashboardId).then(() => {
+ metricStore.save(metric, dashboardId).then((metric) => {
if (wasCreating) {
if (parseInt(dashboardId) > 0) {
- history.push(withSiteId(dashboardMetricDetails(parseInt(dashboardId)), siteId));
+ history.push(withSiteId(dashboardMetricDetails(parseInt(dashboardId), metric.metricId), siteId));
+ history.go(0)
} else {
history.push(withSiteId(metricDetails(metric.metricId), siteId));
+ history.go(0)
}
}
@@ -179,8 +184,9 @@ function WidgetForm(props: Props) {
primary
size="small"
onClick={onSave}
+ disabled={isSaving}
>
- Save
+ {metric.exists() ? 'Update' : 'Create'}
{metric.exists() && (
@@ -189,7 +195,7 @@ function WidgetForm(props: Props) {
Delete
-
+
+
+ setShowDashboardSelectionModal(false)}
+ />
));
}
diff --git a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx
index 0cbfafd49..09e3b66b3 100644
--- a/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetName/WidgetName.tsx
@@ -5,9 +5,10 @@ interface Props {
name: string;
onUpdate: (name) => void;
seriesIndex?: number;
+ canEdit?: boolean
}
function WidgetName(props: Props) {
- const { seriesIndex = 1 } = props;
+ const { canEdit = true } = props;
const [editing, setEditing] = useState(false)
const [name, setName] = useState(props.name)
const ref = useRef(null)
@@ -49,7 +50,7 @@ function WidgetName(props: Props) {
{ name }
)}
- setEditing(true)}>
+ { canEdit && setEditing(true)}>
}
);
}
diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx
index f86e22302..82442bbcc 100644
--- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx
+++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import { useStore } from 'App/mstore';
import WidgetForm from '../WidgetForm';
@@ -15,16 +15,18 @@ interface Props {
}
function WidgetView(props: Props) {
const { match: { params: { siteId, dashboardId, metricId } } } = props;
- const [expanded, setExpanded] = useState(true);
const { metricStore } = useStore();
const widget = useObserver(() => metricStore.instance);
const loading = useObserver(() => metricStore.isLoading);
+ const [expanded, setExpanded] = useState(false);
+
+ useEffect(() => {
+ setExpanded(!widget.exists())
+ }, [widget])
React.useEffect(() => {
if (metricId && metricId !== 'create') {
- metricStore.fetch(metricId).then((metric) => {
- // metricStore.init(metric)
- });
+ metricStore.fetch(metricId);
} else {
metricStore.init();
}
@@ -45,7 +47,11 @@ function WidgetView(props: Props) {
- metricStore.merge({ name })} />
+ metricStore.merge({ name })}
+ canEdit={expanded}
+ />
void;
icon?: string;
direction?: string;
- value: any;
+ value?: any;
multiple?: boolean;
}
diff --git a/frontend/app/mstore/dashboardStore.ts b/frontend/app/mstore/dashboardStore.ts
index 3b497cfa6..85832699e 100644
--- a/frontend/app/mstore/dashboardStore.ts
+++ b/frontend/app/mstore/dashboardStore.ts
@@ -41,10 +41,8 @@ export interface IDashboardSotre {
// initDashboard(dashboard: IDashboard): void
addDashboard(dashboard: IDashboard): void
removeDashboard(dashboard: IDashboard): void
- getDashboard(dashboardId: string): void
- getDashboardIndex(dashboardId: string): number
+ getDashboard(dashboardId: string): IDashboard|null
getDashboardCount(): void
- getDashboardIndexByDashboardId(dashboardId: string): number
updateDashboard(dashboard: IDashboard): void
selectDashboardById(dashboardId: string): void
setSiteId(siteId: any): void
@@ -53,6 +51,7 @@ export interface IDashboardSotre {
saveMetric(metric: IWidget, dashboardId?: string): Promise
fetchTemplates(): Promise
deleteDashboardWidget(dashboardId: string, widgetId: string): Promise
+ addWidgetToDashboard(dashboard: IDashboard, metricIds: any): Promise
}
export default class DashboardStore implements IDashboardSotre {
siteId: any = null
@@ -86,10 +85,8 @@ export default class DashboardStore implements IDashboardSotre {
removeDashboard: action,
updateDashboard: action,
getDashboard: action,
- getDashboardIndex: action,
getDashboardByIndex: action,
getDashboardCount: action,
- getDashboardIndexByDashboardId: action,
selectDashboardById: action,
selectDefaultDashboard: action,
toJson: action,
@@ -287,12 +284,8 @@ export default class DashboardStore implements IDashboardSotre {
this.dashboards = this.dashboards.filter(d => d.dashboardId !== dashboard.dashboardId)
}
- getDashboard(dashboardId: string) {
- return this.dashboards.find(d => d.dashboardId === dashboardId)
- }
-
- getDashboardIndex(dashboardId: string) {
- return this.dashboards.findIndex(d => d.dashboardId === dashboardId)
+ getDashboard(dashboardId: string): IDashboard|null {
+ return this.dashboards.find(d => d.dashboardId === dashboardId) || null
}
getDashboardByIndex(index: number) {
@@ -303,10 +296,6 @@ export default class DashboardStore implements IDashboardSotre {
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) {
@@ -381,6 +370,19 @@ export default class DashboardStore implements IDashboardSotre {
this.isDeleting = false
})
}
+
+ addWidgetToDashboard(dashboard: IDashboard, metricIds: any) : Promise {
+ this.isSaving = true
+ return dashboardService.addWidget(dashboard, metricIds)
+ .then(response => {
+ toast.success('Widget added successfully')
+ }).catch(() => {
+ toast.error('Widget could not be added')
+ }).finally(() => {
+ this.isSaving = false
+ })
+
+ }
}
function getRandomWidget() {
diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts
index d322426a7..4521c4fff 100644
--- a/frontend/app/mstore/metricStore.ts
+++ b/frontend/app/mstore/metricStore.ts
@@ -90,7 +90,7 @@ export default class MetricStore implements IMetricStore {
}
updateKey(key: string, value: any) {
- this.instance[key] = value
+ this[key] = value
}
merge(object: any) {
@@ -131,18 +131,22 @@ export default class MetricStore implements IMetricStore {
}
// API Communication
- save(metric: IWidget, dashboardId?: string) {
+ save(metric: IWidget, dashboardId?: string): Promise {
const wasCreating = !metric[Widget.ID_KEY]
this.isSaving = true
return metricService.saveMetric(metric, dashboardId)
- .then(() => {
+ .then((metric) => {
+ const _metric = new Widget().fromJson(metric)
if (wasCreating) {
toast.success('Metric created successfully')
- this.addToList(metric)
+ this.addToList(_metric)
} else {
toast.success('Metric updated successfully')
- this.updateInList(metric)
+ this.updateInList(_metric)
}
+ return _metric
+ }).catch(() => {
+ toast.error('Error saving metric')
}).finally(() => {
this.isSaving = false
})
diff --git a/frontend/app/mstore/types/dashboard.ts b/frontend/app/mstore/types/dashboard.ts
index 41410b91c..bd6551ba3 100644
--- a/frontend/app/mstore/types/dashboard.ts
+++ b/frontend/app/mstore/types/dashboard.ts
@@ -27,6 +27,7 @@ export interface IDashboard {
swapWidgetPosition(positionA: number, positionB: number): void
sortWidgets(): void
exists(): boolean
+ toggleMetrics(metricId: string): void
}
export default class Dashboard implements IDashboard {
public static get ID_KEY():string { return "dashboardId" }
@@ -46,6 +47,7 @@ export default class Dashboard implements IDashboard {
isPublic: observable,
widgets: observable,
isValid: observable,
+ metrics: observable,
toJson: action,
fromJson: action,
@@ -61,6 +63,7 @@ export default class Dashboard implements IDashboard {
sortWidgets: action,
swapWidgetPosition: action,
update: action,
+ toggleMetrics: action
})
this.validate();
@@ -161,4 +164,12 @@ export default class Dashboard implements IDashboard {
exists() {
return this.dashboardId !== undefined
}
+
+ toggleMetrics(metricId: string) {
+ if (this.metrics.includes(metricId)) {
+ this.metrics = this.metrics.filter(m => m !== metricId)
+ } else {
+ this.metrics.push(metricId)
+ }
+ }
}
\ No newline at end of file
diff --git a/frontend/app/mstore/types/widget.ts b/frontend/app/mstore/types/widget.ts
index de7191eb0..7749600e5 100644
--- a/frontend/app/mstore/types/widget.ts
+++ b/frontend/app/mstore/types/widget.ts
@@ -112,7 +112,7 @@ export default class Widget implements IWidget {
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)
+ this.lastModified = DateTime.fromMillis(json.editedAt || json.createdAt)
this.config = json.config
this.position = json.config.position
})
diff --git a/frontend/app/services/DashboardService.ts b/frontend/app/services/DashboardService.ts
index 3208fd941..3bb99b9b4 100644
--- a/frontend/app/services/DashboardService.ts
+++ b/frontend/app/services/DashboardService.ts
@@ -14,6 +14,7 @@ export interface IDashboardService {
saveMetric(metric: IWidget, dashboardId?: string): Promise
+ addWidget(dashboard: IDashboard, metricIds: []): Promise
saveWidget(dashboardId: string, widget: IWidget): Promise
deleteWidget(dashboardId: string, widgetId: string): Promise
}
@@ -81,6 +82,20 @@ export default class DashboardService implements IDashboardService {
}
}
+ /**
+ * Add a widget to a dashboard.
+ * @param dashboard
+ * @param metricIds
+ * @returns
+ */
+ addWidget(dashboard: IDashboard, metricIds: any): Promise {
+ const data = dashboard.toJson()
+ data.metrics = metricIds
+ return this.client.put(`/dashboards/${dashboard.dashboardId}`, data)
+ .then(response => response.json())
+ .then(response => response.data || {});
+ }
+
/**
* Delete a dashboard.
* @param dashboardId