mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-01 12:29:35 +01:00
Render view for user journey report.
This commit is contained in:
parent
06c62a199c
commit
8a722ff013
@ -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: <Target />,
|
||||
},
|
||||
{
|
||||
title: formatMessage(labels.journey),
|
||||
description: formatMessage(labels.journeyDescription),
|
||||
url: renderTeamUrl('/reports/journey'),
|
||||
icon: <Path />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 <div>{JSON.stringify(data)}</div>;
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.view}>
|
||||
{columns.map((column, index) => {
|
||||
const current = index === selected?.index;
|
||||
const behind = index <= selected?.index - 1;
|
||||
const ahead = index > selected?.index;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={classNames(styles.column, {
|
||||
[styles.current]: current,
|
||||
[styles.behind]: behind,
|
||||
[styles.ahead]: ahead,
|
||||
})}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.num}>{index + 1}</div>
|
||||
</div>
|
||||
{column.map(({ item, total, paths }) => {
|
||||
const highlight = selected?.paths.find(arr => {
|
||||
return arr.find(a => a.items[index] === item);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item}
|
||||
className={classNames(styles.item, {
|
||||
[styles.highlight]: highlight,
|
||||
})}
|
||||
onClick={() => handleClick(item, index, paths)}
|
||||
>
|
||||
{item} ({total})
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -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<JourneyResult[]> {
|
||||
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<JourneyResult[]> {
|
||||
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 }));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user