feat(ui) - dashboard - wip

This commit is contained in:
Shekar Siri 2022-04-06 16:49:26 +02:00
parent caa58d1f98
commit ad706ededd
13 changed files with 162 additions and 36 deletions

View file

@ -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;

View file

@ -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(() => (

View file

@ -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>
));
}

View file

@ -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>
);
}

View file

@ -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

View file

@ -1,5 +1,6 @@
.dropdown {
display: flex !important;
justify-content: space-between;
padding: 4px 8px;
border-radius: 3px;
color: $gray-darkest;

View file

@ -8,7 +8,7 @@ interface Props {
onChange: (e, { name, value }) => void;
icon?: string;
direction?: string;
value: any;
value?: any;
multiple?: boolean;
}

View file

@ -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() {

View file

@ -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
})

View file

@ -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)
}
}
}

View file

@ -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
})

View file

@ -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