change(ui) - dashboard updates - wip

This commit is contained in:
Shekar Siri 2022-10-25 16:06:02 +02:00 committed by sylenien
parent e04c9d7816
commit 809ddcc2c2
20 changed files with 484 additions and 324 deletions

View file

@ -0,0 +1,16 @@
import Modal from 'App/components/Modal/Modal';
import React from 'react';
import MetricTypeList from '../MetricTypeList';
function AddCardModal() {
return (
<>
<Modal.Header title="Add Card" />
<Modal.Content className="p-0">
<MetricTypeList />
</Modal.Content>
</>
);
}
export default AddCardModal;

View file

@ -0,0 +1 @@
export { default } from './AddCardModal';

View file

@ -11,6 +11,7 @@ import DashboardOptions from '../DashboardOptions';
import withModal from 'App/components/Modal/withModal';
import { observer } from 'mobx-react-lite';
import DashboardEditModal from '../DashboardEditModal';
import AddCardModal from '../AddCardModal';
interface IProps {
siteId: string;
@ -23,7 +24,7 @@ function DashboardHeader(props: Props) {
const { siteId } = props;
const { dashboardStore } = useStore();
const { showModal } = useModal();
const [showTooltip, setShowTooltip] = React.useState(false);
// const [showTooltip, setShowTooltip] = React.useState(false);
const [focusTitle, setFocusedInput] = React.useState(true);
const [showEditModal, setShowEditModal] = React.useState(false);
const period = dashboardStore.period;
@ -79,7 +80,11 @@ function DashboardHeader(props: Props) {
/>
</div>
<div className="flex items-center" style={{ flex: 1, justifyContent: 'end' }}>
<Button variant="primary" onClick={() => setShowTooltip(true)} icon="plus">
<Button
variant="primary"
onClick={() => showModal(<AddCardModal />, { right: true })}
icon="plus"
>
Add Card
</Button>
<div className="mx-4"></div>

View file

@ -1,52 +1,12 @@
import React from 'react';
import { Button, PageTitle, Icon } from 'UI';
import withPageTitle from 'HOCs/withPageTitle';
import { useStore } from 'App/mstore';
import { withSiteId } from 'App/routes';
import Select from 'Shared/Select';
import DashboardList from './DashboardList';
import DashboardSearch from './DashboardSearch';
import { sort } from 'App/duck/sessions';
import { useObserver } from 'mobx-react-lite';
import Header from './Header';
function DashboardsView({ history, siteId }: { history: any; siteId: string }) {
const { dashboardStore } = useStore();
const sort = useObserver(() => dashboardStore.sort);
const onAddDashboardClick = () => {
dashboardStore.initDashboard();
dashboardStore.save(dashboardStore.dashboardInstance).then(async (syncedDashboard) => {
dashboardStore.selectDashboardById(syncedDashboard.dashboardId);
history.push(withSiteId(`/dashboard/${syncedDashboard.dashboardId}`, siteId));
});
};
return (
<div style={{ maxWidth: '1300px', margin: 'auto' }} className="bg-white rounded py-4 border">
<div className="flex items-center mb-4 justify-between px-6">
<div className="flex items-baseline mr-3">
<PageTitle title="Dashboards" />
</div>
<div className="ml-auto flex items-center">
<Button variant="primary" onClick={onAddDashboardClick}>
New Dashboard
</Button>
<div className="mx-2">
<Select
options={[
{ label: 'Newest', value: 'desc' },
{ label: 'Oldest', value: 'asc' },
]}
defaultValue={sort.by}
plain
onChange={({ value }) => dashboardStore.updateKey('sort', { by: value.value })}
/>
</div>
<div className="w-1/4" style={{ minWidth: 300 }}>
<DashboardSearch />
</div>
</div>
</div>
<Header history={history} siteId={siteId} />
<DashboardList />
</div>
);

View file

@ -0,0 +1,49 @@
import React from 'react';
import { Button, PageTitle } from 'UI';
import Select from 'Shared/Select';
import DashboardSearch from './DashboardSearch';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import { withSiteId } from 'App/routes';
function Header({ history, siteId }: { history: any; siteId: string }) {
const { dashboardStore } = useStore();
const sort = useObserver(() => dashboardStore.sort);
const onAddDashboardClick = () => {
dashboardStore.initDashboard();
dashboardStore.save(dashboardStore.dashboardInstance).then(async (syncedDashboard) => {
dashboardStore.selectDashboardById(syncedDashboard.dashboardId);
history.push(withSiteId(`/dashboard/${syncedDashboard.dashboardId}`, siteId));
});
};
return (
<div className="flex items-center mb-4 justify-between px-6">
<div className="flex items-baseline mr-3">
<PageTitle title="Dashboards" />
</div>
<div className="ml-auto flex items-center">
<Button variant="primary" onClick={onAddDashboardClick}>
New Dashboard
</Button>
<div className="mx-2">
<Select
options={[
{ label: 'Newest', value: 'desc' },
{ label: 'Oldest', value: 'asc' },
]}
defaultValue={sort.by}
plain
onChange={({ value }) => dashboardStore.updateKey('sort', { by: value.value })}
/>
</div>
<div className="w-1/4" style={{ minWidth: 300 }}>
<DashboardSearch />
</div>
</div>
</div>
);
}
export default Header;

View file

@ -76,8 +76,8 @@ function DashboardView(props: Props) {
return (
<Loader loading={loading}>
<div style={{ maxWidth: '1300px', margin: 'auto' }}>
{/* @ts-ignore */}
<DashboardHeader renderReport={props.renderReport} />
{/* @ts-ignore */}
<DashboardHeader renderReport={props.renderReport} siteId={siteId} />
<DashboardWidgetGrid
siteId={siteId}

View file

@ -1,66 +1,74 @@
import React from 'react';
import { Icon, Tooltip } from 'UI';
import { Icon, Checkbox, Tooltip } from 'UI';
import { checkForRecent } from 'App/date';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { withSiteId } from 'App/routes';
interface Props extends RouteComponentProps {
metric: any;
siteId: string;
metric: any;
siteId: string;
selected?: boolean;
toggleSelection?: any;
}
function MetricTypeIcon({ type }: any) {
const getIcon = () => {
switch (type) {
case 'funnel':
return 'filter';
case 'table':
return 'list-alt';
case 'timeseries':
return 'bar-chart-line';
}
const getIcon = () => {
switch (type) {
case 'funnel':
return 'filter';
case 'table':
return 'list-alt';
case 'timeseries':
return 'bar-chart-line';
}
};
return (
<Tooltip
title={<div className="capitalize">{type}</div>}
>
<div className="w-9 h-9 rounded-full bg-tealx-lightest flex items-center justify-center mr-2">
<Icon name={getIcon()} size="16" color="tealx" />
</div>
</Tooltip>
)
return (
<Tooltip title={<div className="capitalize">{type}</div>} >
<div className="w-9 h-9 rounded-full bg-tealx-lightest flex items-center justify-center mr-2">
<Icon name={getIcon()} size="16" color="tealx" />
</div>
</Tooltip>
);
}
function MetricListItem(props: Props) {
const { metric, history, siteId } = props;
const { metric, history, siteId, selected, toggleSelection = () => {} } = props;
const onItemClick = () => {
const path = withSiteId(`/metrics/${metric.metricId}`, siteId);
history.push(path);
};
return (
<div className="grid grid-cols-12 py-4 border-t select-none items-center hover:bg-active-blue cursor-pointer px-6" onClick={onItemClick}>
<div className="col-span-4 flex items-start">
<div className="flex items-center">
<MetricTypeIcon type={metric.metricType} />
<div className="link capitalize-first">
{metric.name}
</div>
</div>
</div>
<div className="col-span-4">{metric.owner}</div>
<div className="col-span-2">
<div className="flex items-center">
<Icon name={metric.isPublic ? "user-friends" : "person-fill"} className="mr-2" />
<span>{metric.isPublic ? 'Team' : 'Private'}</span>
</div>
</div>
<div className="col-span-2 text-right">{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}</div>
const onItemClick = () => {
const path = withSiteId(`/metrics/${metric.metricId}`, siteId);
history.push(path);
};
return (
<div
className="grid grid-cols-12 py-4 border-t select-none items-center hover:bg-active-blue cursor-pointer px-6"
onClick={onItemClick}
>
<div className="col-span-4 flex items-center">
<Checkbox
name="slack"
className="mr-4"
type="checkbox"
checked={selected}
onClick={toggleSelection}
/>
<div className="flex items-center">
<MetricTypeIcon type={metric.metricType} />
<div className="link capitalize-first">{metric.name}</div>
</div>
);
</div>
<div className="col-span-4">{metric.owner}</div>
<div className="col-span-2">
<div className="flex items-center">
<Icon name={metric.isPublic ? 'user-friends' : 'person-fill'} className="mr-2" />
<span>{metric.isPublic ? 'Team' : 'Private'}</span>
</div>
</div>
<div className="col-span-2 text-right">
{metric.lastModified && checkForRecent(metric.lastModified, 'LLL dd, yyyy, hh:mm a')}
</div>
</div>
);
}
export default withRouter(MetricListItem);

View file

@ -11,14 +11,19 @@ export interface MetricType {
interface Props {
metric: MetricType;
onClick?: any;
}
function MetricTypeItem(props: Props) {
const {
metric: { title, icon, description, slug },
onClick = () => {},
} = props;
return (
<div className="flex items-start p-4 hover:bg-active-blue cursor-pointer group hover-color-teal">
<div
className="flex items-start p-4 hover:bg-active-blue cursor-pointer group hover-color-teal"
onClick={onClick}
>
<div className="pr-4 pt-1">
<Icon name={icon} size="20" />
</div>

View file

@ -1,4 +1,6 @@
import { useModal } from 'App/components/Modal';
import React from 'react';
import MetricsLibraryModal from '../MetricsLibraryModal';
import MetricTypeItem, { MetricType } from '../MetricTypeItem/MetricTypeItem';
const METRIC_TYPES: MetricType[] = [
@ -71,10 +73,16 @@ const METRIC_TYPES: MetricType[] = [
];
function MetricTypeList() {
const { showModal } = useModal();
const onClick = ({ slug }: MetricType) => {
if (slug === 'library') {
showModal(<MetricsLibraryModal />, { right: true, width: 700 });
}
};
return (
<>
{METRIC_TYPES.map((metric: MetricType) => (
<MetricTypeItem metric={metric} />
<MetricTypeItem metric={metric} onClick={() => onClick(metric)} />
))}
</>
);

View file

@ -0,0 +1,45 @@
import React from 'react';
import { Icon, PageTitle, Button, Link } from 'UI';
import MetricsSearch from '../MetricsSearch';
import Select from 'Shared/Select';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
function MetricViewHeader() {
const { metricStore } = useStore();
const sort = useObserver(() => metricStore.sort);
return (
<div>
<div className="flex items-center mb-4 justify-between px-6">
<div className="flex items-baseline mr-3">
<PageTitle title="Metrics" className="" />
</div>
<div className="ml-auto flex items-center">
<Link to={'/metrics/create'}>
<Button variant="primary">Create</Button>
</Link>
<div className="mx-2">
<Select
options={[
{ label: 'Newest', value: 'desc' },
{ label: 'Oldest', value: 'asc' },
]}
defaultValue={sort.by}
plain
onChange={({ value }) => metricStore.updateKey('sort', { by: value.value })}
/>
</div>
<div className="ml-4 w-1/4" style={{ minWidth: 300 }}>
<MetricsSearch />
</div>
</div>
</div>
<div className="text-base text-disabled-text flex items-center px-6">
<Icon name="info-circle-fill" className="mr-2" size={16} />
Create custom Metrics to capture key interactions and track KPIs.
</div>
</div>
);
}
export default MetricViewHeader;

View file

@ -0,0 +1 @@
export { default } from './MetricViewHeader';

View file

@ -0,0 +1,13 @@
import Modal from 'App/components/Modal/Modal';
import React from 'react';
function MetricsLibraryModal() {
return (
<>
<Modal.Header title="Cards Library" />
<Modal.Content>Hello</Modal.Content>
</>
);
}
export default MetricsLibraryModal;

View file

@ -0,0 +1 @@
export { default } from './MetricsLibraryModal';

View file

@ -1,6 +1,6 @@
import { observer } from 'mobx-react-lite';
import React, { useEffect } from 'react';
import { NoContent, Pagination, Icon } from 'UI';
import React, { useEffect, useState } from 'react';
import { NoContent, Pagination, Icon, Checkbox } from 'UI';
import { useStore } from 'App/mstore';
import { filterList } from 'App/utils';
import MetricListItem from '../MetricListItem';
@ -11,6 +11,16 @@ function MetricsList({ siteId }: { siteId: string }) {
const { metricStore } = useStore();
const metrics = metricStore.sortedWidgets;
const metricsSearch = metricStore.metricsSearch;
const [selectedMetrics, setSelectedMetrics] = useState([]);
const toggleMetricSelection = (id: any) => {
console.log('id', id);
if (selectedMetrics.includes(id)) {
selectedMetrics.splice(selectedMetrics.indexOf(id), 1);
} else {
selectedMetrics.push(id);
}
};
const filterByDashboard = (item: Widget, searchRE: RegExp) => {
const dashboardsStr = item.dashboards.map((d: any) => d.name).join(' ');
@ -40,7 +50,16 @@ function MetricsList({ siteId }: { siteId: string }) {
>
<div className="mt-3 border-b rounded bg-white">
<div className="grid grid-cols-12 py-2 font-medium px-6">
<div className="col-span-4">Title</div>
<div className="col-span-4 flex items-center">
<Checkbox
name="slack"
className="mr-4"
type="checkbox"
checked={false}
onClick={() => setSelectedMetrics(list.map((i: any) => i.metricId))}
/>
<span>Title</span>
</div>
<div className="col-span-4">Owner</div>
<div className="col-span-2">Visibility</div>
<div className="col-span-2 text-right">Last Modified</div>
@ -48,7 +67,15 @@ function MetricsList({ siteId }: { siteId: string }) {
{sliceListPerPage(list, metricStore.page - 1, metricStore.pageSize).map((metric: any) => (
<React.Fragment key={metric.metricId}>
<MetricListItem metric={metric} siteId={siteId} />
<MetricListItem
metric={metric}
siteId={siteId}
selected={selectedMetrics[parseInt(metric.metricId)]}
toggleSelection={(e: any) => {
e.stopPropagation();
toggleMetricSelection(parseInt(metric.metricId));
}}
/>
</React.Fragment>
))}
</div>

View file

@ -1,40 +1,26 @@
import React from 'react';
import { Button, PageTitle, Icon, Link } from 'UI';
import withPageTitle from 'HOCs/withPageTitle';
import MetricsList from '../MetricsList';
import MetricsSearch from '../MetricsSearch';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import MetricViewHeader from '../MetricViewHeader';
interface Props {
siteId: string;
siteId: string;
}
function MetricsView({ siteId }: Props) {
const { metricStore } = useStore();
const { metricStore } = useStore();
React.useEffect(() => {
metricStore.fetchList();
}, []);
return useObserver(() => (
<div style={{ maxWidth: '1300px', margin: 'auto'}} className="bg-white rounded py-4 border">
<div className="flex items-center mb-4 justify-between px-6">
<div className="flex items-baseline mr-3">
<PageTitle title="Metrics" className="" />
</div>
<div className="ml-auto flex items-center">
<Link to={'/metrics/create'}><Button variant="primary">Create Metric</Button></Link>
<div className="ml-4 w-1/4" style={{ minWidth: 300 }}>
<MetricsSearch />
</div>
</div>
</div>
<div className="text-base text-disabled-text flex items-center px-6">
<Icon name="info-circle-fill" className="mr-2" size={16} />
Create custom Metrics to capture user frustrations, monitor your app's performance and track other KPIs.
</div>
<MetricsList siteId={siteId} />
</div>
));
React.useEffect(() => {
metricStore.fetchList();
}, []);
return useObserver(() => (
<div style={{ maxWidth: '1300px', margin: 'auto' }} className="bg-white rounded py-4 border">
<MetricViewHeader />
<MetricsList siteId={siteId} />
</div>
));
}
export default withPageTitle('Metrics - OpenReplay')(MetricsView);
export default withPageTitle('Cards - OpenReplay')(MetricsView);

View file

@ -1,9 +1,18 @@
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import ModalOverlay from './ModalOverlay';
import cn from 'classnames';
import { useHistory } from 'react-router';
export default function Modal({ component, props, hideModal }: any) {
const DEFAULT_WIDTH = 350;
interface Props {
component: any;
className?: string;
props: any;
hideModal?: boolean;
width?: number;
}
function Modal({ component, className = 'bg-white', props, hideModal }: Props) {
const history = useHistory();
useEffect(() => {
@ -12,11 +21,15 @@ export default function Modal({ component, props, hideModal }: any) {
document.querySelector('body').style.overflow = 'visible';
}
});
});
return component ? (
});return component ? (
ReactDOM.createPortal(
<ModalOverlay hideModal={hideModal} left={!props.right} right={props.right}>
{component}
<div
className={className}
style={{ width: `${props.width ? props.width : DEFAULT_WIDTH}px` }}
>
{component}
</div>
</ModalOverlay>,
document.querySelector('#modal-root')
)
@ -24,3 +37,17 @@ export default function Modal({ component, props, hideModal }: any) {
<></>
);
}
Modal.Header = ({ title }: { title: string }) => {
return (
<div className="text-lg flex items-center p-4 font-medium">
<div>{title}</div>
</div>
);
};
Modal.Content = ({ children, className = 'p-4' }: { children: any; className?: string }) => {
return <div className={cn('h-screen overflow-y-auto', className)}>{children}</div>;
};
export default Modal;

View file

@ -3,60 +3,59 @@ import React, { Component, createContext } from 'react';
import Modal from './Modal';
const ModalContext = createContext({
component: null,
props: {
right: true,
onClose: () => {},
},
showModal: (component: any, props: any) => {},
hideModal: () => {},
component: null,
props: {
right: true,
onClose: () => {},
},
showModal: (component: any, props: any) => {},
hideModal: () => {},
});
export class ModalProvider extends Component {
handleKeyDown = (e: any) => {
if (e.keyCode === 27) {
this.hideModal();
}
};
showModal = (component, props = { right: true }) => {
this.setState({
component,
props,
});
document.addEventListener('keydown', this.handleKeyDown);
document.querySelector('body').style.overflow = 'hidden';
};
hideModal = () => {
document.removeEventListener('keydown', this.handleKeyDown);
document.querySelector('body').style.overflow = 'visible';
const { props } = this.state;
if (props.onClose) {
props.onClose();
}
this.setState({
component: null,
props: {},
});
};
state = {
component: null,
get isModalActive() { return this.component !== null },
props: {},
showModal: this.showModal,
hideModal: this.hideModal,
};
render() {
return (
<ModalContext.Provider value={this.state}>
<Modal {...this.state} />
{this.props.children}
</ModalContext.Provider>
);
handleKeyDown = (e: any) => {
if (e.keyCode === 27) {
this.hideModal();
}
};
showModal = (component, props = { right: true }) => {
this.setState({
component,
props,
});
document.addEventListener('keydown', this.handleKeyDown);
document.querySelector('body').style.overflow = 'hidden';
};
hideModal = () => {
document.removeEventListener('keydown', this.handleKeyDown);
document.querySelector('body').style.overflow = 'visible';
const { props } = this.state;
if (props.onClose) {
props.onClose();
}
this.setState({
component: null,
props: {},
});
};
state = {
component: null,
get isModalActive() { return this.component !== null },props: {},
showModal: this.showModal,
hideModal: this.hideModal,
};
render() {
return (
<ModalContext.Provider value={this.state}>
<Modal {...this.state} />
{this.props.children}
</ModalContext.Provider>
);
}
}
export const ModalConsumer = ModalContext.Consumer;

View file

@ -1,9 +1,7 @@
import React from 'react';
import { ModalConsumer } from './';
export default BaseComponent => React.memo(props => (
<ModalConsumer>
{ value => <BaseComponent { ...value } { ...props } /> }
</ModalConsumer>
));
export default (BaseComponent) =>
React.memo((props) => (
<ModalConsumer>{(value) => <BaseComponent {...value} {...props} />}</ModalConsumer>
));

View file

@ -119,7 +119,7 @@ export default class DashboardStore {
return this.dashboards.filter((d) => ids.includes(d.dashboardId));
}
initDashboard(dashboard: Dashboard) {
initDashboard(dashboard?: Dashboard) {
this.dashboardInstance = dashboard
? new Dashboard().fromJson(dashboard)
: new Dashboard();

View file

@ -1,153 +1,164 @@
import { makeAutoObservable, computed } from "mobx"
import Widget from "./types/widget";
import { metricService, errorService } from "App/services";
import { makeAutoObservable, computed } from 'mobx';
import Widget from './types/widget';
import { metricService, errorService } from 'App/services';
import { toast } from 'react-toastify';
import Error from "./types/error";
import Error from './types/error';
export default class MetricStore {
isLoading: boolean = false
isSaving: boolean = false
isLoading: boolean = false;
isSaving: boolean = false;
metrics: Widget[] = []
instance = new Widget()
metrics: Widget[] = [];
instance = new Widget();
page: number = 1
pageSize: number = 10
metricsSearch: string = ""
sort: any = {}
page: number = 1;
pageSize: number = 10;
metricsSearch: string = '';
sort: any = { by: 'desc' };
sessionsPage: number = 1
sessionsPageSize: number = 10
sessionsPage: number = 1;
sessionsPageSize: number = 10;
constructor() {
makeAutoObservable(this)
constructor() {
makeAutoObservable(this);
}
@computed
get sortedWidgets() {
return [...this.metrics].sort((a, b) => b.lastModified - a.lastModified);
}
// State Actions
init(metric?: Widget | null) {
this.instance.update(metric || new Widget());
}
updateKey(key: string, value: any) {
// @ts-ignore
this[key] = value;
}
merge(object: any) {
Object.assign(this.instance, object);
this.instance.updateKey('hasChanged', true);
}
reset(id: string) {
const metric = this.findById(id);
if (metric) {
this.instance = metric;
}
}
@computed
get sortedWidgets() {
return [...this.metrics].sort((a, b) => b.lastModified - a.lastModified)
addToList(metric: Widget) {
this.metrics.push(metric);
}
updateInList(metric: Widget) {
// @ts-ignore
const index = this.metrics.findIndex((m: Widget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY]);
if (index >= 0) {
this.metrics[index] = metric;
}
}
// State Actions
init(metric?: Widget | null) {
this.instance.update(metric || new Widget())
}
findById(id: string) {
// @ts-ignore
return this.metrics.find((m) => m[Widget.ID_KEY] === id);
}
updateKey(key: string, value: any) {
// @ts-ignore
this[key] = value
}
removeById(id: string): void {
// @ts-ignore
this.metrics = this.metrics.filter((m) => m[Widget.ID_KEY] !== id);
}
merge(object: any) {
Object.assign(this.instance, object)
this.instance.updateKey('hasChanged', true)
}
get paginatedList(): Widget[] {
const start = (this.page - 1) * this.pageSize;
const end = start + this.pageSize;
return this.metrics.slice(start, end);
}
reset(id: string) {
const metric = this.findById(id)
if (metric) {
this.instance = metric
}
}
addToList(metric: Widget) {
this.metrics.push(metric)
}
updateInList(metric: Widget) {
// @ts-ignore
const index = this.metrics.findIndex((m: Widget) => m[Widget.ID_KEY] === metric[Widget.ID_KEY])
if (index >= 0) {
this.metrics[index] = metric
}
}
findById(id: string) {
// @ts-ignore
return this.metrics.find(m => m[Widget.ID_KEY] === id)
}
removeById(id: string): void {
// @ts-ignore
this.metrics = this.metrics.filter(m => m[Widget.ID_KEY] !== id)
}
get paginatedList(): Widget[] {
const start = (this.page - 1) * this.pageSize
const end = start + this.pageSize
return this.metrics.slice(start, end)
}
// API Communication
save(metric: Widget, dashboardId?: string): Promise<any> {
const wasCreating = !metric.exists()
this.isSaving = true
return new Promise((resolve, reject) => {
metricService.saveMetric(metric, dashboardId)
.then((metric: any) => {
const _metric = new Widget().fromJson(metric)
if (wasCreating) {
toast.success('Metric created successfully')
this.addToList(_metric)
this.instance = _metric
} else {
toast.success('Metric updated successfully')
this.updateInList(_metric)
}
resolve(_metric)
}).catch(() => {
toast.error('Error saving metric')
reject()
}).finally(() => {
this.instance.updateKey('hasChanged', false)
this.isSaving = false
})
// API Communication
save(metric: Widget, dashboardId?: string): Promise<any> {
const wasCreating = !metric.exists();
this.isSaving = true;
return new Promise((resolve, reject) => {
metricService
.saveMetric(metric, dashboardId)
.then((metric: any) => {
const _metric = new Widget().fromJson(metric);
if (wasCreating) {
toast.success('Metric created successfully');
this.addToList(_metric);
this.instance = _metric;
} else {
toast.success('Metric updated successfully');
this.updateInList(_metric);
}
resolve(_metric);
})
}
fetchList() {
this.isLoading = true
return metricService.getMetrics()
.then((metrics: any[]) => {
this.metrics = metrics.map(m => new Widget().fromJson(m))
}).finally(() => {
this.isLoading = false
})
}
fetch(id: string, period?: any) {
this.isLoading = true
return metricService.getMetric(id)
.then((metric: any) => {
return this.instance = new Widget().fromJson(metric, period)
})
.finally(() => {
this.isLoading = false
})
}
delete(metric: Widget) {
this.isSaving = true
// @ts-ignore
return metricService.deleteMetric(metric[Widget.ID_KEY])
.then(() => {
// @ts-ignore
this.removeById(metric[Widget.ID_KEY])
toast.success('Metric deleted successfully')
}).finally(() => {
this.instance.updateKey('hasChanged', false)
this.isSaving = false
})
}
fetchError(errorId: any): Promise<any> {
return new Promise((resolve, reject) => {
errorService.one(errorId).then((error: any) => {
resolve(new Error().fromJSON(error))
}).catch((error: any) => {
toast.error('Failed to fetch error details.')
reject(error)
})
.catch(() => {
toast.error('Error saving metric');
reject();
})
}
.finally(() => {
this.instance.updateKey('hasChanged', false);
this.isSaving = false;
});
});
}
fetchList() {
this.isLoading = true;
return metricService
.getMetrics()
.then((metrics: any[]) => {
this.metrics = metrics.map((m) => new Widget().fromJson(m));
})
.finally(() => {
this.isLoading = false;
});
}
fetch(id: string, period?: any) {
this.isLoading = true;
return metricService
.getMetric(id)
.then((metric: any) => {
return (this.instance = new Widget().fromJson(metric, period));
})
.finally(() => {
this.isLoading = false;
});
}
delete(metric: Widget) {
this.isSaving = true;
// @ts-ignore
return metricService
.deleteMetric(metric[Widget.ID_KEY])
.then(() => {
// @ts-ignore
this.removeById(metric[Widget.ID_KEY]);
toast.success('Metric deleted successfully');
})
.finally(() => {
this.instance.updateKey('hasChanged', false);
this.isSaving = false;
});
}
fetchError(errorId: any): Promise<any> {
return new Promise((resolve, reject) => {
errorService
.one(errorId)
.then((error: any) => {
resolve(new Error().fromJSON(error));
})
.catch((error: any) => {
toast.error('Failed to fetch error details.');
reject(error);
});
});
}
}