feat(ui) - dashboard - wip
This commit is contained in:
parent
caa58d1f98
commit
ad706ededd
13 changed files with 162 additions and 36 deletions
|
|
@ -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(() => (
|
||||
<Modal size="tiny" open={ show }>
|
||||
<Modal.Header className="flex items-center justify-between">
|
||||
<div>{ 'Add to selected Dashboard' }</div>
|
||||
<Icon
|
||||
role="button"
|
||||
tabIndex="-1"
|
||||
color="gray-dark"
|
||||
size="14"
|
||||
name="close"
|
||||
onClick={ closeHandler }
|
||||
/>
|
||||
</Modal.Header>
|
||||
|
||||
<Modal.Content>
|
||||
<div className="py-4">
|
||||
<Form onSubmit={onSave}>
|
||||
<Form.Field>
|
||||
<label>{'Dashbaord:'}</label>
|
||||
<DropdownPlain
|
||||
options={dashboardOptions}
|
||||
value={selectedId}
|
||||
onChange={(e, { value }) => setSelectedId(value)}
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<div className="-mx-2 px-2">
|
||||
<Button
|
||||
primary
|
||||
onClick={ onSave }
|
||||
// loading={ loading }
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
<Button className="" marginRight onClick={ closeHandler }>{ 'Cancel' }</Button>
|
||||
</div>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
));
|
||||
}
|
||||
|
||||
export default DashboardSelectionModal;
|
||||
|
|
@ -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(() => (
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
</Button>
|
||||
<div className="flex items-center">
|
||||
{metric.exists() && (
|
||||
|
|
@ -189,7 +195,7 @@ function WidgetForm(props: Props) {
|
|||
<Icon name="trash" size="14" className="mr-2" color="teal"/>
|
||||
Delete
|
||||
</Button>
|
||||
<Button plain size="small" className="flex items-center ml-2">
|
||||
<Button plain size="small" className="flex items-center ml-2" onClick={() => setShowDashboardSelectionModal(true)}>
|
||||
<Icon name="columns-gap" size="14" className="mr-2" color="teal"/>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
|
|
@ -197,6 +203,13 @@ function WidgetForm(props: Props) {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<DashboardSelectionModal
|
||||
metricId={metric.metricId}
|
||||
show={showDashboardSelectionModal}
|
||||
closeHandler={() => setShowDashboardSelectionModal(false)}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<any>(null)
|
||||
|
|
@ -49,7 +50,7 @@ function WidgetName(props: Props) {
|
|||
<div className="text-2xl h-8 flex items-center border-transparent">{ name }</div>
|
||||
)}
|
||||
|
||||
<div className="ml-3 cursor-pointer" onClick={() => setEditing(true)}><Icon name="pencil" size="14" /></div>
|
||||
{ canEdit && <div className="ml-3 cursor-pointer" onClick={() => setEditing(true)}><Icon name="pencil" size="14" /></div> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
|||
<div className="bg-white rounded border">
|
||||
<div className="p-4 flex justify-between items-center">
|
||||
<h1 className="mb-0 text-2xl">
|
||||
<WidgetName name={widget.name} onUpdate={(name) => metricStore.merge({ name })} />
|
||||
<WidgetName
|
||||
name={widget.name}
|
||||
onUpdate={(name) => metricStore.merge({ name })}
|
||||
canEdit={expanded}
|
||||
/>
|
||||
</h1>
|
||||
<div className="text-gray-600">
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
.dropdown {
|
||||
display: flex !important;
|
||||
justify-content: space-between;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
color: $gray-darkest;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ interface Props {
|
|||
onChange: (e, { name, value }) => void;
|
||||
icon?: string;
|
||||
direction?: string;
|
||||
value: any;
|
||||
value?: any;
|
||||
multiple?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<any>
|
||||
fetchTemplates(): Promise<any>
|
||||
deleteDashboardWidget(dashboardId: string, widgetId: string): Promise<any>
|
||||
addWidgetToDashboard(dashboard: IDashboard, metricIds: any): Promise<any>
|
||||
}
|
||||
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<any> {
|
||||
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() {
|
||||
|
|
|
|||
|
|
@ -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<any> {
|
||||
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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export interface IDashboardService {
|
|||
|
||||
saveMetric(metric: IWidget, dashboardId?: string): Promise<any>
|
||||
|
||||
addWidget(dashboard: IDashboard, metricIds: []): Promise<any>
|
||||
saveWidget(dashboardId: string, widget: IWidget): Promise<any>
|
||||
deleteWidget(dashboardId: string, widgetId: string): Promise<any>
|
||||
}
|
||||
|
|
@ -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<any> {
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue