-
+ { (widget.metricType === 'table' || widget.metricType === 'timeseries') && }
+ { widget.metricType === 'funnel' && }
));
diff --git a/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js b/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js__
similarity index 100%
rename from frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js
rename to frontend/app/components/Funnels/FunnelDetails/FunnelDetails.js__
diff --git a/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.tsx b/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.tsx
new file mode 100644
index 000000000..0bbc2fe5c
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelDetails/FunnelDetails.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { withRouter } from 'react-router-dom';
+
+interface Props {
+
+}
+function FunnelDetails(props: Props) {
+ return (
+
+ Create View/Detail View
+
+ );
+}
+
+export default withRouter(FunnelDetails);
\ No newline at end of file
diff --git a/frontend/app/components/Funnels/FunnelItem/FunnelItem.js b/frontend/app/components/Funnels/FunnelItem/FunnelItem.js__
similarity index 100%
rename from frontend/app/components/Funnels/FunnelItem/FunnelItem.js
rename to frontend/app/components/Funnels/FunnelItem/FunnelItem.js__
diff --git a/frontend/app/components/Funnels/FunnelItem/FunnelItem.tsx b/frontend/app/components/Funnels/FunnelItem/FunnelItem.tsx
new file mode 100644
index 000000000..2c0793745
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelItem/FunnelItem.tsx
@@ -0,0 +1,24 @@
+import { IFunnel } from 'App/mstore/types/funnel';
+import React from 'react';
+import { Icon } from 'UI';
+
+interface Props {
+ funnel: IFunnel
+}
+function FunnelItem(props: Props) {
+ const { funnel } = props;
+ return (
+
+
+
{funnel.name}
+
{funnel.name}
+
+ );
+}
+
+export default FunnelItem;
\ No newline at end of file
diff --git a/frontend/app/components/Funnels/FunnelList/FunnelList.tsx b/frontend/app/components/Funnels/FunnelList/FunnelList.tsx
new file mode 100644
index 000000000..ca18a3c81
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelList/FunnelList.tsx
@@ -0,0 +1,61 @@
+import { PageTitle, Button, Pagination, Icon, Loader } from 'UI';
+import { useStore } from 'App/mstore';
+import { useObserver } from 'mobx-react-lite';
+import React, { useEffect } from 'react';
+import { sliceListPerPage } from 'App/utils';
+import FunnelItem from '../FunnelItem/FunnelItem';
+import FunnelSearch from '../FunnelSearch';
+
+function FunnelList(props) {
+ const { funnelStore } = useStore()
+ const list = useObserver(() => funnelStore.list)
+ const loading = useObserver(() => funnelStore.isLoading)
+
+ useEffect(() => {
+ if (list.length === 0) {
+ funnelStore.fetchFunnels()
+ }
+ }, [])
+
+ return (
+
+
+
+ Funnels make it easy to uncover the most significant issues that impacted conversions.
+
+
+
+
+ Title
+
+
Owner
+
Last Modified
+
+
+ {sliceListPerPage(list, funnelStore.page - 1, funnelStore.pageSize).map((funnel: any) => (
+
+ ))}
+
+
+
+
funnelStore.updateKey('page', page)}
+ limit={funnelStore.pageSize}
+ debounceRequest={100}
+ />
+
+
+ );
+}
+
+export default FunnelList;
\ No newline at end of file
diff --git a/frontend/app/components/Funnels/FunnelList/index.ts b/frontend/app/components/Funnels/FunnelList/index.ts
new file mode 100644
index 000000000..d03b7ff8b
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelList/index.ts
@@ -0,0 +1 @@
+export { default } from './FunnelList';
\ No newline at end of file
diff --git a/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx b/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx
new file mode 100644
index 000000000..57e2c7fe0
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Switch, Route } from 'react-router';
+import FunnelDetails from '../FunnelDetails/FunnelDetails';
+import FunnelList from '../FunnelList';
+
+function FunnelPage(props) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ {/*
+
+ */}
+
+
+ );
+}
+
+export default FunnelPage;
\ No newline at end of file
diff --git a/frontend/app/components/Funnels/FunnelPage/index.ts b/frontend/app/components/Funnels/FunnelPage/index.ts
new file mode 100644
index 000000000..b194e695f
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelPage/index.ts
@@ -0,0 +1 @@
+export { default } from './FunnelPage';
\ No newline at end of file
diff --git a/frontend/app/components/Funnels/FunnelSearch/FunnelSearch.tsx b/frontend/app/components/Funnels/FunnelSearch/FunnelSearch.tsx
new file mode 100644
index 000000000..ba36b7d9e
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelSearch/FunnelSearch.tsx
@@ -0,0 +1,34 @@
+import { useObserver } from 'mobx-react-lite';
+import React, { useEffect, useState } from 'react';
+import { useStore } from 'App/mstore';
+import { Icon } from 'UI';
+import { debounce } from 'App/utils';
+
+let debounceUpdate: any = () => {}
+function FunnelSearch(props) {
+ const { funnelStore } = useStore();
+ const [query, setQuery] = useState(funnelStore.search);
+ useEffect(() => {
+ debounceUpdate = debounce((key, value) => funnelStore.updateKey(key, value), 500);
+ }, [])
+
+ const write = ({ target: { name, value } }) => {
+ setQuery(value);
+ debounceUpdate('metricsSearch', value);
+ }
+
+ return useObserver(() => (
+
+
+
+
+ ));
+}
+
+export default FunnelSearch;
\ No newline at end of file
diff --git a/frontend/app/components/Funnels/FunnelSearch/index.ts b/frontend/app/components/Funnels/FunnelSearch/index.ts
new file mode 100644
index 000000000..2db683671
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelSearch/index.ts
@@ -0,0 +1 @@
+export { default } from './FunnelSearch';
\ No newline at end of file
diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx
new file mode 100644
index 000000000..f0c045ff7
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import FunnelStepText from './FunnelStepText';
+import { Icon } from 'UI';
+
+interface Props {
+ filter: any;
+}
+function FunnelBar(props: Props) {
+ const { filter } = props;
+ const completedPercentage = calculatePercentage(filter.sessionsCount, filter.dropDueToIssues);
+
+ return (
+
+
+
+
+
{completedPercentage}%
+
+
+
+
+
+ {filter.sessionsCount}
+ Completed
+
+
+
+ {filter.dropDueToIssues}
+ Dropped off
+
+
+
+ );
+}
+
+export default FunnelBar;
+
+const calculatePercentage = (completed: number, dropped: number) => {
+ const total = completed + dropped;
+ if (total === 0) {
+ return 0;
+ }
+ return Math.round((completed / total) * 100);
+}
\ 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..f0c9ef84c
--- /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: any, index: number) => (
+
+ {value}
+ {index < total - 1 && or}
+
+ ))}
+
+ );
+}
+
+export default FunnelStepText;
\ No newline at end of file
diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.module.css b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.module.css
new file mode 100644
index 000000000..0102e4d9f
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.module.css
@@ -0,0 +1,25 @@
+.step {
+ /* display: flex; */
+ position: relative;
+ transition: all 0.5s ease;
+ &: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;
+ }
+}
+
+.step-disabled {
+ filter: grayscale(1);
+ opacity: 0.8;
+ transition: all 0.2s ease-in-out;
+}
\ 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..0146a9c1f
--- /dev/null
+++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import Widget from 'App/mstore/types/widget';
+import Funnelbar from './FunnelBar';
+import cn from 'classnames';
+import stl from './FunnelWidget.module.css';
+import { Icon } from 'UI';
+import { useObserver } from 'mobx-react-lite';
+
+interface Props {
+ metric: Widget;
+}
+function FunnelWidget(props: Props) {
+ const { metric } = props;
+ const funnel = metric.data.funnel || { stages: [] };
+
+ return useObserver(() => (
+ <>
+
+ {funnel.stages.map((filter: any, index: any) => (
+
+
+ {index + 1}
+
+
+
+
+
+
+ ))}
+
+
+
+
Lost conversions
+
+ {funnel.lostConversions}
+ (12%)
+
+
+
+
+
Total conversions
+
+ 20
+ (12%)
+
+
+
+
+
Affected users
+
+ {funnel.affectedUsers}
+ {/* (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/components/Header/Header.js b/frontend/app/components/Header/Header.js
index 5b4c14b27..9803a06f2 100644
--- a/frontend/app/components/Header/Header.js
+++ b/frontend/app/components/Header/Header.js
@@ -7,6 +7,7 @@ import {
assist,
client,
errors,
+ funnels,
dashboard,
withSiteId,
CLIENT_DEFAULT_TAB,
@@ -31,6 +32,7 @@ const DASHBOARD_PATH = dashboard();
const SESSIONS_PATH = sessions();
const ASSIST_PATH = assist();
const ERRORS_PATH = errors();
+const FUNNELS_PATH = funnels();
const CLIENT_PATH = client(CLIENT_DEFAULT_TAB);
const AUTOREFRESH_INTERVAL = 30 * 1000;
@@ -105,6 +107,13 @@ const Header = (props) => {
>
{ 'Errors' }
+
+ { 'Funnels' }
+
{
var initOpts = {
projectKey: "PROJECT_KEY",
ingestPoint: "https://${window.location.hostname}/ingest",
- defaultInputMode: ${inputModeOptionsMap[gdpr.defaultInputMode]},
+ defaultInputMode: ${gdpr.defaultInputMode},
obscureTextNumbers: ${gdpr.maskNumbers},
obscureTextEmails: ${gdpr.maskEmails},
};
@@ -65,18 +66,13 @@ const ProjectCodeSnippet = props => {
}
const onChangeSelect = ({ name, value }) => {
- // console.log(name, value)
- // const { gdpr } = site;
const _gdpr = { ...gdpr.toData() };
- props.editGDPR({ [ name ]: value });
_gdpr[name] = value;
props.editGDPR({ [ name ]: value });
saveGDPR(_gdpr)
};
const onChangeOption = ({ target: { name, checked } }) => {
- // const { gdpr } = site;
- console.log(name, checked)
const _gdpr = { ...gdpr.toData() };
_gdpr[name] = checked;
props.editGDPR({ [ name ]: checked });
@@ -121,7 +117,7 @@ const ProjectCodeSnippet = props => {