add domain filter on events

This commit is contained in:
Emily
2025-02-05 16:02:32 +01:00
parent 0963201a32
commit b592695a49
10 changed files with 47 additions and 31 deletions

View File

@@ -16,7 +16,7 @@ function onChange(e: string) {
base: 'z-[999] hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer', base: 'z-[999] hover:!bg-lyx-lightmode-widget-light dark:hover:!bg-lyx-widget-lighter cursor-pointer',
active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter' active: '!bg-lyx-lightmode-widget-light dark:!bg-lyx-widget-lighter'
} }
}" class="w-full" v-if="domainList" @change="onChange" :value="domain" :options="domainList"> }" class="w-full" searchable v-if="domainList" @change="onChange" :value="domain" :options="domainList">
<template #option="{ option, active, selected }"> <template #option="{ option, active, selected }">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">

View File

@@ -76,7 +76,7 @@ export function getDefaultSnapshots(project_id: TProjectSnapshot['project_id'],
project_id, project_id,
_id: '___allTime' as any, _id: '___allTime' as any,
name: 'All Time', name: 'All Time',
from: fns.addMinutes(fns.startOfMonth(new Date(project_created_at.toString())), -new Date().getTimezoneOffset()), from: fns.addMinutes(fns.startOfMonth(new Date(project_created_at.toString())), 0),
to: new Date(Date.now()), to: new Date(Date.now()),
color: '#9362FF', color: '#9362FF',
default: true default: true

View File

@@ -1,14 +1,25 @@
<script lang="ts" setup> <script lang="ts" setup>
import EventsFunnelChart from '~/components/events/EventsFunnelChart.vue'; import EventsFunnelChart from '~/components/events/EventsFunnelChart.vue';
import DateService, { type Slice } from '@services/DateService';
definePageMeta({ layout: 'dashboard' }); definePageMeta({ layout: 'dashboard' });
const selectLabelsEvents = [
const { snapshotDuration } = useSnapshot();
const selectedLabelIndex = ref<number>(0);
const selectLabels: { label: string, value: Slice }[] = [
{ label: 'Hour', value: 'hour' },
{ label: 'Day', value: 'day' }, { label: 'Day', value: 'day' },
{ label: 'Month', value: 'month' }, { label: 'Month', value: 'month' },
]; ];
const eventsStackedSelectIndex = ref<number>(0);
const selectLabelsAvailable = computed<{ label: string, value: Slice, disabled: boolean }[]>(() => {
return selectLabels.map(e => {
return { ...e, disabled: !DateService.canUseSliceFromDays(snapshotDuration.value, e.value)[0] }
});
})
const eventsData = await useFetch(`/api/data/count`, { headers: useComputedHeaders({ custom: { 'x-schema': 'events' } }), lazy: true }); const eventsData = await useFetch(`/api/data/count`, { headers: useComputedHeaders({ custom: { 'x-schema': 'events' } }), lazy: true });
@@ -24,9 +35,6 @@ const eventsData = await useFetch(`/api/data/count`, { headers: useComputedHeade
<div> <div>
Total events: {{ eventsData.data.value?.[0]?.count || '0' }} Total events: {{ eventsData.data.value?.[0]?.count || '0' }}
</div> </div>
<div v-if="(eventsData.data.value?.[0]?.count || 0) === 0">
Waiting for your first event...
</div>
</div> </div>
<div> <div>
<LyxUiButton type="secondary" target="_blank" to="https://docs.litlyx.com/custom-events"> <LyxUiButton type="secondary" target="_blank" to="https://docs.litlyx.com/custom-events">
@@ -45,12 +53,14 @@ const eventsData = await useFetch(`/api/data/count`, { headers: useComputedHeade
<CardTitled :key="refreshKey" class="p-4 xl:flex-[4] w-full h-full" title="Events" <CardTitled :key="refreshKey" class="p-4 xl:flex-[4] w-full h-full" title="Events"
sub="Events stacked bar chart."> sub="Events stacked bar chart.">
<template #header> <template #header>
<SelectButton @changeIndex="eventsStackedSelectIndex = $event"
:currentIndex="eventsStackedSelectIndex" :options="selectLabelsEvents"> <SelectButton class="w-fit" @changeIndex="selectedLabelIndex = $event" :currentIndex="selectedLabelIndex"
:options="selectLabelsAvailable">
</SelectButton> </SelectButton>
</template> </template>
<div class="h-full"> <div class="h-full">
<EventsStackedBarChart :slice="(selectLabelsEvents[eventsStackedSelectIndex].value as any)"> <EventsStackedBarChart :slice="(selectLabelsAvailable[selectedLabelIndex].value as any)">
</EventsStackedBarChart> </EventsStackedBarChart>
</div> </div>
</CardTitled> </CardTitled>

View File

@@ -1,17 +1,16 @@
import { Redis } from "~/server/services/CacheService"; import { Redis } from "~/server/services/CacheService";
import { getRequestDataOld } from "~/server/utils/getRequestData";
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: true }); const data = await getRequestData(event, ['GUEST', 'DOMAIN', 'RANGE', 'SCHEMA']);
if (!data) return; if (!data) return;
const { schemaName, pid, from, to, model, project_id } = data; const { schemaName, pid, from, to, model, project_id, domain } = data;
const cacheKey = `count:${schemaName}:${pid}:${from}:${to}`; const cacheKey = `count:${schemaName}:${pid}:${from}:${to}:${domain}`;
const cacheExp = 60; const cacheExp = 20;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => { return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
@@ -19,7 +18,8 @@ export default defineEventHandler(async event => {
{ {
$match: { $match: {
project_id, project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) } created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain
} }
}, },
{ $count: 'count' }, { $count: 'count' },

View File

@@ -1,17 +1,16 @@
import { EventModel } from "@schema/metrics/EventSchema"; import { EventModel } from "@schema/metrics/EventSchema";
import { Redis } from "~/server/services/CacheService"; import { Redis } from "~/server/services/CacheService";
import { getRequestDataOld } from "~/server/utils/getRequestData";
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false }); const data = await getRequestData(event, ['GUEST', 'DOMAIN', 'RANGE']);
if (!data) return; if (!data) return;
const { pid, from, to, project_id, limit } = data; const { pid, from, to, project_id, limit, domain } = data;
const cacheKey = `events:${pid}:${limit}:${from}:${to}`; const cacheKey = `events:${pid}:${limit}:${from}:${to}:${domain}`;
const cacheExp = 60; const cacheExp = 20;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => { return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
@@ -19,7 +18,8 @@ export default defineEventHandler(async event => {
{ {
$match: { $match: {
project_id, project_id,
created_at: { $gte: new Date(from), $lte: new Date(to) } created_at: { $gte: new Date(from), $lte: new Date(to) },
website: domain
} }
}, },
{ $group: { _id: "$name", count: { $sum: 1, } } }, { $group: { _id: "$name", count: { $sum: 1, } } },

View File

@@ -16,7 +16,7 @@ export default defineEventHandler(async event => {
const timelineData = await executeTimelineAggregation({ const timelineData = await executeTimelineAggregation({
projectId: project_id, projectId: project_id,
model: EventModel, model: EventModel,
from, to, slice, timeOffset, domain, debug: true from, to, slice, timeOffset, domain
}); });
return timelineData; return timelineData;
}); });

View File

@@ -4,12 +4,12 @@ import { executeAdvancedTimelineAggregation } from "~/server/services/TimelineSe
export default defineEventHandler(async event => { export default defineEventHandler(async event => {
const data = await getRequestDataOld(event, { requireSchema: false, requireSlice: true }); const data = await getRequestData(event, ['GUEST', 'RANGE', 'SLICE', 'DOMAIN']);
if (!data) return; if (!data) return;
const { from, to, slice, project_id, timeOffset } = data; const { from, to, slice, project_id, timeOffset, domain } = data;
return await Redis.useCache({ key: `timeline:events_stacked:${project_id}:${slice}:${from || 'none'}:${to || 'none'}`, exp: TIMELINE_EXPIRE_TIME }, async () => { return await Redis.useCache({ key: `timeline:events_stacked:${project_id}:${slice}:${from || 'none'}:${to || 'none'}:${domain}`, exp: TIMELINE_EXPIRE_TIME }, async () => {
const timelineStackedEvents = await executeAdvancedTimelineAggregation<{ name: String }>({ const timelineStackedEvents = await executeAdvancedTimelineAggregation<{ name: String }>({
model: EventModel, model: EventModel,
@@ -17,7 +17,8 @@ export default defineEventHandler(async event => {
from, to, slice, from, to, slice,
customProjection: { name: "$_id.name" }, customProjection: { name: "$_id.name" },
customIdGroup: { name: '$name' }, customIdGroup: { name: '$name' },
timeOffset timeOffset,
domain
}) })
return timelineStackedEvents; return timelineStackedEvents;

View File

@@ -10,13 +10,14 @@ export default defineEventHandler(async event => {
const { pid, from, to, slice, project_id, timeOffset, domain } = data; const { pid, from, to, slice, project_id, timeOffset, domain } = data;
const cacheKey = `timeline:visits:${pid}:${slice}:${from}:${to}:${domain}`; const cacheKey = `timeline:visits:${pid}:${slice}:${from}:${to}:${domain}`;
const cacheExp = 60; const cacheExp = 20;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => { return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
const timelineData = await executeAdvancedTimelineAggregation({ const timelineData = await executeAdvancedTimelineAggregation({
projectId: project_id, projectId: project_id,
model: VisitModel, model: VisitModel,
from, to, slice, timeOffset, domain from, to, slice, timeOffset, domain,
debug: true
}); });
return timelineData; return timelineData;
}); });

View File

@@ -6,6 +6,7 @@ export type TEvent = {
metadata: Record<string, string>, metadata: Record<string, string>,
session: string, session: string,
flowHash: string, flowHash: string,
website: string,
created_at: Date created_at: Date
} }
@@ -15,6 +16,7 @@ const EventSchema = new Schema<TEvent>({
metadata: Schema.Types.Mixed, metadata: Schema.Types.Mixed,
session: { type: String, index: 1 }, session: { type: String, index: 1 },
flowHash: { type: String }, flowHash: { type: String },
website: { type: String, index: 1 },
created_at: { type: Date, default: () => Date.now(), index: true }, created_at: { type: Date, default: () => Date.now(), index: true },
}) })

View File

@@ -6,6 +6,7 @@ export type TSession = {
session: string, session: string,
flowHash: string, flowHash: string,
duration: number, duration: number,
website: string,
updated_at: Date, updated_at: Date,
created_at: Date, created_at: Date,
} }
@@ -14,6 +15,7 @@ const SessionSchema = new Schema<TSession>({
project_id: { type: Types.ObjectId, index: 1 }, project_id: { type: Types.ObjectId, index: 1 },
session: { type: String, required: true, index: 1 }, session: { type: String, required: true, index: 1 },
flowHash: { type: String }, flowHash: { type: String },
website: { type: String },
duration: { type: Number, required: true, default: 0 }, duration: { type: Number, required: true, default: 0 },
updated_at: { type: Date, default: () => Date.now() }, updated_at: { type: Date, default: () => Date.now() },
created_at: { type: Date, default: () => Date.now(), index: true }, created_at: { type: Date, default: () => Date.now(), index: true },