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 - @@ -197,6 +203,13 @@ function WidgetForm(props: Props) { )}
+ + + 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