From 3bb5d9fabd0d86ede924c6fbce8b173cca160699 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 10 May 2022 17:17:15 +0200 Subject: [PATCH] feat(ui) - funnels - graph --- .../components/WidgetChart/WidgetChart.tsx | 5 ++ .../WidgetPreview/WidgetPreview.tsx | 2 +- .../Funnels/FunnelWidget/FunnelBar.tsx | 51 ++++++++++++++++++ .../Funnels/FunnelWidget/FunnelStepText.tsx | 23 ++++++++ .../Funnels/FunnelWidget/FunnelWidget.css | 18 +++++++ .../Funnels/FunnelWidget/FunnelWidget.tsx | 52 +++++++++++++++++++ .../components/Funnels/FunnelWidget/index.ts | 1 + frontend/app/constants/filterOptions.js | 2 + frontend/app/mstore/metricStore.ts | 36 ++++++++++++- frontend/app/mstore/types/filterItem.ts | 6 +++ frontend/app/svg/icons/arrow-right-short.svg | 3 ++ frontend/app/theme/colors.js | 1 + 12 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx create mode 100644 frontend/app/components/Funnels/FunnelWidget/FunnelStepText.tsx create mode 100644 frontend/app/components/Funnels/FunnelWidget/FunnelWidget.css create mode 100644 frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx create mode 100644 frontend/app/components/Funnels/FunnelWidget/index.ts create mode 100644 frontend/app/svg/icons/arrow-right-short.svg diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index eb63d8f08..e95f47387 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -11,6 +11,7 @@ import WidgetPredefinedChart from '../WidgetPredefinedChart'; import CustomMetricOverviewChart from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricOverviewChart'; import { getStartAndEndTimestampsByDensity } from 'Types/dashboard/helper'; import { debounce } from 'App/utils'; +import FunnelWidget from 'App/components/Funnels/FunnelWidget'; interface Props { metric: any; isWidget?: boolean @@ -81,6 +82,10 @@ function WidgetChart(props: Props) { const renderChart = () => { const { metricType, viewType } = metric; + if (metricType === 'funnel') { + return + } + if (metricType === 'predefined') { if (isOverviewWidget) { return diff --git a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx index 24bfdcbd8..6df0bb2e9 100644 --- a/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx +++ b/frontend/app/components/Dashboard/components/WidgetPreview/WidgetPreview.tsx @@ -2,7 +2,7 @@ import React from 'react'; import cn from 'classnames'; import WidgetWrapper from '../WidgetWrapper'; import { useStore } from 'App/mstore'; -import { Loader, NoContent, SegmentSelection, Icon } from 'UI'; +import { SegmentSelection } from 'UI'; import DateRange from 'Shared/DateRange'; import { useObserver } from 'mobx-react-lite'; diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx new file mode 100644 index 000000000..89d6efdb0 --- /dev/null +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import FunnelStepText from './FunnelStepText'; +import { Icon } from 'UI'; + +interface Props { + completed: number; + dropped: number; + filter: any; +}`` +function FunnelBar(props: Props) { + const { completed, dropped, filter } = props; + + return ( +
+ +
+
+
+
+
+ + {13} + completed +
+
+ + 20 + Dropped off +
+
+
+ ); +} + +export default FunnelBar; \ No newline at end of file diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelStepText.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelStepText.tsx new file mode 100644 index 000000000..c805fdef5 --- /dev/null +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelStepText.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +interface Props { + filter: any; +} +function FunnelStepText(props: Props) { + const { filter } = props; + const total = filter.value.length; + return ( +
+ {filter.label} + {filter.operator} + {filter.value.map((value, index) => ( + <> + {value} + {index < total - 1 && or} + + ))} +
+ ); +} + +export default FunnelStepText; \ No newline at end of file diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.css b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.css new file mode 100644 index 000000000..6cca14777 --- /dev/null +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.css @@ -0,0 +1,18 @@ +.step { + /* display: flex; */ + position: relative; + &:before { + content: ''; + border-left: 2px solid $gray-lightest; + position: absolute; + top: 16px; + bottom: 9px; + left: 10px; + /* width: 1px; */ + height: 100%; + z-index: 0; + } + &:last-child:before { + display: none; + } +} \ No newline at end of file diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx new file mode 100644 index 000000000..b71ea78bc --- /dev/null +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import Funnel from 'App/mstore/types/funnel'; +import Widget from 'App/mstore/types/widget'; +import Funnelbar from './FunnelBar' +import cn from 'classnames'; +import stl from './FunnelWidget.css'; +import { Icon } from 'UI'; + +interface Props { + metric: Widget; +} +function FunnelWidget(props: Props) { + const { metric } = props; + + return ( + <> +
+ {metric.series[0].filter.filters.filter(f => f.isEvent).map((filter, index) => ( +
+
+ {index + 1} +
+ +
+ +
+
+ ))} +
+
+
+ Total conversions +
+ 20 + (12%) +
+
+ +
+
+ Lost conversions +
+ 20 + (12%) +
+
+
+ + ); +} + +export default FunnelWidget; \ No newline at end of file diff --git a/frontend/app/components/Funnels/FunnelWidget/index.ts b/frontend/app/components/Funnels/FunnelWidget/index.ts new file mode 100644 index 000000000..e2e5d1797 --- /dev/null +++ b/frontend/app/components/Funnels/FunnelWidget/index.ts @@ -0,0 +1 @@ +export { default } from './FunnelWidget'; \ No newline at end of file diff --git a/frontend/app/constants/filterOptions.js b/frontend/app/constants/filterOptions.js index d584a7b24..b3bcfc43c 100644 --- a/frontend/app/constants/filterOptions.js +++ b/frontend/app/constants/filterOptions.js @@ -62,6 +62,8 @@ export const customOperators = [ export const metricTypes = [ { text: 'Timeseries', value: 'timeseries' }, { text: 'Table', value: 'table' }, + { text: 'Funnel', value: 'funnel' }, + { text: 'Error', value: 'error' }, ]; export const tableColumnName = { diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 8ed1f471e..9442dccbf 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -90,7 +90,10 @@ export default class MetricStore implements IMetricStore { // State Actions init(metric?: IWidget|null) { - this.instance.update(metric || new Widget()) + const _metric = new Widget().fromJson(sampleJson) + this.instance.update(metric || _metric) + + // this.instance.update(metric || new Widget()) } updateKey(key: string, value: any) { @@ -186,4 +189,35 @@ export default class MetricStore implements IMetricStore { this.isSaving = false }) } +} + +const sampleJson = { + metricId: 1, + name: "Funnel Sample", + metricType: 'funnel', + series: [ + { + name: 'Series 1', + filter: { + filters: [ + { + type: 'LOCATION', + operator: 'is', + value: ['/sessions', '/errors', '/users'], + percent: 100, + completed: 60, + dropped: 0, + }, + { + type: 'LOCATION', + operator: 'is', + value: ['/sessions', '/errors', '/users'], + percent: 80, + completed: 40, + dropped: 20, + } + ] + } + } + ], } \ No newline at end of file diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts index 00b2a6fdc..c88d07db5 100644 --- a/frontend/app/mstore/types/filterItem.ts +++ b/frontend/app/mstore/types/filterItem.ts @@ -27,6 +27,12 @@ export default class FilterItem { merge: action }) + if (Array.isArray(data.filters)) { + data.filters = data.filters.map(function (i) { + return new FilterItem(i); + }); + } + this.merge(data) } diff --git a/frontend/app/svg/icons/arrow-right-short.svg b/frontend/app/svg/icons/arrow-right-short.svg new file mode 100644 index 000000000..043996f41 --- /dev/null +++ b/frontend/app/svg/icons/arrow-right-short.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/app/theme/colors.js b/frontend/app/theme/colors.js index 963c71eec..9c25015c1 100644 --- a/frontend/app/theme/colors.js +++ b/frontend/app/theme/colors.js @@ -27,6 +27,7 @@ module.exports = { "green-dark": "#2C9848", red: "#cc0000", red2: "#F5A623", + "red-lightest": 'rgba(204, 0, 0, 0.1)', blue: "#366CD9", blue2: "#0076FF", "active-blue": "#F6F7FF",