From 45e39c8749cabe2f17e9aeb6d2891e23801178f4 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Wed, 20 Apr 2022 18:05:10 +0200 Subject: [PATCH 01/31] feat(ui) - funnels - wip --- frontend/app/mstore/funnelStore.ts | 90 ++++++++++++++++++++++ frontend/app/mstore/types/funnel.ts | 45 +++++++++++ frontend/app/mstore/types/funnelStage.ts | 18 +++++ frontend/app/mstore/types/funnelnsights.ts | 12 +++ frontend/app/services/FunnelService.ts | 62 +++++++++++++++ frontend/app/services/index.ts | 4 +- 6 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 frontend/app/mstore/funnelStore.ts create mode 100644 frontend/app/mstore/types/funnel.ts create mode 100644 frontend/app/mstore/types/funnelStage.ts create mode 100644 frontend/app/mstore/types/funnelnsights.ts create mode 100644 frontend/app/services/FunnelService.ts diff --git a/frontend/app/mstore/funnelStore.ts b/frontend/app/mstore/funnelStore.ts new file mode 100644 index 000000000..cd43065b3 --- /dev/null +++ b/frontend/app/mstore/funnelStore.ts @@ -0,0 +1,90 @@ +import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" +import { funnelService } from "App/services" +import Funnel, { IFunnel } from "./types/funnel"; +import Period, { LAST_7_DAYS } from 'Types/app/period'; + +export default class FunnelStore { + isLoading: boolean = false + isSaving: boolean = false + list: IFunnel[] = [] + instance: IFunnel | null = null + period: Period = Period({ rangeName: LAST_7_DAYS }) + + constructor() { + makeAutoObservable(this, { + fetchFunnels: action, + fetchFunnel: action, + saveFunnel: action, + deleteFunnel: action + }) + } + + fetchFunnels(): Promise { + this.isLoading = true + return new Promise((resolve, reject) => { + funnelService.all() + .then(response => { + this.list = response + resolve(response) + }).catch(error => { + reject(error) + }).finally(() => { + this.isLoading = false + } + ) + }) + } + + fetchFunnel(funnelId: string): Promise { + this.isLoading = true + return new Promise((resolve, reject) => { + funnelService.one(funnelId) + .then(response => { + const _funnel = new Funnel().fromJSON(response) + this.instance = _funnel + resolve(_funnel) + }).catch(error => { + reject(error) + }).finally(() => { + this.isLoading = false + } + ) + }) + } + + saveFunnel(funnel: IFunnel): Promise { + this.isSaving = true + const wasCreating = !funnel.funnelId + return new Promise((resolve, reject) => { + funnelService.save(funnel) + .then(response => { + const _funnel = new Funnel().fromJSON(response) + if (wasCreating) { + this.list.push(_funnel) + } + resolve(_funnel) + }).catch(error => { + reject(error) + }).finally(() => { + this.isSaving = false + } + ) + }) + } + + deleteFunnel(funnelId: string): Promise { + this.isSaving = true + return new Promise((resolve, reject) => { + funnelService.delete(funnelId) + .then(response => { + this.list = this.list.filter(funnel => funnel.funnelId !== funnelId) + resolve(funnelId) + }).catch(error => { + reject(error) + }).finally(() => { + this.isSaving = false + } + ) + }) + } +} \ No newline at end of file diff --git a/frontend/app/mstore/types/funnel.ts b/frontend/app/mstore/types/funnel.ts new file mode 100644 index 000000000..058587a66 --- /dev/null +++ b/frontend/app/mstore/types/funnel.ts @@ -0,0 +1,45 @@ +import Filter, { IFilter } from "./filter" + +export interface IFunnel { + funnelId: string + name: string + filter: IFilter + sessionsCount: number + conversionRate: number + totalConversations: number + lostConversations: number + fromJSON: (json: any) => void + toJSON: () => any +} + +export default class Funnel implements IFunnel { + funnelId: string = '' + name: string = '' + filter: IFilter = new Filter() + sessionsCount: number = 0 + conversionRate: number = 0 + totalConversations: number = 0 + lostConversations: number = 0 + + constructor() { + } + + fromJSON(json: any) { + this.funnelId = json.funnelId + this.name = json.name + this.filter = new Filter().fromJson(json.filter) + this.sessionsCount = json.sessionsCount + this.conversionRate = json.conversionRate + return this + } + + toJSON(): any { + return { + funnelId: this.funnelId, + name: this.name, + filter: this.filter.toJson(), + sessionsCount: this.sessionsCount, + conversionRate: this.conversionRate, + } + } +} \ No newline at end of file diff --git a/frontend/app/mstore/types/funnelStage.ts b/frontend/app/mstore/types/funnelStage.ts new file mode 100644 index 000000000..8714324f6 --- /dev/null +++ b/frontend/app/mstore/types/funnelStage.ts @@ -0,0 +1,18 @@ +export default class FunnelStage { + dropDueToIssues: number = 0; + dropPct: number = 0; + operator: string = ""; + sessionsCount: number = 0; + usersCount: number = 0; + value: string[] = []; + + fromJSON(json: any) { + this.dropDueToIssues = json.dropDueToIssues; + this.dropPct = json.dropPct; + this.operator = json.operator; + this.sessionsCount = json.sessionsCount; + this.usersCount = json.usersCount; + this.value = json.value; + return this; + } +} \ No newline at end of file diff --git a/frontend/app/mstore/types/funnelnsights.ts b/frontend/app/mstore/types/funnelnsights.ts new file mode 100644 index 000000000..8b1e22e6e --- /dev/null +++ b/frontend/app/mstore/types/funnelnsights.ts @@ -0,0 +1,12 @@ +import FunnelStage from "./funnelStage"; + +export default class FunnelInsights { + stages: FunnelStage[] = []; + totalDropDueToIssues: number = 0; + + fromJSON(json: any) { + this.stages = json.stages.map(stage => new FunnelStage().fromJSON(stage)); + this.totalDropDueToIssues = json.totalDropDueToIssues; + return this; + } +} \ No newline at end of file diff --git a/frontend/app/services/FunnelService.ts b/frontend/app/services/FunnelService.ts new file mode 100644 index 000000000..f5f9e4706 --- /dev/null +++ b/frontend/app/services/FunnelService.ts @@ -0,0 +1,62 @@ +import { IFunnel } from "App/mstore/types/funnel" +import APIClient from 'App/api_client'; + +export interface IFunnelService { + initClient(client?: APIClient) + all(): Promise + one(funnelId: string): Promise + save(funnel: IFunnel): Promise + delete(funnelId: string): Promise + + fetchInsights(funnelId: string, payload: any): Promise + fetchIssues(funnelId: string, payload: any): Promise + fetchIssue(funnelId: string, issueId: string): Promise +} + +export default class FunnelService implements IFunnelService { + private client: APIClient; + + constructor(client?: APIClient) { + this.client = client ? client : new APIClient(); + } + + initClient(client?: APIClient) { + this.client = client || new APIClient(); + } + + all(): Promise { + return this.client.get('/funnels') + .then(response => response.json()) + .then(response => response.data || []); + } + + one(funnelId: string): Promise { + return this.client.get(`/funnels/${funnelId}`) + .then(response => response.json()) + } + + save(funnel: IFunnel): Promise { + return this.client.post('/funnels', funnel) + .then(response => response.json()) + } + + delete(funnelId: string): Promise { + return this.client.delete(`/funnels/${funnelId}`) + .then(response => response.json()) + } + + fetchInsights(funnelId: string, payload: any): Promise { + return this.client.post(`/funnels/${funnelId}/insights`, payload) + .then(response => response.json()) + } + + fetchIssues(funnelId: string, payload: any): Promise { + return this.client.post(`/funnels/${funnelId}/issues`, payload) + .then(response => response.json()) + } + + fetchIssue(funnelId: string, issueId: string): Promise { + return this.client.get(`/funnels/${funnelId}/issues/${issueId}`) + .then(response => response.json()) + } +} \ No newline at end of file diff --git a/frontend/app/services/index.ts b/frontend/app/services/index.ts index 944877c16..2d7261f76 100644 --- a/frontend/app/services/index.ts +++ b/frontend/app/services/index.ts @@ -1,5 +1,7 @@ import DashboardService, { IDashboardService } from "./DashboardService"; import MetricService, { IMetricService } from "./MetricService"; +import FunnelService, { IFunnelService } from "./FunnelService"; export const dashboardService: IDashboardService = new DashboardService(); -export const metricService: IMetricService = new MetricService(); \ No newline at end of file +export const metricService: IMetricService = new MetricService(); +export const funnelService: IFunnelService = new FunnelService(); \ No newline at end of file From 3882128d4aad6bf855e08434faa3f50fa89a3fd0 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 21 Apr 2022 16:52:01 +0200 Subject: [PATCH 02/31] feat(ui) - funnels - wip --- frontend/app/Router.js | 9 ++++--- .../{FunnelItem.js => FunnelItem.js__} | 0 .../Funnels/FunnelItem/FunnelItem.tsx | 15 +++++++++++ .../{FunnelList.js => FunnelList.js__} | 0 .../Funnels/FunnelList/FunnelList.tsx | 27 +++++++++++++++++++ .../Funnels/FunnelPage/FunnelPage.tsx | 17 ++++++++++++ .../components/Funnels/FunnelPage/index.ts | 1 + frontend/app/components/Header/Header.js | 9 +++++++ frontend/app/mstore/index.tsx | 6 ++++- frontend/app/routes.js | 4 ++- 10 files changed, 83 insertions(+), 5 deletions(-) rename frontend/app/components/Funnels/FunnelItem/{FunnelItem.js => FunnelItem.js__} (100%) create mode 100644 frontend/app/components/Funnels/FunnelItem/FunnelItem.tsx rename frontend/app/components/Funnels/FunnelList/{FunnelList.js => FunnelList.js__} (100%) create mode 100644 frontend/app/components/Funnels/FunnelList/FunnelList.tsx create mode 100644 frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx create mode 100644 frontend/app/components/Funnels/FunnelPage/index.ts diff --git a/frontend/app/Router.js b/frontend/app/Router.js index ab41173bd..e3250f892 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -19,6 +19,7 @@ const DashboardPure = lazy(() => import('Components/Dashboard/NewDashboard')); const ErrorsPure = lazy(() => import('Components/Errors/Errors')); const FunnelDetails = lazy(() => import('Components/Funnels/FunnelDetails')); const FunnelIssueDetails = lazy(() => import('Components/Funnels/FunnelIssueDetails')); +const FunnelPagePure = lazy(() => import('Components/Funnels/FunnelPage')); import WidgetViewPure from 'Components/Dashboard/components/WidgetView'; import Header from 'Components/Header/Header'; // import ResultsModal from 'Shared/Results/ResultsModal'; @@ -41,13 +42,13 @@ import ModalRoot from './components/Modal/ModalRoot'; const BugFinder = withSiteIdUpdater(BugFinderPure); const Dashboard = withSiteIdUpdater(DashboardPure); -const WidgetView = withSiteIdUpdater(WidgetViewPure); const Session = withSiteIdUpdater(SessionPure); const LiveSession = withSiteIdUpdater(LiveSessionPure); const Assist = withSiteIdUpdater(AssistPure); const Client = withSiteIdUpdater(ClientPure); const Onboarding = withSiteIdUpdater(OnboardingPure); const Errors = withSiteIdUpdater(ErrorsPure); +const FunnelPage = withSiteIdUpdater(FunnelPagePure); const Funnels = withSiteIdUpdater(FunnelDetails); const FunnelIssue = withSiteIdUpdater(FunnelIssueDetails); const withSiteId = routes.withSiteId; @@ -66,7 +67,8 @@ const SESSIONS_PATH = routes.sessions(); const ASSIST_PATH = routes.assist(); const ERRORS_PATH = routes.errors(); const ERROR_PATH = routes.error(); -const FUNNEL_PATH = routes.funnel(); +const FUNNEL_PATH = routes.funnels(); +// const FUNNEL_DETAIL_PATH = routes.funnel(); const FUNNEL_ISSUE_PATH = routes.funnelIssue(); const SESSION_PATH = routes.session(); const LIVE_SESSION_PATH = routes.liveSession(); @@ -213,7 +215,8 @@ class Router extends React.Component { - + + {/* */} 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..1125eecbd --- /dev/null +++ b/frontend/app/components/Funnels/FunnelItem/FunnelItem.tsx @@ -0,0 +1,15 @@ +import { IFunnel } from 'App/mstore/types/funnel'; +import React from 'react'; + +interface Props { + funnel: IFunnel +} +function FunnelItem(props: Props) { + return ( +
+ +
+ ); +} + +export default FunnelItem; \ No newline at end of file diff --git a/frontend/app/components/Funnels/FunnelList/FunnelList.js b/frontend/app/components/Funnels/FunnelList/FunnelList.js__ similarity index 100% rename from frontend/app/components/Funnels/FunnelList/FunnelList.js rename to frontend/app/components/Funnels/FunnelList/FunnelList.js__ diff --git a/frontend/app/components/Funnels/FunnelList/FunnelList.tsx b/frontend/app/components/Funnels/FunnelList/FunnelList.tsx new file mode 100644 index 000000000..d14efe93f --- /dev/null +++ b/frontend/app/components/Funnels/FunnelList/FunnelList.tsx @@ -0,0 +1,27 @@ +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import React, { useEffect } from 'react'; + +function FunnelList(props) { + const { funnelStore } = useStore() + const list = useObserver(() => funnelStore.list) + + useEffect(() => { + if (list.length === 0) { + funnelStore.fetchFunnels() + } + }, []) + + return ( +
+
here
+ {list.map(funnel => ( +
+

{funnel.name}

+
+ ))} +
+ ); +} + +export default 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..c26c4be08 --- /dev/null +++ b/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Switch, Route } from 'react-router'; +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/Header/Header.js b/frontend/app/components/Header/Header.js index 219463499..4982a302e 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, @@ -30,6 +31,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; @@ -99,6 +101,13 @@ const Header = (props) => { { 'Errors' } + + { 'Funnels' } + hashed(`/assist/$ export const errors = params => queried('/errors', params); export const error = (id = ':errorId', hash) => hashed(`/errors/${ id }`, hash); +export const funnels = params => queried('/funnels', params) export const funnel = (id = ':funnelId', hash) => hashed(`/funnels/${ id }`, hash); export const funnelIssue = (id = ':funnelId', issueId = ':issueId', hash) => hashed(`/funnels/${ id }/${ issueId}`, hash); @@ -110,7 +111,6 @@ export const metrics = () => `/metrics`; export const metricCreate = () => `/metrics/create`; export const metricDetails = (id = ':metricId', hash) => hashed(`/metrics/${ id }`, hash); - export const RESULTS_QUERY_KEY = 'results'; export const METRICS_QUERY_KEY = 'metrics'; export const SOURCE_QUERY_KEY = 'source'; @@ -134,6 +134,7 @@ const REQUIRED_SITE_ID_ROUTES = [ error(''), errors(), onboarding(''), + funnels(''), funnel(''), funnelIssue(''), ]; @@ -161,6 +162,7 @@ export function isRoute(route, path){ const SITE_CHANGE_AVALIABLE_ROUTES = [ sessions(), + funnels(), assist(), dashboard(), metrics(), From 4907c1b26c1649b6d2e894ea4d175ba1a0d35330 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 22 Apr 2022 14:47:38 +0200 Subject: [PATCH 03/31] feat(ui) - funnels listing --- .../Funnels/FunnelItem/FunnelItem.tsx | 13 +++++- .../Funnels/FunnelList/FunnelList.tsx | 42 +++++++++++++++---- .../Funnels/FunnelPage/FunnelPage.tsx | 2 +- .../app/components/ui/PageTitle/PageTitle.tsx | 7 +++- frontend/app/mstore/funnelStore.ts | 8 ++++ frontend/app/mstore/types/funnel.ts | 2 + frontend/app/styles/general.css | 1 + frontend/app/svg/icons/funnel-fill.svg | 3 ++ frontend/app/theme/colors.js | 2 + 9 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 frontend/app/svg/icons/funnel-fill.svg diff --git a/frontend/app/components/Funnels/FunnelItem/FunnelItem.tsx b/frontend/app/components/Funnels/FunnelItem/FunnelItem.tsx index 1125eecbd..2c0793745 100644 --- a/frontend/app/components/Funnels/FunnelItem/FunnelItem.tsx +++ b/frontend/app/components/Funnels/FunnelItem/FunnelItem.tsx @@ -1,13 +1,22 @@ 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}
+
{funnel.name}
); } diff --git a/frontend/app/components/Funnels/FunnelList/FunnelList.tsx b/frontend/app/components/Funnels/FunnelList/FunnelList.tsx index d14efe93f..b73b643c2 100644 --- a/frontend/app/components/Funnels/FunnelList/FunnelList.tsx +++ b/frontend/app/components/Funnels/FunnelList/FunnelList.tsx @@ -1,10 +1,14 @@ +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'; function FunnelList(props) { const { funnelStore } = useStore() const list = useObserver(() => funnelStore.list) + const loading = useObserver(() => funnelStore.isLoading) useEffect(() => { if (list.length === 0) { @@ -13,14 +17,38 @@ function FunnelList(props) { }, []) return ( -
-
here
- {list.map(funnel => ( -
-

{funnel.name}

+ +
+ + +
+ +
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} + /> +
+ ); } diff --git a/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx b/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx index c26c4be08..f4b3da812 100644 --- a/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx +++ b/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx @@ -4,7 +4,7 @@ import FunnelList from '../FunnelList'; function FunnelPage(props) { return ( -
+
diff --git a/frontend/app/components/ui/PageTitle/PageTitle.tsx b/frontend/app/components/ui/PageTitle/PageTitle.tsx index 6ee102c0d..1d94bdf3c 100644 --- a/frontend/app/components/ui/PageTitle/PageTitle.tsx +++ b/frontend/app/components/ui/PageTitle/PageTitle.tsx @@ -1,7 +1,12 @@ import React from 'react'; import cn from 'classnames'; -function PageTitle({ title, className = '' }) { +interface Props { + title: string; + className?: string; +} +function PageTitle(props: Props) { + const { title, className = '' } = props; return (

{title} diff --git a/frontend/app/mstore/funnelStore.ts b/frontend/app/mstore/funnelStore.ts index cd43065b3..721adeb6b 100644 --- a/frontend/app/mstore/funnelStore.ts +++ b/frontend/app/mstore/funnelStore.ts @@ -9,9 +9,13 @@ export default class FunnelStore { list: IFunnel[] = [] instance: IFunnel | null = null period: Period = Period({ rangeName: LAST_7_DAYS }) + + page: number = 1 + pageSize: number = 10 constructor() { makeAutoObservable(this, { + updateKey: action, fetchFunnels: action, fetchFunnel: action, saveFunnel: action, @@ -19,6 +23,10 @@ export default class FunnelStore { }) } + updateKey(key: string, value: any) { + this[key] = value + } + fetchFunnels(): Promise { this.isLoading = true return new Promise((resolve, reject) => { diff --git a/frontend/app/mstore/types/funnel.ts b/frontend/app/mstore/types/funnel.ts index 058587a66..95c95e5c4 100644 --- a/frontend/app/mstore/types/funnel.ts +++ b/frontend/app/mstore/types/funnel.ts @@ -8,6 +8,7 @@ export interface IFunnel { conversionRate: number totalConversations: number lostConversations: number + isPublic: boolean fromJSON: (json: any) => void toJSON: () => any } @@ -20,6 +21,7 @@ export default class Funnel implements IFunnel { conversionRate: number = 0 totalConversations: number = 0 lostConversations: number = 0 + isPublic: boolean = false constructor() { } diff --git a/frontend/app/styles/general.css b/frontend/app/styles/general.css index 7e99468ad..ecd424721 100644 --- a/frontend/app/styles/general.css +++ b/frontend/app/styles/general.css @@ -255,6 +255,7 @@ p { .link { color: $blue !important; + cursor: pointer; &:hover { text-decoration: underline !important; } diff --git a/frontend/app/svg/icons/funnel-fill.svg b/frontend/app/svg/icons/funnel-fill.svg new file mode 100644 index 000000000..5f16f16ae --- /dev/null +++ b/frontend/app/svg/icons/funnel-fill.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 07aca3a8b..963c71eec 100644 --- a/frontend/app/theme/colors.js +++ b/frontend/app/theme/colors.js @@ -16,6 +16,8 @@ module.exports = { "tealx-light": "#E2F0EE", "tealx-light-border": "#C6DCDA", + "tealx-lightest": 'rgba(62, 170, 175, 0.1)', + orange: "#E28940", yellow: "#FFFBE5", yellow2: "#F5A623", From fb44ff70fe220ddc2e6838ef912638f0b442fa63 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 22 Apr 2022 19:07:01 +0200 Subject: [PATCH 04/31] feat(ui) - funnels wip --- frontend/app/Router.js | 7 ++-- .../{FunnelDetails.js => FunnelDetails.js__} | 0 .../Funnels/FunnelDetails/FunnelDetails.tsx | 15 ++++++++ .../Funnels/FunnelList/FunnelList.tsx | 10 ++++-- .../Funnels/FunnelPage/FunnelPage.tsx | 9 +++++ .../Funnels/FunnelSearch/FunnelSearch.tsx | 34 +++++++++++++++++++ .../components/Funnels/FunnelSearch/index.ts | 1 + frontend/app/mstore/funnelStore.ts | 1 + frontend/app/routes.js | 2 ++ 9 files changed, 74 insertions(+), 5 deletions(-) rename frontend/app/components/Funnels/FunnelDetails/{FunnelDetails.js => FunnelDetails.js__} (100%) create mode 100644 frontend/app/components/Funnels/FunnelDetails/FunnelDetails.tsx create mode 100644 frontend/app/components/Funnels/FunnelSearch/FunnelSearch.tsx create mode 100644 frontend/app/components/Funnels/FunnelSearch/index.ts diff --git a/frontend/app/Router.js b/frontend/app/Router.js index e3250f892..cc31ea640 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -17,7 +17,7 @@ const AssistPure = lazy(() => import('Components/Assist')); const BugFinderPure = lazy(() => import('Components/BugFinder/BugFinder')); const DashboardPure = lazy(() => import('Components/Dashboard/NewDashboard')); const ErrorsPure = lazy(() => import('Components/Errors/Errors')); -const FunnelDetails = lazy(() => import('Components/Funnels/FunnelDetails')); +const FunnelDetailsPure = lazy(() => import('Components/Funnels/FunnelDetails')); const FunnelIssueDetails = lazy(() => import('Components/Funnels/FunnelIssueDetails')); const FunnelPagePure = lazy(() => import('Components/Funnels/FunnelPage')); import WidgetViewPure from 'Components/Dashboard/components/WidgetView'; @@ -49,7 +49,7 @@ const Client = withSiteIdUpdater(ClientPure); const Onboarding = withSiteIdUpdater(OnboardingPure); const Errors = withSiteIdUpdater(ErrorsPure); const FunnelPage = withSiteIdUpdater(FunnelPagePure); -const Funnels = withSiteIdUpdater(FunnelDetails); +const FunnelsDetails = withSiteIdUpdater(FunnelDetailsPure); const FunnelIssue = withSiteIdUpdater(FunnelIssueDetails); const withSiteId = routes.withSiteId; const withObTab = routes.withObTab; @@ -68,7 +68,7 @@ const ASSIST_PATH = routes.assist(); const ERRORS_PATH = routes.errors(); const ERROR_PATH = routes.error(); const FUNNEL_PATH = routes.funnels(); -// const FUNNEL_DETAIL_PATH = routes.funnel(); +const FUNNEL_CREATE_PATH = routes.funnelsCreate(); const FUNNEL_ISSUE_PATH = routes.funnelIssue(); const SESSION_PATH = routes.session(); const LIVE_SESSION_PATH = routes.liveSession(); @@ -216,6 +216,7 @@ class Router extends React.Component { + {/* */} 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/FunnelList/FunnelList.tsx b/frontend/app/components/Funnels/FunnelList/FunnelList.tsx index b73b643c2..ca18a3c81 100644 --- a/frontend/app/components/Funnels/FunnelList/FunnelList.tsx +++ b/frontend/app/components/Funnels/FunnelList/FunnelList.tsx @@ -4,6 +4,7 @@ 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() @@ -19,8 +20,13 @@ function FunnelList(props) { return (
- - +
+ + +
+
+ +
Funnels make it easy to uncover the most significant issues that impacted conversions.
diff --git a/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx b/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx index f4b3da812..57e2c7fe0 100644 --- a/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx +++ b/frontend/app/components/Funnels/FunnelPage/FunnelPage.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { Switch, Route } from 'react-router'; +import FunnelDetails from '../FunnelDetails/FunnelDetails'; import FunnelList from '../FunnelList'; function FunnelPage(props) { @@ -9,6 +10,14 @@ function FunnelPage(props) { + + + + + + {/* + + */}

); 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/mstore/funnelStore.ts b/frontend/app/mstore/funnelStore.ts index 721adeb6b..1913b2506 100644 --- a/frontend/app/mstore/funnelStore.ts +++ b/frontend/app/mstore/funnelStore.ts @@ -9,6 +9,7 @@ export default class FunnelStore { list: IFunnel[] = [] instance: IFunnel | null = null period: Period = Period({ rangeName: LAST_7_DAYS }) + search: string = '' page: number = 1 pageSize: number = 10 diff --git a/frontend/app/routes.js b/frontend/app/routes.js index da458f46b..fd8859935 100644 --- a/frontend/app/routes.js +++ b/frontend/app/routes.js @@ -92,6 +92,7 @@ export const errors = params => queried('/errors', params); export const error = (id = ':errorId', hash) => hashed(`/errors/${ id }`, hash); export const funnels = params => queried('/funnels', params) +export const funnelsCreate = () => `/funnels/create`; export const funnel = (id = ':funnelId', hash) => hashed(`/funnels/${ id }`, hash); export const funnelIssue = (id = ':funnelId', issueId = ':issueId', hash) => hashed(`/funnels/${ id }/${ issueId}`, hash); @@ -135,6 +136,7 @@ const REQUIRED_SITE_ID_ROUTES = [ errors(), onboarding(''), funnels(''), + funnelsCreate(''), funnel(''), funnelIssue(''), ]; From 3bb5d9fabd0d86ede924c6fbce8b173cca160699 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 10 May 2022 17:17:15 +0200 Subject: [PATCH 05/31] 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", From d619083a85bf0205e904460bf55d1803c9255676 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 10 May 2022 17:37:27 +0200 Subject: [PATCH 06/31] feat(ui) - funnels - step toggle --- .../Funnels/FunnelWidget/FunnelWidget.css | 7 +++++++ .../Funnels/FunnelWidget/FunnelWidget.tsx | 13 +++++++------ frontend/app/mstore/types/filterItem.ts | 12 +++++++----- frontend/app/svg/icons/eye-slash-fill.svg | 4 ++++ 4 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 frontend/app/svg/icons/eye-slash-fill.svg diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.css b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.css index 6cca14777..0102e4d9f 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.css +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.css @@ -1,6 +1,7 @@ .step { /* display: flex; */ position: relative; + transition: all 0.5s ease; &:before { content: ''; border-left: 2px solid $gray-lightest; @@ -15,4 +16,10 @@ &: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 index b71ea78bc..981111256 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelWidget.tsx @@ -1,7 +1,6 @@ import React from 'react'; -import Funnel from 'App/mstore/types/funnel'; import Widget from 'App/mstore/types/widget'; -import Funnelbar from './FunnelBar' +import Funnelbar from './FunnelBar'; import cn from 'classnames'; import stl from './FunnelWidget.css'; import { Icon } from 'UI'; @@ -16,13 +15,15 @@ function FunnelWidget(props: Props) { <>
{metric.series[0].filter.filters.filter(f => f.isEvent).map((filter, index) => ( -
+
{index + 1}
- -
- + +
+
))} diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts index c88d07db5..45442cc4d 100644 --- a/frontend/app/mstore/types/filterItem.ts +++ b/frontend/app/mstore/types/filterItem.ts @@ -14,6 +14,7 @@ export default class FilterItem { filters: FilterItem[] = [] operatorOptions: any[] = [] options: any[] = [] + isActive: boolean = true constructor(data: any = {}) { makeAutoObservable(this, { @@ -23,19 +24,20 @@ export default class FilterItem { operator: observable, source: observable, filters: observable, + isActive: observable, merge: action }) - if (Array.isArray(data.filters)) { - data.filters = data.filters.map(function (i) { - return new FilterItem(i); - }); - } + this.merge(data) } + updateKey(key: string, value: any) { + this[key] = value + } + merge(data) { Object.keys(data).forEach(key => { this[key] = data[key] diff --git a/frontend/app/svg/icons/eye-slash-fill.svg b/frontend/app/svg/icons/eye-slash-fill.svg new file mode 100644 index 000000000..dbd0e9889 --- /dev/null +++ b/frontend/app/svg/icons/eye-slash-fill.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From 0174e265e0592ed96f1f95339f0a844cd044fade Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 10 May 2022 17:50:50 +0200 Subject: [PATCH 07/31] feat(ui) - funnels - step percentage --- .../components/Funnels/FunnelWidget/FunnelBar.tsx | 8 +++++--- frontend/app/mstore/metricStore.ts | 12 ++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx index 89d6efdb0..4e35ce2f3 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx @@ -22,7 +22,7 @@ function FunnelBar(props: Props) { borderRadius: '3px', overflow: 'hidden', }}> -
+ }}> +
10%
+
@@ -39,7 +41,7 @@ function FunnelBar(props: Props) { completed
- + 20 Dropped off
diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 9442dccbf..2c60141b3 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -206,12 +206,20 @@ const sampleJson = { value: ['/sessions', '/errors', '/users'], percent: 100, completed: 60, - dropped: 0, + dropped: 40, }, { type: 'LOCATION', operator: 'is', - value: ['/sessions', '/errors', '/users'], + value: ['/sessions'], + percent: 80, + completed: 40, + dropped: 20, + }, + { + type: 'CLICK', + operator: 'on', + value: ['DASHBOARDS'], percent: 80, completed: 40, dropped: 20, From 9ecb4c369e9aa750e75c773ca9929a5dec102d95 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 10 May 2022 18:03:19 +0200 Subject: [PATCH 08/31] feat(ui) - funnels - step percentage dynamic --- .../Funnels/FunnelWidget/FunnelBar.tsx | 21 ++++++++++++++----- frontend/app/mstore/metricStore.ts | 8 +++---- frontend/app/mstore/types/filterItem.ts | 5 +++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx index 4e35ce2f3..5530a550c 100644 --- a/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx +++ b/frontend/app/components/Funnels/FunnelWidget/FunnelBar.tsx @@ -8,7 +8,18 @@ interface Props { filter: any; }`` function FunnelBar(props: Props) { - const { completed, dropped, filter } = props; + const { filter } = props; + const { completed, dropped } = filter; + + const calculatePercentage = (completed: number, dropped: number) => { + const total = completed + dropped; + if (total === 0) { + return 0; + } + return Math.round((completed / total) * 100); + } + const completedPercentage = calculatePercentage(completed, dropped); + console.log('completedPercentage', completedPercentage) return (
@@ -23,7 +34,7 @@ function FunnelBar(props: Props) { overflow: 'hidden', }}>
-
10%
+
{completedPercentage}%
- {13} + {completed} completed
- 20 + {dropped} Dropped off
diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 2c60141b3..eb9147620 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -192,7 +192,7 @@ export default class MetricStore implements IMetricStore { } const sampleJson = { - metricId: 1, + // metricId: 1, name: "Funnel Sample", metricType: 'funnel', series: [ @@ -214,15 +214,15 @@ const sampleJson = { value: ['/sessions'], percent: 80, completed: 40, - dropped: 20, + dropped: 60, }, { type: 'CLICK', operator: 'on', value: ['DASHBOARDS'], percent: 80, - completed: 40, - dropped: 20, + completed: 10, + dropped: 90, } ] } diff --git a/frontend/app/mstore/types/filterItem.ts b/frontend/app/mstore/types/filterItem.ts index 45442cc4d..291fd6f8a 100644 --- a/frontend/app/mstore/types/filterItem.ts +++ b/frontend/app/mstore/types/filterItem.ts @@ -15,6 +15,8 @@ export default class FilterItem { operatorOptions: any[] = [] options: any[] = [] isActive: boolean = true + completed: number = 0 + dropped: number = 0 constructor(data: any = {}) { makeAutoObservable(this, { @@ -65,6 +67,9 @@ export default class FilterItem { this.operator = json.operator this.filters = _filter.type === FilterType.SUB_FILTERS && json.filters ? json.filters.map(i => new FilterItem().fromJson(i, json.type)) : [] + + this.completed = json.completed + this.dropped = json.dropped return this } From 34425b8b0267d6a8a962e33870797369c0cc553c Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Tue, 10 May 2022 19:25:08 +0200 Subject: [PATCH 09/31] feat(ui) - funnels - check for table and funnel --- .../components/WidgetChart/WidgetChart.tsx | 5 +++-- .../components/WidgetForm/WidgetForm.tsx | 9 +++++---- .../components/WidgetView/WidgetView.tsx | 2 +- .../Funnels/FunnelWidget/FunnelBar.tsx | 19 +++++++++---------- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx index e95f47387..c3d40867e 100644 --- a/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx +++ b/frontend/app/components/Dashboard/components/WidgetChart/WidgetChart.tsx @@ -33,6 +33,7 @@ function WidgetChart(props: Props) { const isTableWidget = metric.metricType === 'table' && metric.viewType === 'table'; const isPieChart = metric.metricType === 'table' && metric.viewType === 'pieChart'; + const isFunnel = metric.metricType === 'funnel'; const onChartClick = (event: any) => { if (event) { @@ -82,7 +83,7 @@ function WidgetChart(props: Props) { const renderChart = () => { const { metricType, viewType } = metric; - if (metricType === 'funnel') { + if (isFunnel) { return } @@ -136,7 +137,7 @@ function WidgetChart(props: Props) { return
Unknown
; } return useObserver(() => ( - + {renderChart()} )); diff --git a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx index 79b44bc5a..4659a4494 100644 --- a/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx +++ b/frontend/app/components/Dashboard/components/WidgetForm/WidgetForm.tsx @@ -27,6 +27,7 @@ function WidgetForm(props: Props) { const timeseriesOptions = metricOf.filter(i => i.type === 'timeseries'); const tableOptions = metricOf.filter(i => i.type === 'table'); const isTable = metric.metricType === 'table'; + const isFunnel = metric.metricType === 'funnel'; const _issueOptions = [{ text: 'All', value: 'all' }].concat(issueOptions); const canAddToDashboard = metric.exists() && dashboards.length > 0; const canAddSeries = metric.series.length < 3; @@ -87,7 +88,7 @@ function WidgetForm(props: Props) { } return useObserver(() => ( -
+
@@ -152,8 +153,8 @@ function WidgetForm(props: Props) {
- {`${isTable ? 'Filter by' : 'Chart Series'}`} - {!isTable && ( + {`${(isTable || isFunnel) ? 'Filter by' : 'Chart Series'}`} + {!isTable && !isFunnel && ( + + ), + Placeholder: () => null, + SingleValue: () => null, + }} + /> + +
+ ); +} + +export default FunnelIssuesDropdown; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesDropdown/index.ts b/frontend/app/components/Dashboard/components/FunnelIssuesDropdown/index.ts new file mode 100644 index 000000000..7b8b3555e --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssuesDropdown/index.ts @@ -0,0 +1 @@ +export { default } from './FunnelIssuesDropdown'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx b/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx new file mode 100644 index 000000000..c36ad5044 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Icon } from 'UI'; + +interface Props { + options: any[]; + removeSelectedValue: (value: string) => void; +} +function FunnelIssuesSelectedFilters(props: Props) { + const { options, removeSelectedValue } = props; + return ( +
+ {options.map((option, index) => ( +
+ {option.label} + +
+ ))} +
+ ); +} + +export default FunnelIssuesSelectedFilters; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/index.ts b/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/index.ts new file mode 100644 index 000000000..a35f23d5f --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/index.ts @@ -0,0 +1 @@ +export { default } from './FunnelIssuesSelectedFilters'; \ No newline at end of file diff --git a/frontend/app/components/shared/Select/Select.tsx b/frontend/app/components/shared/Select/Select.tsx index a57be7e87..6cc11cbb4 100644 --- a/frontend/app/components/shared/Select/Select.tsx +++ b/frontend/app/components/shared/Select/Select.tsx @@ -9,27 +9,44 @@ interface Props { defaultValue?: string; plain?: boolean; components?: any; + styles?: any; [x:string]: any; } -export default function({ alignRight = false, plain = false, options, isSearchable = false, components = {}, defaultValue = '', ...rest }: Props) { +export default function({ styles= {}, alignRight = false, plain = false, options, isSearchable = false, components = {}, defaultValue = '', ...rest }: Props) { const defaultSelected = defaultValue ? options.find(x => x.value === defaultValue) : null; const customStyles = { option: (provided, state) => ({ - ...provided, - whiteSpace: 'nowrap', + ...provided, + whiteSpace: 'nowrap', + transition: 'all 0.3s', + backgroundColor: state.isFocused ? colors['active-blue'] : 'transparent', + color: state.isFocused ? colors.teal : 'black', + '&:hover': { + transition: 'all 0.2s', + backgroundColor: colors['active-blue'], + }, + '&:focus': { + transition: 'all 0.2s', + backgroundColor: colors['active-blue'], + } }), menu: (provided, state) => ({ ...provided, top: 31, - border: 'solid thin #ccc', + border: `1px solid ${colors['gray-light']}`, borderRadius: '3px', backgroundColor: '#fff', - boxShadow: '0px 0px 10px rgba(0, 0, 0, 0.1)', + boxShadow: '1px 1px 1px rgba(0, 0, 0, 0.1)', position: 'absolute', minWidth: 'fit-content', + overflow: 'hidden', ...(alignRight && { right: 0 }) }), + menuList: (provided, state) => ({ + ...provided, + padding: 0, + }), control: (provided) => { const obj = { ...provided, @@ -63,7 +80,12 @@ export default function({ alignRight = false, plain = false, options, isSearchab const transition = 'opacity 300ms'; return { ...provided, opacity, transition }; - } + }, + noOptionsMessage: (provided) => ({ + ...provided, + whiteSpace: 'nowrap !important', + // minWidth: 'fit-content', + }), } @@ -77,7 +99,7 @@ export default function({ alignRight = false, plain = false, options, isSearchab DropdownIndicator, ...components, }} - styles={customStyles} + styles={{ ...customStyles, ...styles }} theme={(theme) => ({ ...theme, colors: { From f40403f4e9adc6fce884cca392cbb1a9987fdebb Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Thu, 12 May 2022 14:31:44 +0200 Subject: [PATCH 14/31] feat(ui) - funnels - issues filters --- .../FunnelIssuesList/FunnelIssuesList.tsx | 21 +++++++++++++++++++ .../components/FunnelIssuesList/index.ts | 1 + .../FunnelIssuesListItem.tsx | 14 +++++++++++++ .../components/FunnelIssuesListItem/index.ts | 1 + .../FunnelIssuesDropdown.tsx | 13 +++++++++--- .../FunnelIssuesSelectedFilters.tsx | 10 ++++++--- frontend/app/mstore/funnelStore.ts | 1 + 7 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx create mode 100644 frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/index.ts create mode 100644 frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx create mode 100644 frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/index.ts diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx new file mode 100644 index 000000000..8a6f05c6d --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx @@ -0,0 +1,21 @@ +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import React from 'react'; +import FunnelIssuesListItem from '../FunnelIssuesListItem'; + +function FunnelIssuesList(props) { + const { funnelStore } = useStore(); + const issues = useObserver(() => funnelStore.issues); + + return ( +
+ {issues.map((issue, index) => ( +
+ +
+ ))} +
+ ); +} + +export default FunnelIssuesList; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/index.ts b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/index.ts new file mode 100644 index 000000000..8bab257bb --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/index.ts @@ -0,0 +1 @@ +export { default } from './FunnelIssuesList'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx new file mode 100644 index 000000000..6e5bb65c0 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +interface Props { + issue: any; +} +function FunnelIssuesListItem(props) { + return ( +
+ list item +
+ ); +} + +export default FunnelIssuesListItem; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/index.ts b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/index.ts new file mode 100644 index 000000000..f8237e361 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/index.ts @@ -0,0 +1 @@ +export { default } from './FunnelIssuesListItem'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx b/frontend/app/components/Dashboard/components/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx index 7ca5757cd..7bf620cd1 100644 --- a/frontend/app/components/Dashboard/components/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx +++ b/frontend/app/components/Dashboard/components/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx @@ -1,8 +1,9 @@ -import React, { Component, ReactNode, FunctionComponent } from 'react'; +import React, { Component, ReactNode, FunctionComponent, useEffect } from 'react'; import Select from 'Shared/Select' import { components } from 'react-select'; import { Icon } from 'UI'; import FunnelIssuesSelectedFilters from '../FunnelIssuesSelectedFilters'; +import { useStore } from 'App/mstore'; const options = [ { value: "click_rage", label: "Click Rage" }, @@ -20,15 +21,21 @@ const options = [ ] function FunnelIssuesDropdown(props) { + const { funnelStore } = useStore(); const [isOpen, setIsOpen] = React.useState(false); - const [selectedValues, setSelectedValues] = React.useState(options.map(option => option.value)); + const [selectedValues, setSelectedValues] = React.useState([]); const filteredOptions = options.filter((option: any) => { return !selectedValues.includes(option.value); }); + const selectedOptions = options.filter((option: any) => { return selectedValues.includes(option.value); }); + useEffect(() => { + funnelStore.updateKey('issuesFilter', selectedOptions); + }, [selectedOptions]); + const handleChange = ({ value }: any) => { toggleSelectedValue(value); } @@ -81,7 +88,7 @@ function FunnelIssuesDropdown(props) { SingleValue: () => null, }} /> - +
); } diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx b/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx index c36ad5044..a4bb3909e 100644 --- a/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx +++ b/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx @@ -1,15 +1,19 @@ import React from 'react'; import { Icon } from 'UI'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; interface Props { - options: any[]; removeSelectedValue: (value: string) => void; } function FunnelIssuesSelectedFilters(props: Props) { - const { options, removeSelectedValue } = props; + const { funnelStore } = useStore(); + const issuesFilter = useObserver(() => funnelStore.issuesFilter); + const { removeSelectedValue } = props; + return (
- {options.map((option, index) => ( + {issuesFilter.map((option, index) => (
{option.label}
diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx new file mode 100644 index 000000000..4a69dc280 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { Popup } from 'UI'; + +const MIN_WIDTH = '20px'; +interface Props { + issue: any +} +function FunnelIssueGraph(props: Props) { + const { issue } = props; + return ( +
+ +
+
{issue.unaffectedSessions}
+
+ } + content={ `Unaffected sessions` } + size="tiny" + inverted + position="top center" + /> + +
+
{issue.affectedSessions}
+ {/*
{issue.affectedSessionsPer}
*/} +
+ } + content={ `Affected sessions` } + size="tiny" + inverted + position="top center" + /> + +
+
{issue.lostConversions}
+
+ } + content={ `Conversion lost` } + size="tiny" + inverted + position="top center" + /> +
+ ); +} + +export default FunnelIssueGraph; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/index.ts b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/index.ts new file mode 100644 index 000000000..11aed8599 --- /dev/null +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/index.ts @@ -0,0 +1 @@ +export { default } from './FunnelIssueGraph'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx index 8a6f05c6d..839ca35dd 100644 --- a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx @@ -5,12 +5,14 @@ import FunnelIssuesListItem from '../FunnelIssuesListItem'; function FunnelIssuesList(props) { const { funnelStore } = useStore(); + const issuesFilter = useObserver(() => funnelStore.issuesFilter.map((issue: any) => issue.value)); const issues = useObserver(() => funnelStore.issues); + const filteredIssues = useObserver(() => issuesFilter.length > 0 ? issues.filter((issue: any) => issuesFilter.includes(issue.type)) : issues); return (
- {issues.map((issue, index) => ( -
+ {filteredIssues.map((issue, index) => ( +
))} diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx index 6e5bb65c0..4adfbeb31 100644 --- a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx +++ b/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx @@ -1,14 +1,85 @@ import React from 'react'; +import cn from 'classnames'; +import { Icon, TextEllipsis } from 'UI'; +import FunnelIssueGraph from '../FunnelIssueGraph'; interface Props { issue: any; + inDetails?: boolean; } function FunnelIssuesListItem(props) { + const { issue, inDetails = false } = props; + const onClick = () => {} return ( -
- list item -
+
null}> + {/* {inDetails && ( + + )} */} +
+
+
+ +
+
+ + {inDetails && ( +
+
{issue.title}
+
+ +
+
+ )} + + {!inDetails && ( +
+
{issue.title}
+
+ +
+
+ )} + +
+
{issue.affectedUsers}
+
Affected Users
+
+ +
+
{issue.conversionImpact}%
+
Conversion Impact
+
+ +
+
{issue.lostConversions}
+
Lost Conversions
+
+
+ {inDetails && ( +
+ +
+ + + +
+
+ )} +
); } -export default FunnelIssuesListItem; \ No newline at end of file +export default FunnelIssuesListItem; + +const Info = ({ label = '', color = 'red'}) => { + return ( +
+
+
+
{ label }
+
+
+ ) + } \ No newline at end of file diff --git a/frontend/app/components/shared/Select/Select.tsx b/frontend/app/components/shared/Select/Select.tsx index 6cc11cbb4..f7e834561 100644 --- a/frontend/app/components/shared/Select/Select.tsx +++ b/frontend/app/components/shared/Select/Select.tsx @@ -41,6 +41,7 @@ export default function({ styles= {}, alignRight = false, plain = false, options position: 'absolute', minWidth: 'fit-content', overflow: 'hidden', + zIndex: 100, ...(alignRight && { right: 0 }) }), menuList: (provided, state) => ({ diff --git a/frontend/app/mstore/funnelStore.ts b/frontend/app/mstore/funnelStore.ts index c339de92f..ad1020a1a 100644 --- a/frontend/app/mstore/funnelStore.ts +++ b/frontend/app/mstore/funnelStore.ts @@ -27,6 +27,7 @@ export default class FunnelStore { saveFunnel: action, deleteFunnel: action }) + this.issues = sampleIssues.map(i => new FunnelIssue().fromJSON(i)); } updateKey(key: string, value: any) { @@ -117,4 +118,7 @@ export default class FunnelStore { }) }) } -} \ No newline at end of file +} + + +const sampleIssues = [{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/assist-story-4.svg","issueId":"9157d556854f17cc25df3510bf7a980fd4d"},{"type":"click_rage","title":"Click Rage","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"Back More Info 0:40 78:02 Pause Back Skip to Issue 1x Skip Inactivity Network Fetch 6 Redux 2 Consol","issueId":"91502c2c13f69c09a68503c61b0fd4461ca"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/icn-slack.svg","issueId":"915b1b3a25c5f1127ec762235be7f896c3a"},{"type":"dead_click","title":"Dead Click","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"py-1 px-2 bg-white border border-gray-light rounded w-16","issueId":"9159e30220bb6a6a31afcaa1979a0c7d69c"},{"type":"dead_click","title":"Dead Click","affectedSessions":4,"unaffectedSessions":56,"lostConversions":2,"affectedUsers":2,"conversionImpact":61,"contextString":"OpenReplay App New Project SESSIONS ASSIST ERRORS DASHBOARDS Billing Details Announcements There are","issueId":"91515f9118ed803291f87133e2cb49a16ea"},{"type":"dead_click","title":"Dead Click","affectedSessions":2,"unaffectedSessions":58,"lostConversions":0,"affectedUsers":1,"conversionImpact":20,"contextString":"Type to search","issueId":"915832d68d21f03f83af1bfc758a1dda50b"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/icn-linkedin.svg","issueId":"91506bb929c2cb3679f8b01c228d8a0b5c8"},{"type":"dead_click","title":"Dead Click","affectedSessions":3,"unaffectedSessions":57,"lostConversions":0,"affectedUsers":2,"conversionImpact":31,"contextString":"Search sessions using any captured event (click, input, page, error...)","issueId":"9157be39a537e81243a2ff44ad74867941f"},{"type":"cpu","title":"High CPU","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"","issueId":"915a68d6bb4448b5822836dbc797bafadf9"},{"type":"dead_click","title":"Dead Click","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"Back More Info Report Issue This session's issues 10:35 83:19 Play Back Skip to Issue 4x Skip Inacti","issueId":"915b43e81f8da042f70ea47bd9ad14a3bb8"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/icn-git.svg","issueId":"915f7f277daa7a695d5bf9e233c43af7f02"},{"type":"click_rage","title":"Click Rage","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"Back More Info 0:35 78:02 Pause Back Skip to Issue 1x Skip Inactivity Network Fetch 6 Redux 2 Consol","issueId":"915788d9976c9f80c6f599e3e5816f2c7be"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/icn-twitter.svg","issueId":"915ab993784a0432c39b4a4e9248dfe6acd"},{"type":"missing_resource","title":"Missing Image","affectedSessions":1,"unaffectedSessions":59,"lostConversions":0,"affectedUsers":1,"conversionImpact":11,"contextString":"https://openreplay.com/images/logo-open-replay.svg","issueId":"915ac8719c95392adb8b79d2d5eae1063b9"}] \ No newline at end of file diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index eb9147620..0f4163711 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -192,7 +192,7 @@ export default class MetricStore implements IMetricStore { } const sampleJson = { - // metricId: 1, + metricId: 1, name: "Funnel Sample", metricType: 'funnel', series: [ diff --git a/frontend/app/mstore/types/funnelIssue.ts b/frontend/app/mstore/types/funnelIssue.ts index c5bafbfaf..5f07ee123 100644 --- a/frontend/app/mstore/types/funnelIssue.ts +++ b/frontend/app/mstore/types/funnelIssue.ts @@ -8,6 +8,10 @@ export default class FunnelIssue { contextString: string = '' conversionImpact: number = 0 lostConversions: number = 0 + lostConversionsPer: number = 0 + affectedSessionsPer: number = 0 + unaffectedSessionsPer: number = 0 + icon: any = {} constructor() { } @@ -16,12 +20,54 @@ export default class FunnelIssue { this.issueId = json.issueId this.title = json.title this.type = json.type + this.icon = getIconDetails(json.type) this.affectedSessions = json.affectedSessions this.affectedUsers = json.affectedUsers this.unaffectedSessions = json.unaffectedSessions this.contextString = json.contextString this.conversionImpact = json.conversionImpact this.lostConversions = json.lostConversions + + const total = json.lostConversions + json.affectedSessions + json.unaffectedSessions; + this.lostConversionsPer = json.lostConversions * 100 / total; + this.affectedSessionsPer = json.affectedSessions * 100 / total; + this.unaffectedSessionsPer = json.unaffectedSessions * 100 / total; return this } -} \ No newline at end of file +} + + +const getIconDetails = (type) => { + switch(type) { + case 'click_rage': + return { icon: 'funnel/emoji-angry-fill', color: '#CC0000' }; + case 'dead_click': + return { icon: 'funnel/emoji-dizzy-fill', color: '#9C001F' }; + case 'excessive_scrolling': + return { icon: 'funnel/mouse', color: '#D3545F' }; + case 'bad_request': + return { icon: 'funnel/patch-exclamation-fill', color: '#D70072' }; + case 'missing_resource': + return { icon: 'funnel/image-fill', color: '#B89C50' }; + case 'memory': + return { icon: 'funnel/cpu-fill', color: '#8A5A83' }; + case 'cpu': + return { icon: 'funnel/hdd-fill', color: '#8A5A83' }; + case 'slow_resource': + return { icon: 'funnel/hourglass-top', color: '#8B006D' }; + case 'slow_page_load': + return { icon: 'funnel/hourglass-top', color: '#8B006D' }; + case 'custom_event_error': + case 'custom': + return { icon: 'funnel/exclamation-circle-fill', color: '#BF6C00' }; + case 'crash': + return { icon: 'funnel/file-x', color: '#BF2D00' }; + case 'js_exception': + return { icon: 'funnel/exclamation-circle', color: '#BF2D00' }; + } + + return { + icon: 'info', + color: 'red' + } + } \ No newline at end of file From a88763d0eb74749682236cb0cbaf302edbd11f83 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 13 May 2022 11:13:55 +0200 Subject: [PATCH 17/31] feat(ui) - funnels - issues list --- .../components/WidgetView/WidgetView.tsx | 8 ++++- .../shared/Breadcrumb/Breadcrumb.tsx | 30 +++++++++++++++++++ .../app/components/shared/Breadcrumb/index.ts | 1 + .../MessageDistributor/MessageDistributor.ts | 2 +- frontend/app/routes.js | 1 + 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 frontend/app/components/shared/Breadcrumb/Breadcrumb.tsx create mode 100644 frontend/app/components/shared/Breadcrumb/index.ts diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index ddc45a5ac..de0bdd6f7 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -8,6 +8,7 @@ import { useObserver } from 'mobx-react-lite'; import { withSiteId } from 'App/routes'; import WidgetName from '../WidgetName'; import FunnelIssues from '../FunnelIssues/FunnelIssues'; +import Breadcrumb from 'Shared/Breadcrumb'; interface Props { history: any; match: any @@ -40,7 +41,12 @@ function WidgetView(props: Props) { return useObserver(() => (
- +

diff --git a/frontend/app/components/shared/Breadcrumb/Breadcrumb.tsx b/frontend/app/components/shared/Breadcrumb/Breadcrumb.tsx new file mode 100644 index 000000000..67a1a120d --- /dev/null +++ b/frontend/app/components/shared/Breadcrumb/Breadcrumb.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { Icon } from 'UI'; +import { Link } from 'react-router-dom'; + +interface Props { + items: any +} +function Breadcrumb(props) { + const { items } = props; + return ( +
+ {items.map((item, index) => { + if (index === items.length - 1) { + return ( + {item.label} + ); + } + return ( +
+ {item.label} + / + {/* */} +
+ ); + })} +
+ ); +} + +export default Breadcrumb; \ No newline at end of file diff --git a/frontend/app/components/shared/Breadcrumb/index.ts b/frontend/app/components/shared/Breadcrumb/index.ts new file mode 100644 index 000000000..0d4dbd36f --- /dev/null +++ b/frontend/app/components/shared/Breadcrumb/index.ts @@ -0,0 +1 @@ +export { default } from './Breadcrumb'; \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/MessageDistributor/MessageDistributor.ts index 653b05f54..0f661e43d 100644 --- a/frontend/app/player/MessageDistributor/MessageDistributor.ts +++ b/frontend/app/player/MessageDistributor/MessageDistributor.ts @@ -154,7 +154,7 @@ export default class MessageDistributor extends StatedScreen { const r = new MFileReader(new Uint8Array(), this.sessionStart) const msgs: Array = [] - loadFiles(this.session.mobsUrl, + loadFiles([this.session.mobsUrl], b => { r.append(b) let next: ReturnType diff --git a/frontend/app/routes.js b/frontend/app/routes.js index 91ccc8578..9dac4d885 100644 --- a/frontend/app/routes.js +++ b/frontend/app/routes.js @@ -112,6 +112,7 @@ export const dashboardMetricCreate = (dashboardId = ':dashboardId', hash) => ha export const metrics = () => `/metrics`; export const metricCreate = () => `/metrics/create`; export const metricDetails = (id = ':metricId', hash) => hashed(`/metrics/${ id }`, hash); +export const metricDetailsSub = (id = ':metricId', subId = ':subId', hash) => hashed(`/metrics/${ id }/details/${subId}`, hash); export const RESULTS_QUERY_KEY = 'results'; export const METRICS_QUERY_KEY = 'metrics'; From 87f42b4a79c4f771ae18503ad4ee1ef2167c7514 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 13 May 2022 12:35:55 +0200 Subject: [PATCH 18/31] feat(ui) - funnels - sub details view --- frontend/app/Router.js | 2 + .../DashboardRouter/DashboardRouter.tsx | 6 +++ .../WidgetSubDetailsView.tsx | 37 +++++++++++++++++++ .../components/WidgetSubDetailsView/index.ts | 1 + .../components/WidgetView/WidgetView.tsx | 6 ++- frontend/app/routes.js | 1 + 6 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx create mode 100644 frontend/app/components/Dashboard/components/WidgetSubDetailsView/index.ts diff --git a/frontend/app/Router.js b/frontend/app/Router.js index 314f36a6e..b760950b7 100644 --- a/frontend/app/Router.js +++ b/frontend/app/Router.js @@ -55,6 +55,7 @@ const withObTab = routes.withObTab; const METRICS_PATH = routes.metrics(); const METRICS_DETAILS = routes.metricDetails(); +const METRICS_DETAILS_SUB = routes.metricDetailsSub(); const DASHBOARD_PATH = routes.dashboard(); const DASHBOARD_SELECT_PATH = routes.dashboardSelected(); @@ -204,6 +205,7 @@ class Router extends React.Component { {/* DASHBOARD and Metrics */} + diff --git a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx index 6004202a2..cff066a09 100644 --- a/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx +++ b/frontend/app/components/Dashboard/components/DashboardRouter/DashboardRouter.tsx @@ -5,6 +5,7 @@ import { withRouter } from 'react-router-dom'; import { metrics, metricDetails, + metricDetailsSub, dashboardSelected, dashboardMetricCreate, dashboardMetricDetails, @@ -14,6 +15,7 @@ import { import DashboardView from '../DashboardView'; import MetricsView from '../MetricsView'; import WidgetView from '../WidgetView'; +import WidgetSubDetailsView from '../WidgetSubDetailsView'; function DashboardViewSelected({ siteId, dashboardId}) { return ( @@ -37,6 +39,10 @@ function DashboardRouter(props: Props) { + + + + diff --git a/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx new file mode 100644 index 000000000..12e7d821b --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx @@ -0,0 +1,37 @@ +import Breadcrumb from 'App/components/shared/Breadcrumb'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import React, { useEffect } from 'react'; +import { withSiteId } from 'App/routes'; + +interface Props { + history: any; + match: any + siteId: any +} +function WidgetSubDetailsView(props: Props) { + const { match: { params: { siteId, dashboardId, metricId } } } = props; + const { metricStore } = useStore(); + const widget = useObserver(() => metricStore.instance); + const loadingWidget = useObserver(() => metricStore.isLoading); + + useEffect(() => { + if (!widget || !widget.exists()) { + metricStore.fetch(metricId); + } + }, []); + + return ( +
+ +
+ ); +} + +export default WidgetSubDetailsView; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/WidgetSubDetailsView/index.ts b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/index.ts new file mode 100644 index 000000000..669474025 --- /dev/null +++ b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/index.ts @@ -0,0 +1 @@ +export { default } from './WidgetSubDetailsView'; \ 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 index de0bdd6f7..789c7d895 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -23,9 +23,11 @@ function WidgetView(props: Props) { const [expanded, setExpanded] = useState(!metricId || metricId === 'create'); React.useEffect(() => { - if (metricId && metricId !== 'create') { + if (metricId && metricId !== 'create' && (!widget || !widget.exists())) { metricStore.fetch(metricId); - } else { + } + + if (metricId === 'create') { metricStore.init(); } }, []) diff --git a/frontend/app/routes.js b/frontend/app/routes.js index 9dac4d885..4b7b35ebe 100644 --- a/frontend/app/routes.js +++ b/frontend/app/routes.js @@ -127,6 +127,7 @@ const REQUIRED_SITE_ID_ROUTES = [ metrics(), metricDetails(''), + metricDetailsSub(''), dashboard(''), dashboardSelected(''), From fd68f7b576f2f3f6ebe5a5ea2f12e6d0e8444667 Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 13 May 2022 13:07:35 +0200 Subject: [PATCH 19/31] feat(ui) - funnels - path changes --- .../FunnelIssues/FunnelIssues.tsx | 0 .../FunnelIssueDetails/FunnelIssueDetails.tsx | 32 +++++++++++++++++++ .../components/FunnelIssueDetails/index.ts | 1 + .../FunnelIssueGraph/FunnelIssueGraph.tsx | 0 .../components/FunnelIssueGraph/index.ts | 0 .../FunnelIssuesList/FunnelIssuesList.tsx | 0 .../components/FunnelIssuesList/index.ts | 0 .../FunnelIssuesListItem.tsx | 0 .../components/FunnelIssuesListItem/index.ts | 0 .../{ => Funnels}/FunnelIssues/index.ts | 0 .../FunnelIssuesDropdown.tsx | 0 .../FunnelIssuesDropdown/index.ts | 0 .../FunnelIssuesSelectedFilters.tsx | 0 .../FunnelIssuesSelectedFilters/index.ts | 0 .../FunnelIssuesSort/FunnelIssuesSort.tsx | 0 .../{ => Funnels}/FunnelIssuesSort/index.ts | 0 .../WidgetSubDetailsView.tsx | 7 ++++ .../components/WidgetView/WidgetView.tsx | 2 +- frontend/app/mstore/funnelStore.ts | 18 +++++++++++ frontend/app/mstore/types/funnel.ts | 5 +++ 20 files changed, 64 insertions(+), 1 deletion(-) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssues/FunnelIssues.tsx (100%) create mode 100644 frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/FunnelIssueDetails.tsx create mode 100644 frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/index.ts rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssues/components/FunnelIssueGraph/index.ts (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssues/components/FunnelIssuesList/index.ts (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssues/components/FunnelIssuesListItem/index.ts (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssues/index.ts (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssuesDropdown/index.ts (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssuesSelectedFilters/index.ts (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssuesSort/FunnelIssuesSort.tsx (100%) rename frontend/app/components/Dashboard/components/{ => Funnels}/FunnelIssuesSort/index.ts (100%) diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/FunnelIssues.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssues/FunnelIssues.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/FunnelIssueDetails.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/FunnelIssueDetails.tsx new file mode 100644 index 000000000..06c845da9 --- /dev/null +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/FunnelIssueDetails.tsx @@ -0,0 +1,32 @@ +import React, { useEffect } from 'react'; +import { useStore } from 'App/mstore'; +import { useObserver } from 'mobx-react-lite'; +import { Loader } from 'UI'; + +interface Props { + funnelId: string; + issueId: string; +} +function FunnelIssueDetails(props: Props) { + const { funnelStore } = useStore(); + const { funnelId, issueId } = props; + const funnel = useObserver(() => funnelStore.instance); + const funnelIssue = useObserver(() => funnelStore.issueInstance); + const loading = useObserver(() => funnelStore.isLoadingIssues); + + useEffect(() => { + if (!funnel || !funnel.exists()) { + funnelStore.fetchFunnel(props.funnelId); + } + + funnelStore.fetchIssue(funnelId, issueId); + }, []); + + return ( + + + + ); +} + +export default FunnelIssueDetails; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/index.ts new file mode 100644 index 000000000..486b120d5 --- /dev/null +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/index.ts @@ -0,0 +1 @@ +export { default } from './FunnelIssueDetails'; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueGraph/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssueGraph/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueGraph/index.ts diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesList/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesList/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesList/index.ts diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesListItem/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssues/components/FunnelIssuesListItem/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesListItem/index.ts diff --git a/frontend/app/components/Dashboard/components/FunnelIssues/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssues/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssues/index.ts diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesDropdown/FunnelIssuesDropdown.tsx diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesDropdown/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesDropdown/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssuesDropdown/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesDropdown/index.ts diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSelectedFilters/FunnelIssuesSelectedFilters.tsx diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSelectedFilters/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssuesSelectedFilters/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSelectedFilters/index.ts diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesSort/FunnelIssuesSort.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSort/FunnelIssuesSort.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssuesSort/FunnelIssuesSort.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSort/FunnelIssuesSort.tsx diff --git a/frontend/app/components/Dashboard/components/FunnelIssuesSort/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSort/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/FunnelIssuesSort/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSort/index.ts diff --git a/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx index 12e7d821b..da772e747 100644 --- a/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx @@ -3,6 +3,8 @@ import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; import React, { useEffect } from 'react'; import { withSiteId } from 'App/routes'; +import { Loader } from 'UI'; +// import FunnelSubDetailsView from './FunnelSubDetailsView'; interface Props { history: any; @@ -14,6 +16,7 @@ function WidgetSubDetailsView(props: Props) { const { metricStore } = useStore(); const widget = useObserver(() => metricStore.instance); const loadingWidget = useObserver(() => metricStore.isLoading); + const isFunnel = widget.metricType === 'funnel'; useEffect(() => { if (!widget || !widget.exists()) { @@ -30,6 +33,10 @@ function WidgetSubDetailsView(props: Props) { { label: 'Sub Details' } ]} /> + + + {/* {isFunnel && } */} +

); } diff --git a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx index 789c7d895..e6c29f4af 100644 --- a/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetView/WidgetView.tsx @@ -7,7 +7,7 @@ import { Icon, BackLink, Loader } from 'UI'; import { useObserver } from 'mobx-react-lite'; import { withSiteId } from 'App/routes'; import WidgetName from '../WidgetName'; -import FunnelIssues from '../FunnelIssues/FunnelIssues'; +import FunnelIssues from '../Funnels/FunnelIssues/FunnelIssues'; import Breadcrumb from 'Shared/Breadcrumb'; interface Props { history: any; diff --git a/frontend/app/mstore/funnelStore.ts b/frontend/app/mstore/funnelStore.ts index ad1020a1a..181305661 100644 --- a/frontend/app/mstore/funnelStore.ts +++ b/frontend/app/mstore/funnelStore.ts @@ -18,6 +18,8 @@ export default class FunnelStore { issues: any[] = [] isLoadingIssues: boolean = false issuesFilter: any = [] + + issueInstance: FunnelIssue | null = null constructor() { makeAutoObservable(this, { @@ -118,6 +120,22 @@ export default class FunnelStore { }) }) } + + fetchIssue(funnelId: string, issueId: string): Promise { + this.isLoadingIssues = true + return new Promise((resolve, reject) => { + funnelService.fetchIssue(funnelId, issueId) + .then(response => { + this.issueInstance = new FunnelIssue().fromJSON(response) + resolve(this.issueInstance) + }).catch(error => { + reject(error) + } + ).finally(() => { + this.isLoadingIssues = false + }) + }) + } } diff --git a/frontend/app/mstore/types/funnel.ts b/frontend/app/mstore/types/funnel.ts index 95c95e5c4..ffe7037ef 100644 --- a/frontend/app/mstore/types/funnel.ts +++ b/frontend/app/mstore/types/funnel.ts @@ -11,6 +11,7 @@ export interface IFunnel { isPublic: boolean fromJSON: (json: any) => void toJSON: () => any + exists: () => boolean } export default class Funnel implements IFunnel { @@ -44,4 +45,8 @@ export default class Funnel implements IFunnel { conversionRate: this.conversionRate, } } + + exists(): boolean { + return this.funnelId !== '' + } } \ No newline at end of file From f6bd3dd0dd9f137ba752168f78041f00c31b6c1f Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 13 May 2022 16:05:11 +0200 Subject: [PATCH 20/31] feat(ui) - funnels - details wip --- .../FunnelIssueDetails/FunnelIssueDetails.tsx | 18 ++++++++++++++++-- .../FunnelIssueDetails/index.ts | 0 .../FunnelIssueGraph/FunnelIssueGraph.tsx | 0 .../components => }/FunnelIssueGraph/index.ts | 0 .../Funnels/FunnelIssues/FunnelIssues.tsx | 2 +- .../FunnelIssuesList/FunnelIssuesList.tsx | 0 .../components => }/FunnelIssuesList/index.ts | 0 .../FunnelIssuesListItem.tsx | 0 .../FunnelIssuesListItem/index.ts | 0 .../WidgetSubDetailsView.tsx | 14 ++++++++------ frontend/app/mstore/funnelStore.ts | 8 ++++++-- frontend/app/mstore/metricStore.ts | 2 +- frontend/app/mstore/types/funnelIssue.ts | 1 + frontend/app/services/FunnelService.ts | 4 +++- 14 files changed, 36 insertions(+), 13 deletions(-) rename frontend/app/components/Dashboard/components/Funnels/{FunnelIssues/components => }/FunnelIssueDetails/FunnelIssueDetails.tsx (55%) rename frontend/app/components/Dashboard/components/Funnels/{FunnelIssues/components => }/FunnelIssueDetails/index.ts (100%) rename frontend/app/components/Dashboard/components/Funnels/{FunnelIssues/components => }/FunnelIssueGraph/FunnelIssueGraph.tsx (100%) rename frontend/app/components/Dashboard/components/Funnels/{FunnelIssues/components => }/FunnelIssueGraph/index.ts (100%) rename frontend/app/components/Dashboard/components/Funnels/{FunnelIssues/components => }/FunnelIssuesList/FunnelIssuesList.tsx (100%) rename frontend/app/components/Dashboard/components/Funnels/{FunnelIssues/components => }/FunnelIssuesList/index.ts (100%) rename frontend/app/components/Dashboard/components/Funnels/{FunnelIssues/components => }/FunnelIssuesListItem/FunnelIssuesListItem.tsx (100%) rename frontend/app/components/Dashboard/components/Funnels/{FunnelIssues/components => }/FunnelIssuesListItem/index.ts (100%) diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/FunnelIssueDetails.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx similarity index 55% rename from frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/FunnelIssueDetails.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx index 06c845da9..fa4ff3b4e 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/FunnelIssueDetails.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx @@ -2,6 +2,8 @@ import React, { useEffect } from 'react'; import { useStore } from 'App/mstore'; import { useObserver } from 'mobx-react-lite'; import { Loader } from 'UI'; +import FunnelIssuesListItem from '../FunnelIssuesListItem'; +import SessionItem from 'App/components/shared/SessionItem/SessionItem'; interface Props { funnelId: string; @@ -16,15 +18,27 @@ function FunnelIssueDetails(props: Props) { useEffect(() => { if (!funnel || !funnel.exists()) { - funnelStore.fetchFunnel(props.funnelId); + // funnelStore.fetchFunnel(props.funnelId); + funnelStore.fetchFunnel('143'); } funnelStore.fetchIssue(funnelId, issueId); }, []); + console.log('funnelIssue', funnelIssue) + return ( - + { funnelIssue && } + +
+ {funnelIssue && funnelIssue.sessions.map(session => ( + + ))} +
); } diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueDetails/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/index.ts diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueGraph/FunnelIssueGraph.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueGraph/FunnelIssueGraph.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssueGraph/FunnelIssueGraph.tsx diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueGraph/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueGraph/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssueGraph/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssueGraph/index.ts diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx index e779d4f49..81bb4457e 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/FunnelIssues.tsx @@ -4,7 +4,7 @@ import React, { useEffect } from 'react'; import { NoContent, Loader } from 'UI'; import FunnelIssuesDropdown from '../FunnelIssuesDropdown'; import FunnelIssuesSort from '../FunnelIssuesSort'; -import FunnelIssuesList from './components/FunnelIssuesList'; +import FunnelIssuesList from '../FunnelIssuesList'; function FunnelIssues(props) { const { funnelStore } = useStore(); diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesList/FunnelIssuesList.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesList/FunnelIssuesList.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesList/FunnelIssuesList.tsx diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesList/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesList/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesList/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesList/index.ts diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx similarity index 100% rename from frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesListItem/FunnelIssuesListItem.tsx rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/FunnelIssuesListItem.tsx diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesListItem/index.ts b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/index.ts similarity index 100% rename from frontend/app/components/Dashboard/components/Funnels/FunnelIssues/components/FunnelIssuesListItem/index.ts rename to frontend/app/components/Dashboard/components/Funnels/FunnelIssuesListItem/index.ts diff --git a/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx index da772e747..261e77efd 100644 --- a/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx +++ b/frontend/app/components/Dashboard/components/WidgetSubDetailsView/WidgetSubDetailsView.tsx @@ -4,7 +4,7 @@ import { useObserver } from 'mobx-react-lite'; import React, { useEffect } from 'react'; import { withSiteId } from 'App/routes'; import { Loader } from 'UI'; -// import FunnelSubDetailsView from './FunnelSubDetailsView'; +import FunnelIssueDetails from '../Funnels/FunnelIssueDetails'; interface Props { history: any; @@ -12,11 +12,13 @@ interface Props { siteId: any } function WidgetSubDetailsView(props: Props) { - const { match: { params: { siteId, dashboardId, metricId } } } = props; - const { metricStore } = useStore(); + const { match: { params: { siteId, dashboardId, metricId, subId } } } = props; + const { metricStore, funnelStore } = useStore(); const widget = useObserver(() => metricStore.instance); + const issueInstance = useObserver(() => funnelStore.issueInstance); const loadingWidget = useObserver(() => metricStore.isLoading); - const isFunnel = widget.metricType === 'funnel'; + // const isFunnel = widget.metricType === 'funnel'; // TODO uncomment this line + const isFunnel = widget.metricType === 'table'; // TODO remove this line useEffect(() => { if (!widget || !widget.exists()) { @@ -30,12 +32,12 @@ function WidgetSubDetailsView(props: Props) { items={[ { label: dashboardId ? 'Dashboard' : 'Metrics', to: dashboardId ? withSiteId('/dashboard/' + dashboardId, siteId) : withSiteId('/metrics', siteId) }, { label: widget.name, to: withSiteId(`/metrics/${widget.metricId}`, siteId) }, - { label: 'Sub Details' } + { label: issueInstance ? issueInstance.title : 'Sub Details' } ]} /> - {/* {isFunnel && } */} + {isFunnel && }
); diff --git a/frontend/app/mstore/funnelStore.ts b/frontend/app/mstore/funnelStore.ts index 181305661..946e801d3 100644 --- a/frontend/app/mstore/funnelStore.ts +++ b/frontend/app/mstore/funnelStore.ts @@ -1,6 +1,7 @@ import { makeAutoObservable, runInAction, observable, action, reaction } from "mobx" import { funnelService } from "App/services" import Funnel, { IFunnel } from "./types/funnel"; +import Session from './types/session'; import FunnelIssue from './types/funnelIssue'; import Period, { LAST_7_DAYS } from 'Types/app/period'; @@ -124,9 +125,12 @@ export default class FunnelStore { fetchIssue(funnelId: string, issueId: string): Promise { this.isLoadingIssues = true return new Promise((resolve, reject) => { - funnelService.fetchIssue(funnelId, issueId) + // funnelService.fetchIssue(funnelId, issueId) + funnelService.fetchIssue('143', '91515f9118ed803291f87133e2cb49a16ea') .then(response => { - this.issueInstance = new FunnelIssue().fromJSON(response) + this.issueInstance = new FunnelIssue().fromJSON(response.issue) + this.issueInstance.sessions = response.sessions.sessions.map(i => new Session().fromJson(i)) + console.log('response.sessions', response.sessions); resolve(this.issueInstance) }).catch(error => { reject(error) diff --git a/frontend/app/mstore/metricStore.ts b/frontend/app/mstore/metricStore.ts index 0f4163711..eb9147620 100644 --- a/frontend/app/mstore/metricStore.ts +++ b/frontend/app/mstore/metricStore.ts @@ -192,7 +192,7 @@ export default class MetricStore implements IMetricStore { } const sampleJson = { - metricId: 1, + // metricId: 1, name: "Funnel Sample", metricType: 'funnel', series: [ diff --git a/frontend/app/mstore/types/funnelIssue.ts b/frontend/app/mstore/types/funnelIssue.ts index 5f07ee123..203a6727c 100644 --- a/frontend/app/mstore/types/funnelIssue.ts +++ b/frontend/app/mstore/types/funnelIssue.ts @@ -12,6 +12,7 @@ export default class FunnelIssue { affectedSessionsPer: number = 0 unaffectedSessionsPer: number = 0 icon: any = {} + sessions: any[] = [] constructor() { } diff --git a/frontend/app/services/FunnelService.ts b/frontend/app/services/FunnelService.ts index 099ad8d1f..676d7fb3f 100644 --- a/frontend/app/services/FunnelService.ts +++ b/frontend/app/services/FunnelService.ts @@ -54,10 +54,12 @@ export default class FunnelService implements IFunnelService { const path = funnelId ? `/funnels/${funnelId}/issues` : '/funnels/issues'; return this.client.post(path, payload) .then(response => response.json()) + .then(response => response.data || []); } fetchIssue(funnelId: string, issueId: string): Promise { - return this.client.get(`/funnels/${funnelId}/issues/${issueId}`) + return this.client.post(`/funnels/${funnelId}/issues/${issueId}/sessions`, {}) .then(response => response.json()) + .then(response => response.data || {}); } } \ No newline at end of file From 05bd61b83c4160a72e15ef1952cd9bb8d77ac7ff Mon Sep 17 00:00:00 2001 From: Shekar Siri Date: Fri, 13 May 2022 19:03:01 +0200 Subject: [PATCH 21/31] feat(ui) - funnels - issues sort --- .../FunnelIssueDetails/FunnelIssueDetails.tsx | 2 -- .../Funnels/FunnelIssuesList/FunnelIssuesList.tsx | 9 ++++++--- .../Funnels/FunnelIssuesSort/FunnelIssuesSort.tsx | 12 ++++++++++-- frontend/app/mstore/funnelStore.ts | 4 ++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx index fa4ff3b4e..3bde57674 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssueDetails/FunnelIssueDetails.tsx @@ -25,8 +25,6 @@ function FunnelIssueDetails(props: Props) { funnelStore.fetchIssue(funnelId, issueId); }, []); - console.log('funnelIssue', funnelIssue) - return ( { funnelIssue && funnelStore.issuesSort); const issuesFilter = useObserver(() => funnelStore.issuesFilter.map((issue: any) => issue.value)); const issues = useObserver(() => funnelStore.issues); - const filteredIssues = useObserver(() => issuesFilter.length > 0 ? issues.filter((issue: any) => issuesFilter.includes(issue.type)) : issues); + let filteredIssues = useObserver(() => issuesFilter.length > 0 ? issues.filter((issue: any) => issuesFilter.includes(issue.type)) : issues); + filteredIssues = useObserver(() => issuesSort.sort ? filteredIssues.slice().sort((a, b) => a[issuesSort.sort] - b[issuesSort.sort]): filteredIssues); + filteredIssues = useObserver(() => issuesSort.order === 'desc' ? filteredIssues.reverse() : filteredIssues); - return ( + return useObserver(() => (
{filteredIssues.map((issue, index) => (
@@ -17,7 +20,7 @@ function FunnelIssuesList(props) {
))}
- ); + )); } export default FunnelIssuesList; \ No newline at end of file diff --git a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSort/FunnelIssuesSort.tsx b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSort/FunnelIssuesSort.tsx index 782be0eb3..cdd9a0c3c 100644 --- a/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSort/FunnelIssuesSort.tsx +++ b/frontend/app/components/Dashboard/components/Funnels/FunnelIssuesSort/FunnelIssuesSort.tsx @@ -1,3 +1,4 @@ +import { useStore } from 'App/mstore'; import React from 'react'; import Select from 'Shared/Select'; @@ -11,9 +12,16 @@ const sortOptions = [ ] interface Props { - onChange?: (value: string) => void; + // onChange?: (value: string) => void; } function FunnelIssuesSort(props: Props) { + const { funnelStore } = useStore(); + + const onSortChange = (opt) => { + const [ sort, order ] = opt.value.split('-'); + funnelStore.updateKey('issuesSort', { sort, order }); + } + return (
onChangeSelect({ name: 'defaultInputMode', value }) } + onChange={ ({ value }) => onChangeSelect({ name: 'defaultInputMode', value: value.value }) } placeholder="Default Input Mode" defaultValue={ gdpr.defaultInputMode } /> @@ -170,7 +166,7 @@ const ProjectCodeSnippet = props => { host={ site && site.host } projectKey={ site && site.projectKey } ingestPoint={`"https://${window.location.hostname}/ingest"`} - defaultInputMode={ inputModeOptionsMap[gdpr.defaultInputMode] } + defaultInputMode={ gdpr.defaultInputMode } obscureTextNumbers={ gdpr.maskNumbers } obscureTextEmails={ gdpr.maskEmails } /> diff --git a/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx b/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx index 0979684bd..8302800f9 100644 --- a/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx +++ b/frontend/app/components/shared/CodeSnippet/CodeSnippet.tsx @@ -10,6 +10,7 @@ const inputModeOptions = [ const inputModeOptionsMap: any = {} inputModeOptions.forEach((o: any, i: any) => inputModeOptionsMap[o.value] = i) +console.log('inputModeOptionsMap', inputModeOptionsMap) interface Props { @@ -22,12 +23,13 @@ interface Props { } function CodeSnippet(props: Props) { const { host, projectKey, ingestPoint, defaultInputMode, obscureTextNumbers, obscureTextEmails } = props; + console.log('defaultInputMode', defaultInputMode) const codeSnippet = `