add event page + fix menu

This commit is contained in:
Emily
2024-06-13 16:19:50 +02:00
parent 4fd892e1bd
commit 16147da824
9 changed files with 170 additions and 101 deletions

View File

@@ -59,6 +59,15 @@
}
.hide-scrollbars {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
&::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
}
.card-shadow {
box-shadow: 0 0 18px #00000033;

View File

@@ -27,6 +27,23 @@ const props = defineProps<Props>();
const { isAdmin } = useUserRoles();
let resizeHandler: any;
onMounted(() => {
resizeHandler = () => {
isMenuTooLarge.value = innerHeight < 720;
}
addEventListener('resize', resizeHandler);
})
onUnmounted(() => {
if (resizeHandler) removeEventListener('resize', resizeHandler);
})
const isMenuTooLarge = ref<boolean>(false);
</script>
<template>
@@ -35,16 +52,16 @@ const { isAdmin } = useUserRoles();
class="CVerticalNavigation absolute z-[80] bg-menu h-full overflow-hidden w-0 md:w-[5rem]"
:class="{ '!w-[18rem] shadow-[0_0_20px_#000000] rounded-r-2xl': isOpen }">
<div :class="{ 'w-[18rem]': isOpen }">
<div class="flex gap-4 items-center py-6 px-[.9rem] pb-8">
<div class="flex gap-4 items-center py-6 px-[.9rem] pb-8" :class="{ '!pb-4': isMenuTooLarge }">
<div class="bg-black h-[2.8rem] aspect-[1/1] flex items-center justify-center rounded-lg">
<img class="h-[2.4rem]" :src="'/logo.png'">
</div>
<div v-if="isOpen" class="font-bold text-[1.4rem] text-gray-300"> Litlyx </div>
</div>
<div class="pb-8" v-for="section of sections">
<div class="pb-8" :class="{ '!pb-3': isMenuTooLarge }" v-for="section of sections">
<div class="flex flex-col px-3 gap-2">
<div class="flex flex-col px-3 gap-2" :class="{ '!gap-[.3rem]': isMenuTooLarge }">
<template v-for="entry of section.entries">

View File

@@ -1,7 +1,5 @@
<script lang="ts" setup>
definePageMeta({ layout: 'dashboard' });
const activeProject = useActiveProject();
@@ -50,57 +48,56 @@ const canSearch = computed(() => {
return selectedMetadataField.value != undefined;
});
</script>
<template>
<div class="w-full h-full p-8 flex flex-col">
<CardTitled title="Event metadata analyzer" sub="Filter events metadata fields to analyze them" class="w-full p-4">
<CardTitled title="Event metadata analyzer" sub="Filter events metadata fields to analyze them" class="w-full p-4">
<div class="p-2 flex flex-col">
<div class="p-2 flex flex-col">
<div class="flex flex-col gap-2">
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
placeholder="Select an event" :options="eventNames" v-model="selectedEventName">
</USelectMenu>
<div class="flex flex-col gap-2">
<USelectMenu searchable searchable-placeholder="Search an event..." class="w-full"
placeholder="Select an event" :options="eventNames" v-model="selectedEventName">
</USelectMenu>
<USelectMenu v-if="metadataFields.length > 0" searchable searchable-placeholder="Search a field..."
class="w-full" placeholder="Select a field" :options="metadataFields"
v-model="selectedMetadataField">
</USelectMenu>
</div>
<div v-if="canSearch" class="flex gap-4 mt-4 items-center">
<div> Filter by name: </div>
<div class="h-full flex items-center text-[1.2rem]">
<input v-model="currentSearchText"
class="bg-black/70 hover:bg-black/40 rounded-lg px-4 py-1 focus:outline-none" type="text">
</div>
<USelectMenu v-if="metadataFields.length > 0" searchable searchable-placeholder="Search a field..."
class="w-full" placeholder="Select a field" :options="metadataFields"
v-model="selectedMetadataField">
</USelectMenu>
</div>
<div v-if="canSearch" class="flex gap-4 mt-4 items-center">
<div> Filter by name: </div>
<div class="h-full flex items-center text-[1.2rem]">
<input v-model="currentSearchText"
class="bg-black/70 hover:bg-black/40 rounded-lg px-4 py-1 focus:outline-none" type="text">
</div>
</div>
</CardTitled>
<div class="mt-8 overflow-y-auto px-4 flex flex-col gap-3">
<div class="text-accent poppins font-semibold">
Search results: {{ metadataFieldGroupedFiltered.length }}
</div>
<div class="mt-8 overflow-y-auto px-4 flex flex-col gap-3">
<div class="flex flex-col">
<div v-for="item of metadataFieldGroupedFiltered">
<div class="flex gap-2">
<div> {{ item._id || 'OLD_EVENTS' }} </div>
<div> {{ item.count }} </div>
<div class="text-accent poppins font-semibold">
Search results: {{ metadataFieldGroupedFiltered.length }}
</div>
<div class="flex flex-col">
<div v-for="item of metadataFieldGroupedFiltered">
<div class="flex gap-2">
<div> {{ item._id || 'OLD_EVENTS' }} </div>
<div> {{ item.count }} </div>
</div>
</div>
</div>
</div>
</div>
</CardTitled>
</div>
</template>

View File

@@ -19,7 +19,7 @@ const sections: Section[] = [
title: 'Project',
entries: [
{ label: 'Dashboard', to: '/', icon: 'far fa-home' },
// { label: 'Events', to: '/events', icon: 'far fa-bolt' },
{ label: 'Events', to: '/events', icon: 'far fa-bolt' },
{ label: 'Analyst', to: '/analyst', icon: 'far fa-microchip-ai' },
{ label: 'Report', to: '/report', icon: 'far fa-notes' },
// { label: 'AI', to: '/dashboard/settings', icon: 'far fa-robot brightness-[.4]' },

View File

@@ -0,0 +1,55 @@
<script lang="ts" setup>
definePageMeta({ layout: 'dashboard' });
const selectLabelsEvents = [
{ label: 'Day', value: 'day' },
{ label: 'Month', value: 'month' },
];
const eventsStackedSelectIndex = ref<number>(0);
</script>
<template>
<div class="w-full h-full overflow-y-auto pb-20 md:pt-4 lg:pt-0">
<div class="flex p-6 gap-6 flex-col xl:flex-row">
<CardTitled class="p-4 flex-[4]" title="Events" sub="Events stacked bar chart.">
<template #header>
<SelectButton @changeIndex="eventsStackedSelectIndex = $event"
:currentIndex="eventsStackedSelectIndex" :options="selectLabelsEvents">
</SelectButton>
</template>
<div>
<EventsStackedBarChart :slice="(selectLabelsEvents[eventsStackedSelectIndex].value as any)">
</EventsStackedBarChart>
</div>
</CardTitled>
<div class="bg-menu p-4 rounded-xl flex-[2] flex flex-col gap-10 h-full">
<div class="flex flex-col gap-1">
<div class="poppins font-semibold text-[1.4rem] text-text">
Top events
</div>
<div class="poppins text-[1rem] text-text-sub/90">
Displays key events.
</div>
</div>
<DashboardEventsChart class="w-full"> </DashboardEventsChart>
</div>
</div>
<div class="flex p-6">
<EventsMetadataAnalyzer></EventsMetadataAnalyzer>
</div>
</div>
</template>

View File

@@ -7,7 +7,7 @@ const activeProject = useActiveProject();
const mainChartSelectIndex = ref<number>(1);
const sessionsChartSelectIndex = ref<number>(1);
const eventsStackedSelectIndex = ref<number>(0);
const route = useRoute();
@@ -66,10 +66,6 @@ const selectLabels = [
{ label: 'Day', value: 'day' }
];
const selectLabelsEvents = [
{ label: 'Day', value: 'day' },
{ label: 'Month', value: 'month' },
];
</script>
@@ -123,42 +119,6 @@ const selectLabelsEvents = [
</div>
<div class="flex p-6 gap-6 flex-col xl:flex-row">
<CardTitled class="p-4 flex-[4]" title="Events" sub="Events stacked bar chart.">
<template #header>
<SelectButton @changeIndex="eventsStackedSelectIndex = $event"
:currentIndex="eventsStackedSelectIndex" :options="selectLabelsEvents">
</SelectButton>
</template>
<div>
<EventsStackedBarChart :slice="(selectLabelsEvents[eventsStackedSelectIndex].value as any)">
</EventsStackedBarChart>
</div>
</CardTitled>
<div class="bg-menu p-4 rounded-xl flex-[2] flex flex-col gap-10 h-full">
<div class="flex flex-col gap-1">
<div class="poppins font-semibold text-[1.4rem] text-text">
Top events
</div>
<div class="poppins text-[1rem] text-text-sub/90">
Displays key events.
</div>
</div>
<DashboardEventsChart class="w-full"> </DashboardEventsChart>
</div>
</div>
<div class="flex w-full justify-center mt-6 px-6">
<div class="flex w-full gap-6 flex-col xl:flex-row">
<div class="flex-1">

View File

@@ -12,7 +12,7 @@ onMounted(async () => {
});
function test() {
const res = $fetch(`/api/metrics/${activeProject.value?._id.toString()}/events/flow_from_name?name=docs_clicked`)
const res = $fetch(`/api/metrics/${activeProject.value?._id.toString()}/events/flow_from_name?name=ToggleFavorite`, signHeaders());
console.log(res);
}

View File

@@ -2,8 +2,7 @@
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
import { EventModel } from "@schema/metrics/EventSchema";
import { EVENT_METADATA_FIELDS_EXPIRE_TIME, Redis } from "~/server/services/CacheService";
import { PipelineStage } from "mongoose";
import { VisitModel } from "@schema/metrics/VisitSchema";
export default defineEventHandler(async event => {
@@ -19,27 +18,59 @@ export default defineEventHandler(async event => {
const { name: eventName } = getQuery(event);
if (!eventName) return [];
const aggregation: PipelineStage[] = [
{ $match: { project_id: project._id, name: eventName } },
{ $group: { _id: "$flowHash", count: { $sum: 1 } } },
{ $match: { _id: { $ne: null } } },
{
$lookup: {
from: "visits",
let: { flowHash: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$flowHash", "$$flowHash"] } } },
{ $group: { _id: "referrers", list: { $addToSet: "$referrer" } } },
{ $limit: 1 }
],
as: "referrers"
}
const allEvents = await EventModel.find({ project_id: project_id, name: eventName }, { flowHash: 1 });
const allFlowHashes = new Map<string, number>();
allEvents.forEach(e => {
if (!e.flowHash) return;
if (e.flowHash.length == 0) return;
if (allFlowHashes.has(e.flowHash)) {
const count = allFlowHashes.get(e.flowHash) as number;
allFlowHashes.set(e.flowHash, count + 1);
} else {
allFlowHashes.set(e.flowHash, 1);
}
];
});
const flow: { _id: string, count: number, referrers: [{ list: string[] }] }[] = await EventModel.aggregate(aggregation);
const flowHashIds = Array.from(allFlowHashes.keys());
return flow;
const allReferrers: { referrer: string, flowHash: string }[] = [];
const promises: any[] = [];
while (flowHashIds.length > 0) {
promises.push(new Promise<void>(async resolve => {
const flowHashIdsChunk = flowHashIds.splice(0, 10);
const visits = await VisitModel.find({ project_id, flowHash: { $in: flowHashIdsChunk } }, { referrer: 1, flowHash: 1 });
allReferrers.push(...visits.map(e => { return { referrer: e.referrer, flowHash: e.flowHash } }));
resolve();
}));
}
await Promise.all(promises);
const groupedFlows: Record<string, { referrers: string[] }> = {};
flowHashIds.forEach(flowHash => {
if (!groupedFlows[flowHash]) groupedFlows[flowHash] = { referrers: [] };
const target = groupedFlows[flowHash];
if (!target) return;
const referrers = allReferrers.filter(e => e.flowHash === flowHash).map(e => e.referrer);
for (const referrer of referrers) {
if (target.referrers.includes(referrer)) continue;
target.referrers.push(referrer);
}
});
const grouped: Record<string, number> = {};
for (const referrerPlusHash of allReferrers) {
const referrer = referrerPlusHash.referrer;
if (!grouped[referrer]) grouped[referrer] = 0
grouped[referrer]++;
}
return grouped;
});