From 8a722ff01396882c5303f1a8480b5a526f33a600 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 1 Jun 2024 11:45:06 -0700 Subject: [PATCH 1/2] Render view for user journey report. --- .../(main)/reports/create/ReportTemplates.tsx | 7 ++ .../reports/journey/JourneyView.module.css | 82 +++++++++++++--- .../(main)/reports/journey/JourneyView.tsx | 94 ++++++++++++++++++- src/queries/analytics/reports/getJourney.ts | 39 ++++---- 4 files changed, 187 insertions(+), 35 deletions(-) diff --git a/src/app/(main)/reports/create/ReportTemplates.tsx b/src/app/(main)/reports/create/ReportTemplates.tsx index fdf5c5f5..0777cc1f 100644 --- a/src/app/(main)/reports/create/ReportTemplates.tsx +++ b/src/app/(main)/reports/create/ReportTemplates.tsx @@ -6,6 +6,7 @@ import Lightbulb from 'assets/lightbulb.svg'; import Magnet from 'assets/magnet.svg'; import Tag from 'assets/tag.svg'; import Target from 'assets/target.svg'; +import Path from 'assets/path.svg'; import styles from './ReportTemplates.module.css'; import { useMessages, useTeamUrl } from 'components/hooks'; @@ -44,6 +45,12 @@ export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) url: renderTeamUrl('/reports/goals'), icon: , }, + { + title: formatMessage(labels.journey), + description: formatMessage(labels.journeyDescription), + url: renderTeamUrl('/reports/journey'), + icon: , + }, ]; return ( diff --git a/src/app/(main)/reports/journey/JourneyView.module.css b/src/app/(main)/reports/journey/JourneyView.module.css index fa7cc0b4..4d5a96a4 100644 --- a/src/app/(main)/reports/journey/JourneyView.module.css +++ b/src/app/(main)/reports/journey/JourneyView.module.css @@ -1,14 +1,74 @@ -.title { - font-size: 24px; - line-height: 36px; - font-weight: 700; +.container { + height: 900px; + position: relative; } -.row { - display: grid; - grid-template-columns: 50% 50%; - gap: 20px; - border-bottom: 1px solid var(--base300); - padding-bottom: 30px; - margin-bottom: 30px; +.view { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + gap: 60px; + overflow: auto; +} + +.header { + display: flex; + margin-bottom: 20px; +} + +.num { + display: flex; + align-items: center; + justify-content: center; + border-radius: 100%; + width: 50px; + height: 50px; + font-size: 16px; + font-weight: 700; + color: var(--base100); + background: var(--base800); + z-index: 1; + margin: 0 auto; +} + +.column { + min-width: 300px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.item { + cursor: pointer; + padding: 10px 20px; + background: var(--base75); + border-radius: 5px; +} + +.item:hover:not(.highlight) { + color: var(--base900); + background: var(--base100); +} + +.highlight { + color: var(--base75); + background: var(--base900); + font-weight: 400; +} + +.behind { + color: var(--base400); +} + +.ahead { + color: var(--base400); +} + +.current { + color: var(--base500); } diff --git a/src/app/(main)/reports/journey/JourneyView.tsx b/src/app/(main)/reports/journey/JourneyView.tsx index 6905d74c..ff1941dc 100644 --- a/src/app/(main)/reports/journey/JourneyView.tsx +++ b/src/app/(main)/reports/journey/JourneyView.tsx @@ -1,13 +1,103 @@ -import { useContext } from 'react'; +import { useContext, useMemo, useState } from 'react'; import { ReportContext } from '../[reportId]/Report'; +import { firstBy } from 'thenby'; +import styles from './JourneyView.module.css'; +import classNames from 'classnames'; +import { useEscapeKey } from 'components/hooks'; export default function JourneyView() { + const [selected, setSelected] = useState(null); const { report } = useContext(ReportContext); const { data } = report || {}; + useEscapeKey(() => setSelected(null)); + + const columns = useMemo(() => { + if (!data) { + return []; + } + return Array(data[0].items.length) + .fill(undefined) + .map((col = {}, index) => { + data.forEach(({ items, count }) => { + const item = items[index]; + + if (item) { + if (!col[item]) { + col[item] = { + item, + total: +count, + index, + paths: [ + data.filter((d, i) => { + return d.items[index] === item && i !== index; + }), + ], + }; + } else { + col[item].total += +count; + } + } + }); + + return Object.keys(col) + .map(key => col[key]) + .sort(firstBy('total', -1)); + }); + }, [data]); + + const handleClick = (item: string, index: number, paths: any[]) => { + if (item !== selected?.item || index !== selected?.index) { + setSelected({ item, index, paths }); + } else { + setSelected(null); + } + }; if (!data) { return null; } - return
{JSON.stringify(data)}
; + return ( +
+
+ {columns.map((column, index) => { + const current = index === selected?.index; + const behind = index <= selected?.index - 1; + const ahead = index > selected?.index; + + return ( +
+
+
{index + 1}
+
+ {column.map(({ item, total, paths }) => { + const highlight = selected?.paths.find(arr => { + return arr.find(a => a.items[index] === item); + }); + + return ( +
handleClick(item, index, paths)} + > + {item} ({total}) +
+ ); + })} +
+ ); + })} +
+
+ ); } diff --git a/src/queries/analytics/reports/getJourney.ts b/src/queries/analytics/reports/getJourney.ts index 088f7ee8..a02bf9bf 100644 --- a/src/queries/analytics/reports/getJourney.ts +++ b/src/queries/analytics/reports/getJourney.ts @@ -2,6 +2,15 @@ import clickhouse from 'lib/clickhouse'; import { CLICKHOUSE, PRISMA, runQuery } from 'lib/db'; import prisma from 'lib/prisma'; +interface JourneyResult { + e1: string; + e2: string; + e3: string; + e4: string; + e5: string; + count: string; +} + export async function getJourney( ...args: [ websiteId: string, @@ -23,16 +32,7 @@ async function relationalQuery( startDate: Date; endDate: Date; }, -): Promise< - { - e1: string; - e2: string; - e3: string; - e4: string; - e5: string; - count: string; - }[] -> { +): Promise { const { startDate, endDate } = filters; const { rawQuery } = prisma; @@ -79,7 +79,7 @@ async function relationalQuery( startDate, endDate, }, - ); + ).then(parseResult); } async function clickhouseQuery( @@ -88,16 +88,7 @@ async function clickhouseQuery( startDate: Date; endDate: Date; }, -): Promise< - { - e1: string; - e2: string; - e3: string; - e4: string; - e5: string; - count: string; - }[] -> { +): Promise { const { startDate, endDate } = filters; const { rawQuery } = clickhouse; @@ -144,5 +135,9 @@ async function clickhouseQuery( startDate, endDate, }, - ); + ).then(parseResult); +} + +function parseResult(data: any) { + return data.map(({ e1, e2, e3, e4, e5, count }) => ({ items: [e1, e2, e3, e4, e5], count })); } From 4b67d10f045337bebb0c9569c8f101de38e86220 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 1 Jun 2024 14:06:17 -0700 Subject: [PATCH 2/2] Fixed teams urls. --- .../(main)/reports/[reportId]/ReportHeader.tsx | 2 +- .../teams/[teamId]/reports/goals/page.tsx | 3 --- src/app/(main)/websites/WebsitesPage.tsx | 5 ++++- src/app/(main)/websites/page.tsx | 4 ++-- src/components/common/Favicon.module.css | 3 --- src/components/common/Favicon.tsx | 3 --- src/components/metrics/ReferrersTable.tsx | 18 ++++++++---------- .../analytics/sessions/getSessionStats.ts | 4 ++-- 8 files changed, 17 insertions(+), 25 deletions(-) delete mode 100644 src/app/(main)/teams/[teamId]/reports/goals/page.tsx delete mode 100644 src/components/common/Favicon.module.css diff --git a/src/app/(main)/reports/[reportId]/ReportHeader.tsx b/src/app/(main)/reports/[reportId]/ReportHeader.tsx index 2936d806..7ab80fcd 100644 --- a/src/app/(main)/reports/[reportId]/ReportHeader.tsx +++ b/src/app/(main)/reports/[reportId]/ReportHeader.tsx @@ -60,7 +60,7 @@ export function ReportHeader({ icon }) {
REPORT_TYPES[key] === report?.type)], diff --git a/src/app/(main)/teams/[teamId]/reports/goals/page.tsx b/src/app/(main)/teams/[teamId]/reports/goals/page.tsx deleted file mode 100644 index 34aab933..00000000 --- a/src/app/(main)/teams/[teamId]/reports/goals/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import Page from 'app/(main)/reports/goals/page'; - -export default Page; diff --git a/src/app/(main)/websites/WebsitesPage.tsx b/src/app/(main)/websites/WebsitesPage.tsx index 8d8ee2e2..d6f8524b 100644 --- a/src/app/(main)/websites/WebsitesPage.tsx +++ b/src/app/(main)/websites/WebsitesPage.tsx @@ -1,8 +1,11 @@ 'use client'; import WebsitesHeader from 'app/(main)/settings/websites/WebsitesHeader'; import WebsitesDataTable from 'app/(main)/settings/websites/WebsitesDataTable'; +import { useTeamUrl } from 'components/hooks'; + +export default function WebsitesPage() { + const { teamId } = useTeamUrl(); -export default function WebsitesPage({ teamId }: { teamId: string }) { return ( <> diff --git a/src/app/(main)/websites/page.tsx b/src/app/(main)/websites/page.tsx index ee49cb8a..859516c9 100644 --- a/src/app/(main)/websites/page.tsx +++ b/src/app/(main)/websites/page.tsx @@ -1,8 +1,8 @@ import WebsitesPage from './WebsitesPage'; import { Metadata } from 'next'; -export default function ({ params: { teamId, userId } }) { - return ; +export default function () { + return ; } export const metadata: Metadata = { diff --git a/src/components/common/Favicon.module.css b/src/components/common/Favicon.module.css deleted file mode 100644 index f8972ad1..00000000 --- a/src/components/common/Favicon.module.css +++ /dev/null @@ -1,3 +0,0 @@ -.favicon { - margin-inline-end: 8px; -} diff --git a/src/components/common/Favicon.tsx b/src/components/common/Favicon.tsx index cdaeaf4b..e78bdbc7 100644 --- a/src/components/common/Favicon.tsx +++ b/src/components/common/Favicon.tsx @@ -1,5 +1,3 @@ -import styles from './Favicon.module.css'; - function getHostName(url: string) { const match = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:/\n?=]+)/im); return match && match.length > 1 ? match[1] : null; @@ -14,7 +12,6 @@ export function Favicon({ domain, ...props }) { return hostName ? ( { return ( - + - - + ); }; diff --git a/src/queries/analytics/sessions/getSessionStats.ts b/src/queries/analytics/sessions/getSessionStats.ts index c977187d..e3af7ba6 100644 --- a/src/queries/analytics/sessions/getSessionStats.ts +++ b/src/queries/analytics/sessions/getSessionStats.ts @@ -66,8 +66,8 @@ async function clickhouseQuery( order by t `, params, - ).then(a => { - return Object.values(a).map(a => { + ).then(result => { + return Object.values(result).map((a: any) => { return { x: a.x, y: Number(a.y) }; }); });