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",