From da336d01407b7d4ce9d03db54e1466ceff59b139 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 24 Mar 2022 09:24:48 +0100 Subject: [PATCH] feat(ui) - dashboards wip --- frontend/app/Router.js | 14 +- .../app/components/Dashboard/NewDashboard.tsx | 89 ++++++---- .../Dashboard/WidgetView/WidgetView.tsx | 20 --- .../DashboardSideMenu/DashboardSideMenu.tsx | 11 +- .../components/FilterSeries/FilterSeries.tsx | 111 ++++++++++++ .../FilterSeries/SeriesName/SeriesName.tsx | 57 +++++++ .../FilterSeries/SeriesName/index.ts | 1 + .../components/FilterSeries/index.ts | 1 + .../components/MetricsView/MetricsView.tsx | 16 ++ .../Dashboard/components/MetricsView/index.ts | 1 + .../components/WidgetForm/WidgetForm.tsx | 160 ++++++++++++++++++ .../Dashboard/components/WidgetForm/index.ts | 1 + .../WidgetPreview/WidgetPreview.tsx | 87 ++++++++++ .../components/WidgetPreview/index.ts | 1 + .../WidgetSessions/WidgetSessions.tsx | 37 ++++ .../components/WidgetSessions/index.ts | 1 + .../components/WidgetView/WidgetView.tsx | 41 +++++ .../{ => components}/WidgetView/index.ts | 0 .../Dashboard/store/dashboardStore.ts | 12 +- .../app/components/Dashboard/store/filter.ts | 37 ++++ .../Dashboard/store/filterSeries.ts | 22 +++ .../app/components/Dashboard/store/widget.ts | 35 +++- .../FilterSeries/FilterSeries.tsx | 16 +- .../shared/DropdownPlain/DropdownPlain.css | 2 +- .../shared/Filters/FilterList/FilterList.tsx | 16 +- .../ui/DropdownPlain/DropdownPlain.js | 1 - frontend/app/routes.js | 12 +- frontend/app/svg/icons/bar-chart-line.svg | 2 +- .../app/svg/icons/chevron-double-left.svg | 2 +- .../app/svg/icons/chevron-double-right.svg | 2 +- frontend/app/svg/icons/columns-gap.svg | 3 + frontend/app/svg/icons/controller.svg | 2 +- frontend/app/svg/icons/flag-na.svg | 2 +- frontend/tailwind.config.js | 2 +- 34 files changed, 730 insertions(+), 87 deletions(-) delete mode 100644 frontend/app/components/Dashboard/WidgetView/WidgetView.tsx create mode 100644 frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx create mode 100644 frontend/app/components/Dashboard/components/FilterSeries/SeriesName/SeriesName.tsx create mode 100644 frontend/app/components/Dashboard/components/FilterSeries/SeriesName/index.ts create mode 100644 frontend/app/components/Dashboard/components/FilterSeries/index.ts create mode 100644 frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx create mode 100644 frontend/app/components/Dashboard/components/MetricsView/index.ts create mode 100644 frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetForm/index.ts create mode 100644 frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetPreview/index.ts create mode 100644 frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetSessions/index.ts create mode 100644 frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx rename frontend/app/components/Dashboard/{ => components}/WidgetView/index.ts (100%) create mode 100644 frontend/app/components/Dashboard/store/filter.ts create mode 100644 frontend/app/components/Dashboard/store/filterSeries.ts create mode 100644 frontend/app/svg/icons/columns-gap.svg diff --git a/frontend/app/Router.js b/frontend/app/Router.js index a6a9ba090..74e8845f7 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -15,7 +15,7 @@ import LiveSessionPure from 'Components/Session/LiveSession'; import AssistPure from 'Components/Assist'; import BugFinderPure from 'Components/BugFinder/BugFinder'; import DashboardPure from 'Components/Dashboard/NewDashboard'; -import WidgetViewPure from 'Components/Dashboard/WidgetView'; +import WidgetViewPure from 'Components/Dashboard/components/WidgetView'; import ErrorsPure from 'Components/Errors/Errors'; import Header from 'Components/Header/Header'; // import ResultsModal from 'Shared/Results/ResultsModal'; @@ -49,10 +49,8 @@ const withSiteId = routes.withSiteId; const withObTab = routes.withObTab; const DASHBOARD_PATH = routes.dashboard(); -// const DASHBOARD_WIDGET_CREATE_PATH = routes.dashboardMetricCreate(); -// const DASHBOARD_WIDGET_DETAILS_PATH = routes.dashboardMetricDetails(); -// const METRIC_CREATE_PATH = routes.metricCreate(); -// const METRIC_DETAILS_PATH = routes.metricDetails(); +const DASHBOARD_SELECT_PATH = routes.dashboardSelected(); +const DASHBOARD_METRICS_PATH = routes.dashboardMetrics(); // const WIDGET_PATAH = routes.dashboardMetric(); const SESSIONS_PATH = routes.sessions(); @@ -187,7 +185,11 @@ class Router extends React.Component { { siteIdList.length === 0 && } - + + + {/* */} + {/* */} + {/* diff --git a/frontend/app/components/Dashboard/NewDashboard.tsx b/frontend/app/components/Dashboard/NewDashboard.tsx index 831e44bd8..77a784ec2 100644 --- a/frontend/app/components/Dashboard/NewDashboard.tsx +++ b/frontend/app/components/Dashboard/NewDashboard.tsx @@ -5,12 +5,19 @@ import { observer } from "mobx-react-lite"; import { useDashboardStore } from './store/store'; import { withRouter } from 'react-router-dom'; import DashboardView from './components/DashboardView'; -import { dashboardSelected, dashboardMetricDetails, dashboardMetricCreate, withSiteId } from 'App/routes'; +import { + dashboardSelected, + dashboardMetricDetails, + dashboardMetricCreate, + withSiteId, + dashboardMetrics, +} from 'App/routes'; import DashboardSideMenu from './components/DashboardSideMenu'; -import WidgetView from './WidgetView'; +import WidgetView from './components/WidgetView'; +import MetricsView from './components/MetricsView'; function NewDashboard(props) { - const { match: { params: { siteId, dashboardId, metricId } } } = props; + const { history, match: { params: { siteId, dashboardId, metricId } } } = props; const store: any = useDashboardStore(); const dashboard = store.selectedDashboard; @@ -19,42 +26,66 @@ function NewDashboard(props) { }, []); useEffect(() => { - console.log('dashboardId', dashboardId); if (!dashboard || !dashboard.dashboardId) { if (dashboardId) { store.selectDashboardById(dashboardId); } else { store.selectDefaultDashboard(); } - } + } + + // if (dashboard) { + // if (dashboard.dashboardId !== dashboardId) { + // history.push(withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(siteId))); + // } + + // history.replace(withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(siteId))); + // } + // console.log('dashboard', dashboard) }, [dashboard]); + console.log('rendering dashboard', props.match.params); + return ( -
-
- -
-
- { dashboard && dashboard.dashboardId && ( - - - - - - - - - - - {/* - - */} - - - )} -
-
+ <> + {/* { dashboard && dashboard.dashboardId && ( */} + + +
+
+ +
+
+ +
+
+
+ { dashboardId && ( + <> + +
+
+ +
+
+ +
+
+
+ + {/* + + + + + */} + {/* */} + + )} +
+ {/* )} */} + ); } diff --git a/frontend/app/components/Dashboard/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/WidgetView/WidgetView.tsx deleted file mode 100644 index cfa34cd5c..000000000 --- a/frontend/app/components/Dashboard/WidgetView/WidgetView.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { withRouter } from 'react-router-dom'; -import { useDashboardStore } from '../store/store'; - -function WidgetView(props) { - console.log('WidgetView', props); - const store: any = useDashboardStore(); - const widget = store.currentWidget; - return ( -
-
-
-

{widget.name}

-
-
-
- ); -} - -export default withRouter(WidgetView); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx index 4c984904e..7f223bb55 100644 --- a/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx +++ b/frontend/app/components/Dashboard/components/DashboardSideMenu/DashboardSideMenu.tsx @@ -3,17 +3,20 @@ import React from 'react'; import { SideMenuitem, SideMenuHeader, Icon } from 'UI'; import { withDashboardStore } from '../../store/store'; import { withRouter } from 'react-router-dom'; -import { withSiteId, dashboardSelected } from 'App/routes'; +import { withSiteId, dashboardSelected, dashboardMetrics } from 'App/routes'; function DashboardSideMenu(props) { const { store, history } = props; const { dashboardId } = store.selectedDashboard; + const redirect = (path) => { + history.push(path); + } + const onItemClick = (dashboard) => { store.selectDashboardById(dashboard.dashboardId); const path = withSiteId(dashboardSelected(dashboard.dashboardId), parseInt(store.siteId)); - // console.log('path', path); - // history.push(path); + history.push(path); }; return ( @@ -41,7 +44,7 @@ function DashboardSideMenu(props) { id="menu-manage-alerts" title="Metrics" iconName="bar-chart-line" - // onClick={() => setShowAlerts(true)} + onClick={() => redirect(withSiteId(dashboardMetrics(), store.siteId))} />
diff --git a/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx new file mode 100644 index 000000000..c43e1b97a --- /dev/null +++ b/frontend/app/components/Dashboard/components/FilterSeries/FilterSeries.tsx @@ -0,0 +1,111 @@ +import React, { useState } from 'react'; +import FilterList from 'Shared/Filters/FilterList'; +import { + edit, + updateSeries, + addSeriesFilterFilter, + removeSeriesFilterFilter, + editSeriesFilterFilter, + editSeriesFilter, +} from 'Duck/customMetrics'; +import { connect } from 'react-redux'; +import { IconButton, Icon } from 'UI'; +import FilterSelection from 'Shared/Filters/FilterSelection'; +import SeriesName from './SeriesName'; +import cn from 'classnames'; +import { useDashboardStore } from '../../store/store'; +import { observer, useObserver } from 'mobx-react-lite'; + +interface Props { + seriesIndex: number; + series: any; + edit: typeof edit; + updateSeries: typeof updateSeries; + onRemoveSeries: (seriesIndex) => void; + canDelete?: boolean; + addSeriesFilterFilter: typeof addSeriesFilterFilter; + editSeriesFilterFilter: typeof editSeriesFilterFilter; + editSeriesFilter: typeof editSeriesFilter; + removeSeriesFilterFilter: typeof removeSeriesFilterFilter; + hideHeader?: boolean; + emptyMessage?: any; +} + +function FilterSeries(props: Props) { + const { canDelete, hideHeader = false, emptyMessage = 'Add user event or filter to define the series by clicking Add Step.' } = props; + const [expanded, setExpanded] = useState(true) + const { series, seriesIndex } = props; + + const onAddFilter = (filter) => { + series.filter.addFilter(filter) + } + + const onUpdateFilter = (filterIndex, filter) => { + series.filter.updateFilter(filterIndex, filter) + } + + const onChangeEventsOrder = (e, { name, value }) => { + series.filter.updateKey(name, value) + // props.editSeriesFilter(seriesIndex, { eventsOrder: value }); + } + + const onRemoveFilter = (filterIndex) => { + series.filter.removeFilter(filterIndex) + // props.removeSeriesFilterFilter(seriesIndex, filterIndex); + } + + return ( +
+
+
+ props.updateSeries(seriesIndex, { name }) } /> +
+ +
+
+ +
+ +
setExpanded(!expanded)} className="ml-3"> + +
+
+
+ { expanded && ( + <> +
+ { series.filter.filters.length > 0 ? ( + + ): ( +
{emptyMessage}
+ )} +
+
+
+ + + +
+
+ + )} +
+ ); +} + +export default connect(null, { + edit, + updateSeries, + addSeriesFilterFilter, + editSeriesFilterFilter, + editSeriesFilter, + removeSeriesFilterFilter, +})(observer(FilterSeries)); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/SeriesName.tsx b/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/SeriesName.tsx new file mode 100644 index 000000000..5d25e9de9 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/SeriesName.tsx @@ -0,0 +1,57 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Icon } from 'UI'; + +interface Props { + name: string; + onUpdate: (name) => void; + seriesIndex?: number; +} +function SeriesName(props: Props) { + const { seriesIndex = 1 } = props; + const [editing, setEditing] = useState(false) + const [name, setName] = useState(props.name) + const ref = useRef(null) + + const write = ({ target: { value, name } }) => { + setName(value) + } + + const onBlur = () => { + setEditing(false) + props.onUpdate(name) + } + + useEffect(() => { + if (editing) { + ref.current.focus() + } + }, [editing]) + + useEffect(() => { + setName(props.name) + }, [props.name]) + + // const { name } = props; + return ( +
+ { editing ? ( + setEditing(true)} + /> + ) : ( +
{name.trim() === '' ? 'Seriess ' + (seriesIndex + 1) : name }
+ )} + +
setEditing(true)}>
+
+ ); +} + +export default SeriesName; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/index.ts b/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/index.ts new file mode 100644 index 000000000..90e63cdb6 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FilterSeries/SeriesName/index.ts @@ -0,0 +1 @@ +export { default } from './SeriesName'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FilterSeries/index.ts b/frontend/app/components/Dashboard/components/FilterSeries/index.ts new file mode 100644 index 000000000..5882e382a --- /dev/null +++ b/frontend/app/components/Dashboard/components/FilterSeries/index.ts @@ -0,0 +1 @@ +export { default } from './FilterSeries' \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx new file mode 100644 index 000000000..d358075db --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricsView/MetricsView.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Button, PageTitle, Link } from 'UI'; +import { withSiteId, dashboardMetricCreate } from 'App/routes'; + +function MetricsView(props) { + return ( +
+
+ + {/* */} +
+
+ ); +} + +export default MetricsView; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/MetricsView/index.ts b/frontend/app/components/Dashboard/components/MetricsView/index.ts new file mode 100644 index 000000000..bfebac6b9 --- /dev/null +++ b/frontend/app/components/Dashboard/components/MetricsView/index.ts @@ -0,0 +1 @@ +export { default } from './MetricsView'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx new file mode 100644 index 000000000..631a3b791 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -0,0 +1,160 @@ +import React from 'react'; +import DropdownPlain from 'Shared/DropdownPlain'; +import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions'; +import { FilterKey } from 'Types/filter/filterType'; +import { useDashboardStore } from '../../store/store'; +import { useObserver } from 'mobx-react-lite'; +import { HelpText, Button, Icon } from 'UI' +import FilterSeries from '../FilterSeries'; + +interface Props { + // metric: any, + // editWidget: (metric, shouldFetch?) => void +} + +function WidgetForm(props: Props) { + // const { metric } = props; + const store: any = useDashboardStore(); + const metric = store.currentWidget; + + const timeseriesOptions = metricOf.filter(i => i.type === 'timeseries'); + const tableOptions = metricOf.filter(i => i.type === 'table'); + const isTable = metric.metricType === 'table'; + const isTimeSeries = metric.metricType === 'timeseries'; + const _issueOptions = [{ text: 'All', value: 'all' }].concat(issueOptions); + + const write = ({ target: { value, name } }) => store.editWidget({ [ name ]: value }, false); + const writeOption = (e, { value, name }) => { + store.editWidget({ [ name ]: value }, false); + + if (name === 'metricValue') { + store.editWidget({ metricValue: [value] }, false); + } + + if (name === 'metricOf') { + if (value === FilterKey.ISSUE) { + store.editWidget({ metricValue: ['all'] }, false); + } + } + + if (name === 'metricType') { + if (value === 'timeseries') { + store.editWidget({ metricOf: timeseriesOptions[0].value, viewType: 'lineChart' }, false); + } else if (value === 'table') { + store.editWidget({ metricOf: tableOptions[0].value, viewType: 'table' }, false); + } + } + }; + + return useObserver(() => ( +
+
+ +
+ + + {metric.metricType === 'timeseries' && ( + <> + of + + + )} + + {metric.metricType === 'table' && ( + <> + of + + + )} + + {metric.metricOf === FilterKey.ISSUE && ( + <> + issue type + + + )} + + {metric.metricType === 'table' && ( + <> + showing + + + )} +
+
+ +
+ + + {metric.series.length > 0 && metric.series.slice(0, isTable ? 1 : metric.series.length).map((series: any, index: number) => ( +
+ removeSeries(index)} + onRemoveSeries={() => metric.removeSeries(index)} + canDelete={metric.series.length > 1} + emptyMessage={isTable ? + 'Filter data using any event or attribute. Use Add Step button below to do so.' : + 'Add user event or filter to define the series by clicking Add Step.' + } + /> +
+ ))} +
+ +
+ +
+ + +
+
+
+ )); +} + +export default WidgetForm; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetForm/index.ts b/frontend/app/components/Dashboard/components/WidgetForm/index.ts new file mode 100644 index 000000000..283f9ec23 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetForm/index.ts @@ -0,0 +1 @@ +export { default } from './WidgetForm'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx new file mode 100644 index 000000000..4e6de9662 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import cn from 'classnames'; +import WidgetWrapper from '../../WidgetWrapper'; +import { useDashboardStore } from '../../store/store'; +import { Loader, NoContent, SegmentSelection, Icon } from 'UI'; +import DateRange from 'Shared/DateRange'; +import { useObserver } from 'mobx-react-lite'; + +interface Props { + className?: string; +} +function WidgetPreview(props: Props) { + const { className = '' } = props; + const store: any = useDashboardStore(); + const metric = store.currentWidget; + const isTimeSeries = metric.metricType === 'timeseries'; + const isTable = metric.metricType === 'table'; + + const chagneViewType = (e, { name, value }) => { + metric.update({ [ name ]: value }); + } + + const onDateChange = (changedDates) => { + // setPeriod({ ...changedDates, rangeName: changedDates.rangeValue }) + metric.update({ ...changedDates, rangeName: changedDates.rangeValue }); + } + + return useObserver(() => ( +
+
+

Trend

+
+ {isTimeSeries && ( + <> + Visualization + + + )} + + {isTable && ( + <> + Visualization + + + )} +
+ Time Range + +
+
+
+ +
+
+ )); +} + +export default WidgetPreview; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/index.ts b/frontend/app/components/Dashboard/components/WidgetPreview/index.ts new file mode 100644 index 000000000..9d28f8146 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetPreview/index.ts @@ -0,0 +1 @@ +export { default } from './WidgetPreview'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx new file mode 100644 index 000000000..60eb1a569 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetSessions/WidgetSessions.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { NoContent } from 'UI'; +import cn from 'classnames'; +import { useDashboardStore } from '../../store/store'; +import SessionItem from 'Shared/SessionItem'; + +interface Props { + className?: string; +} +function WidgetSessions(props: Props) { + const { className = '' } = props; + const store: any = useDashboardStore(); + const widget = store.currentWidget; + + return ( +
+
+

Sessions

+ {/*
Showing all sessions between {startTime} and {endTime}
*/} +
+ +
+ + {widget.sessions.map((session: any) => ( + + ))} + +
+
+ ); +} + +export default WidgetSessions; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetSessions/index.ts b/frontend/app/components/Dashboard/components/WidgetSessions/index.ts new file mode 100644 index 000000000..8f309114a --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetSessions/index.ts @@ -0,0 +1 @@ +export { default } from './WidgetSessions'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx new file mode 100644 index 000000000..cbe2d38e4 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { withRouter } from 'react-router-dom'; +import { useDashboardStore } from '../../store/store'; +import WidgetForm from '../WidgetForm'; +import WidgetPreview from '../WidgetPreview'; +import WidgetSessions from '../WidgetSessions'; +import { Icon } from 'UI'; + +interface Props { + +} +function WidgetView(props: Props) { + const [expanded, setExpanded] = useState(true); + const store: any = useDashboardStore(); + const widget = store.currentWidget; + return ( +
+
+
+

{widget.name}

+
+
setExpanded(!expanded)} + className="flex items-center cursor-pointer select-none" + > + {expanded ? 'Collapse' : 'Expand'} + +
+
+
+ + { expanded && } +
+ + + +
+ ); +} + +export default withRouter(WidgetView); \ No newline at end of file diff --git a/frontend/app/components/Dashboard/WidgetView/index.ts b/frontend/app/components/Dashboard/components/WidgetView/index.ts similarity index 100% rename from frontend/app/components/Dashboard/WidgetView/index.ts rename to frontend/app/components/Dashboard/components/WidgetView/index.ts diff --git a/frontend/app/components/Dashboard/store/dashboardStore.ts b/frontend/app/components/Dashboard/store/dashboardStore.ts index 832988ce9..8f03c4e59 100644 --- a/frontend/app/components/Dashboard/store/dashboardStore.ts +++ b/frontend/app/components/Dashboard/store/dashboardStore.ts @@ -18,6 +18,7 @@ export default class DashboardStore { selectedDashboard: observable, isLoading: observable, + resetCurrentWidget: action, addDashboard: action, removeDashboard: action, updateDashboard: action, @@ -31,6 +32,7 @@ export default class DashboardStore { toJson: action, fromJson: action, setSiteId: action, + editWidget: action, }) @@ -48,6 +50,14 @@ export default class DashboardStore { // }, 3000) } + resetCurrentWidget() { + this.currentWidget = new Widget() + } + + editWidget(widget: Widget) { + this.currentWidget.update(widget) + } + fetchList() { this.isLoading = true @@ -210,7 +220,7 @@ function getRandomWidget() { const widget = new Widget(); widget.widgetId = Math.floor(Math.random() * 100); widget.name = randomMetricName(); - widget.type = "random"; + // widget.type = "random"; widget.colSpan = Math.floor(Math.random() * 2) + 1; return widget; } diff --git a/frontend/app/components/Dashboard/store/filter.ts b/frontend/app/components/Dashboard/store/filter.ts new file mode 100644 index 000000000..c9bdd3452 --- /dev/null +++ b/frontend/app/components/Dashboard/store/filter.ts @@ -0,0 +1,37 @@ +import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" + +export default class Filter { + name: string = '' + filters: any[] = [] + eventsOrder: string = 'then' + + constructor() { + makeAutoObservable(this, { + addFilter: action, + removeFilter: action, + updateKey: action, + }) + } + + addFilter(filter: any) { + filter.value = [""] + if (filter.hasOwnProperty('filters')) { + filter.filters = filter.filters.map(i => ({ ...i, value: [""] })) + } + this.filters.push(filter) + console.log('addFilter', this.filters) + } + + updateFilter(index: number, filter: any) { + this.filters[index] = filter + console.log('updateFilter', this.filters) + } + + updateKey(key, value) { + this[key] = value + } + + removeFilter(index: number) { + this.filters.splice(index, 1) + } +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/store/filterSeries.ts b/frontend/app/components/Dashboard/store/filterSeries.ts new file mode 100644 index 000000000..284e50230 --- /dev/null +++ b/frontend/app/components/Dashboard/store/filterSeries.ts @@ -0,0 +1,22 @@ +// import Filter from 'Types/filter'; +import Filter from './filter' +import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" + +export default class FilterSeries { + seriesId?: any = undefined + name: string = "Series 1" + filter: Filter = new Filter() + + constructor() { + makeAutoObservable(this, { + name: observable, + filter: observable, + + update: action, + }) + } + + update(key, value) { + this[key] = value + } +} \ No newline at end of file diff --git a/frontend/app/components/Dashboard/store/widget.ts b/frontend/app/components/Dashboard/store/widget.ts index 2cd1bee32..27f5dfd46 100644 --- a/frontend/app/components/Dashboard/store/widget.ts +++ b/frontend/app/components/Dashboard/store/widget.ts @@ -1,9 +1,17 @@ import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import Filter from 'Types/filter'; +import FilterSeries from "./filterSeries"; export default class Widget { widgetId: any = undefined name: string = "New Metric" - type: string = "" + metricType: string = "timeseries" + metricOf: string = "sessionCount" + metricValue: string = "" + viewType: string = "lineChart" + series: FilterSeries[] = [] + sessions: [] = [] + position: number = 0 data: any = {} isLoading: boolean = false @@ -15,26 +23,46 @@ export default class Widget { makeAutoObservable(this, { widgetId: observable, name: observable, - type: observable, + metricType: observable, + metricOf: observable, position: observable, data: observable, isLoading: observable, isValid: observable, dashboardId: observable, + addSeries: action, colSpan: observable, fromJson: action, toJson: action, validate: action, update: action, + udpateKey: action, }) + + const filterSeries = new FilterSeries() + this.series.push(filterSeries) } + udpateKey(key: string, value: any) { + this[key] = value + } + + removeSeries(index: number) { + this.series.splice(index, 1) + } + + addSeries() { + const series = new FilterSeries() + series.name = "Series " + (this.series.length + 1) + this.series.push(series) + } + + fromJson(json: any) { runInAction(() => { this.widgetId = json.widgetId this.name = json.name - this.type = json.type this.data = json.data }) return this @@ -44,7 +72,6 @@ export default class Widget { return { widgetId: this.widgetId, name: this.name, - type: this.type, data: this.data } } diff --git a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx index 5327308dd..c86f626ac 100644 --- a/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx +++ b/frontend/app/components/shared/CustomMetrics/FilterSeries/FilterSeries.tsx @@ -86,13 +86,15 @@ function FilterSeries(props: Props) {
{emptyMessage}
)}
-
- - - +
+
+ + + +
)} diff --git a/frontend/app/components/shared/DropdownPlain/DropdownPlain.css b/frontend/app/components/shared/DropdownPlain/DropdownPlain.css index dd7b9a2a5..772060e87 100644 --- a/frontend/app/components/shared/DropdownPlain/DropdownPlain.css +++ b/frontend/app/components/shared/DropdownPlain/DropdownPlain.css @@ -1,6 +1,6 @@ .dropdown { display: flex !important; - padding: 4px; + padding: 4px 8px; border-radius: 3px; color: $gray-darkest; font-weight: 500; diff --git a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx index 8eba891bb..110702ac7 100644 --- a/frontend/app/components/shared/Filters/FilterList/FilterList.tsx +++ b/frontend/app/components/shared/Filters/FilterList/FilterList.tsx @@ -1,6 +1,8 @@ import React, { useState} from 'react'; import FilterItem from '../FilterItem'; import { SegmentSelection, Popup } from 'UI'; +import { List } from 'immutable'; +import { useObserver } from 'mobx-react-lite'; interface Props { // filters: any[]; // event/filter @@ -12,16 +14,16 @@ interface Props { } function FilterList(props: Props) { const { filter, hideEventsOrder = false } = props; - const filters = filter.filters; - const hasEvents = filter.filters.filter(i => i.isEvent).size > 0; - const hasFilters = filter.filters.filter(i => !i.isEvent).size > 0; + const filters = List(filter.filters); + const hasEvents = filters.filter((i: any) => i.isEvent).size > 0; + const hasFilters = filters.filter((i: any) => !i.isEvent).size > 0; let rowIndex = 0; const onRemoveFilter = (filterIndex) => { props.onRemoveFilter(filterIndex); } - return ( + return useObserver(() => (
{ hasEvents && ( <> @@ -54,7 +56,7 @@ function FilterList(props: Props) {
)}
- {filters.map((filter, filterIndex) => filter.isEvent ? ( + {filters.map((filter: any, filterIndex: any) => filter.isEvent ? ( {hasEvents &&
}
FILTERS
- {filters.map((filter, filterIndex) => !filter.isEvent ? ( + {filters.map((filter: any, filterIndex: any) => !filter.isEvent ? ( )}
- ); + )); } export default FilterList; \ No newline at end of file diff --git a/frontend/app/components/ui/DropdownPlain/DropdownPlain.js b/frontend/app/components/ui/DropdownPlain/DropdownPlain.js index 389b75b93..ce1fc506d 100644 --- a/frontend/app/components/ui/DropdownPlain/DropdownPlain.js +++ b/frontend/app/components/ui/DropdownPlain/DropdownPlain.js @@ -21,7 +21,6 @@ function DropdownPlain({ name, label, options, onChange, defaultValue, wrapperSt options={ options } onChange={ onChange } defaultValue={ defaultValue || options[ 0 ].value } - icon={null} disabled={disabled} icon={ } /> diff --git a/frontend/app/routes.js b/frontend/app/routes.js index 8693ae3e6..4aea022a4 100644 --- a/frontend/app/routes.js +++ b/frontend/app/routes.js @@ -101,6 +101,7 @@ export const testBuilderNew = () => '/test-builder'; export const testBuilder = (testId = ':testId') => `/test-builder/${ testId }`; export const dashboard = () => '/dashboard'; +export const dashboardMetrics = () => '/dashboard/metrics'; export const dashboardSelected = (id = ':dashboardId', hash) => hashed(`/dashboard/${ id }`, hash); export const dashboardMetricDetails = (id = ':dashboardId', metricId = ':metricId', hash) => hashed(`/dashboard/${ id }/metric/${metricId}`, hash); @@ -121,6 +122,7 @@ const REQUIRED_SITE_ID_ROUTES = [ assist(), dashboard(''), dashboardSelected(''), + dashboardMetrics(''), // dashboardMetricCreate(''), dashboardMetricDetails(''), metricCreate(''), @@ -152,7 +154,15 @@ export function isRoute(route, path){ routeParts.every((p, i) => p.startsWith(':') || p === pathParts[ i ]); } -const SITE_CHANGE_AVALIABLE_ROUTES = [ sessions(), assist(), dashboard(), dashboardSelected(''), errors(), onboarding('')]; +const SITE_CHANGE_AVALIABLE_ROUTES = [ + sessions(), + assist(), + dashboard(), + dashboardMetrics(''), + dashboardSelected(''), + errors(), + onboarding('') +]; export const siteChangeAvaliable = path => SITE_CHANGE_AVALIABLE_ROUTES.some(r => isRoute(r, path)); export const redirects = Object.entries({ diff --git a/frontend/app/svg/icons/bar-chart-line.svg b/frontend/app/svg/icons/bar-chart-line.svg index e3f0cf255..c46c1d8f8 100644 --- a/frontend/app/svg/icons/bar-chart-line.svg +++ b/frontend/app/svg/icons/bar-chart-line.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/frontend/app/svg/icons/chevron-double-left.svg b/frontend/app/svg/icons/chevron-double-left.svg index 7181fd111..8694e8195 100644 --- a/frontend/app/svg/icons/chevron-double-left.svg +++ b/frontend/app/svg/icons/chevron-double-left.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/frontend/app/svg/icons/chevron-double-right.svg b/frontend/app/svg/icons/chevron-double-right.svg index 73e1b352d..ee4323e06 100644 --- a/frontend/app/svg/icons/chevron-double-right.svg +++ b/frontend/app/svg/icons/chevron-double-right.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/frontend/app/svg/icons/columns-gap.svg b/frontend/app/svg/icons/columns-gap.svg new file mode 100644 index 000000000..f4ed1f9ae --- /dev/null +++ b/frontend/app/svg/icons/columns-gap.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/svg/icons/controller.svg b/frontend/app/svg/icons/controller.svg index 15e777456..7c9499541 100644 --- a/frontend/app/svg/icons/controller.svg +++ b/frontend/app/svg/icons/controller.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/frontend/app/svg/icons/flag-na.svg b/frontend/app/svg/icons/flag-na.svg index ca42ac405..33796e2f0 100644 --- a/frontend/app/svg/icons/flag-na.svg +++ b/frontend/app/svg/icons/flag-na.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 0da29c3d5..28fd4d982 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -93,7 +93,7 @@ module.exports = { // 'transitionProperty', // 'transitionTimingFunction', // 'translate', - // 'userSelect', + 'userSelect', // 'verticalAlign', 'visibility', 'whitespace',