mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-10 15:58:38 +01:00
implementing snapshots
This commit is contained in:
@@ -32,8 +32,8 @@ const { isOpen, close } = useMenu();
|
||||
const { snapshots, snapshot } = useSnapshot();
|
||||
|
||||
const snapshotsItems = computed(() => {
|
||||
if (!snapshots.data.value) return []
|
||||
return snapshots.data.value as any[];
|
||||
if (!snapshots.value) return []
|
||||
return snapshots.value as any[];
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { BrowsersAggregated } from '~/server/api/metrics/[project_id]/data/browsers';
|
||||
|
||||
const activeProject = await useActiveProject();
|
||||
const { data: events, pending, refresh } = await useFetch<BrowsersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/browsers`, {
|
||||
...signHeaders(),
|
||||
lazy: true
|
||||
});
|
||||
|
||||
|
||||
const { data: browsers, pending, refresh } = useBrowsersData(10);
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
function showMore() {
|
||||
|
||||
|
||||
showDialog.value = true;
|
||||
dialogBarData.value = [];
|
||||
isDataLoading.value = true;
|
||||
|
||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/browsers`, signHeaders({
|
||||
'x-query-limit': '200'
|
||||
})).then(data => {
|
||||
dialogBarData.value = data;
|
||||
const moreRes = useBrowsersData(200);
|
||||
|
||||
moreRes.onResponse(data => {
|
||||
dialogBarData.value = data.value || [];
|
||||
isDataLoading.value = false;
|
||||
});
|
||||
|
||||
@@ -32,7 +23,7 @@ function showMore() {
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="events || []"
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="browsers || []"
|
||||
desc="The browsers most used to search your website." :dataIcons="false" :loading="pending"
|
||||
label="Top Browsers" sub-label="Browsers"></DashboardBarsCard>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { DevicesAggregated } from '~/server/api/metrics/[project_id]/data/devices';
|
||||
|
||||
const activeProject = await useActiveProject();
|
||||
const { data: events, pending, refresh } = await useFetch<DevicesAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/devices`, {
|
||||
...signHeaders(),
|
||||
lazy: true
|
||||
});
|
||||
|
||||
const { data: devices, pending, refresh } = useDevicesData(10);
|
||||
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
@@ -18,10 +11,10 @@ function showMore() {
|
||||
dialogBarData.value = [];
|
||||
isDataLoading.value = true;
|
||||
|
||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/devices`, signHeaders({
|
||||
'x-query-limit': '200'
|
||||
})).then(data => {
|
||||
dialogBarData.value = data;
|
||||
const moreRes = useDevicesData(200);
|
||||
|
||||
moreRes.onResponse(data => {
|
||||
dialogBarData.value = data.value || [];
|
||||
isDataLoading.value = false;
|
||||
});
|
||||
|
||||
@@ -32,7 +25,7 @@ function showMore() {
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="events || []" :dataIcons="false"
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="devices || []" :dataIcons="false"
|
||||
desc="The devices most used to access your website." :loading="pending" label="Top Devices"
|
||||
sub-label="Devices"></DashboardBarsCard>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { CustomEventsAggregated } from '~/server/api/metrics/[project_id]/visits/events';
|
||||
|
||||
const activeProject = await useActiveProject();
|
||||
const { data: events, pending, refresh } = await useFetch<CustomEventsAggregated[]>(`/api/metrics/${activeProject.value?._id}/visits/events`, {
|
||||
...signHeaders(),
|
||||
lazy: true
|
||||
});
|
||||
const { data: events, pending, refresh } = useEventsData();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -23,10 +17,10 @@ function showMore() {
|
||||
dialogBarData.value = [];
|
||||
isDataLoading.value = true;
|
||||
|
||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/visits/events`, signHeaders({
|
||||
'x-query-limit': '200'
|
||||
})).then(data => {
|
||||
dialogBarData.value = data;
|
||||
const moreRes = useEventsData(200);
|
||||
|
||||
moreRes.onResponse(data => {
|
||||
dialogBarData.value = data.value || [];
|
||||
isDataLoading.value = false;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { CountriesAggregated } from '~/server/api/metrics/[project_id]/data/countries';
|
||||
import type { IconProvider } from './BarsCard.vue';
|
||||
|
||||
const activeProject = await useActiveProject();
|
||||
const { data: countries, pending, refresh } = await useFetch<CountriesAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/countries`, {
|
||||
...signHeaders(),
|
||||
lazy: true
|
||||
});
|
||||
const { data: countries, pending, refresh } = useGeolocationData(10);
|
||||
|
||||
function iconProvider(id: string): ReturnType<IconProvider> {
|
||||
if (id === 'self') return ['icon', 'fas fa-link'];
|
||||
@@ -23,17 +18,18 @@ const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
function showMore() {
|
||||
|
||||
|
||||
showDialog.value = true;
|
||||
dialogBarData.value = [];
|
||||
isDataLoading.value = true;
|
||||
|
||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/countries`, signHeaders({
|
||||
'x-query-limit': '200'
|
||||
})).then(data => {
|
||||
dialogBarData.value = data;
|
||||
const moreRes = useGeolocationData(200);
|
||||
|
||||
moreRes.onResponse(data => {
|
||||
dialogBarData.value = data.value?.map(e => {
|
||||
return { ...e, icon: iconProvider(e._id) }
|
||||
}) || [];
|
||||
isDataLoading.value = false;
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { OssAggregated } from '~/server/api/metrics/[project_id]/data/oss';
|
||||
|
||||
const activeProject = await useActiveProject();
|
||||
const { data: events, pending, refresh } = await useFetch<OssAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/oss`, {
|
||||
...signHeaders(),
|
||||
lazy: true
|
||||
});
|
||||
|
||||
|
||||
const { data: oss, pending, refresh } = useOssData()
|
||||
const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
|
||||
function showMore() {
|
||||
|
||||
|
||||
showDialog.value = true;
|
||||
dialogBarData.value = [];
|
||||
isDataLoading.value = true;
|
||||
|
||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/oss`, signHeaders({
|
||||
'x-query-limit': '200'
|
||||
})).then(data => {
|
||||
dialogBarData.value = data;
|
||||
const moreRes = useOssData(200);
|
||||
|
||||
moreRes.onResponse(data => {
|
||||
dialogBarData.value = data.value || [];
|
||||
isDataLoading.value = false;
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +23,7 @@ function showMore() {
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="events || []"
|
||||
<DashboardBarsCard @showMore="showMore()" @dataReload="refresh" :data="oss || []"
|
||||
desc="The operating systems most commonly used by your website's visitors." :dataIcons="false"
|
||||
:loading="pending" label="Top OS" sub-label="OSs"></DashboardBarsCard>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
|
||||
import type { ReferrersAggregated } from '~/server/api/metrics/[project_id]/data/referrers';
|
||||
import type { IconProvider } from './BarsCard.vue';
|
||||
import ReferrerBarChart from '../referrer/ReferrerBarChart.vue';
|
||||
|
||||
const activeProject = await useActiveProject();
|
||||
|
||||
const { safeSnapshotDates, snapshot } = useSnapshot();
|
||||
|
||||
const { data: events, pending, refresh } = await useFetch<ReferrersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/referrers`, {
|
||||
...signHeaders({
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to
|
||||
}),
|
||||
lazy: true
|
||||
});
|
||||
|
||||
watch(snapshot,()=>{
|
||||
refresh();
|
||||
})
|
||||
const { data: events, pending, refresh } = useReferrersData(10);
|
||||
|
||||
|
||||
function iconProvider(id: string): ReturnType<IconProvider> {
|
||||
@@ -37,7 +22,6 @@ const { showDialog, dialogBarData, isDataLoading } = useBarCardDialog();
|
||||
const customDialog = useCustomDialog();
|
||||
|
||||
function onShowDetails(referrer: string) {
|
||||
|
||||
customDialog.openDialog(ReferrerBarChart, { slice: 'day', referrer });
|
||||
}
|
||||
|
||||
@@ -46,19 +30,19 @@ function onShowDetails(referrer: string) {
|
||||
|
||||
function showMore() {
|
||||
|
||||
|
||||
showDialog.value = true;
|
||||
dialogBarData.value = [];
|
||||
isDataLoading.value = true;
|
||||
|
||||
$fetch<any[]>(`/api/metrics/${activeProject.value?._id}/data/referrers`, signHeaders({
|
||||
'x-query-limit': '200'
|
||||
})).then(data => {
|
||||
dialogBarData.value = data.map(e => {
|
||||
|
||||
const moreRes = useReferrersData(200);
|
||||
|
||||
moreRes.onResponse(data => {
|
||||
dialogBarData.value = data.value?.map(e => {
|
||||
return { ...e, icon: iconProvider(e._id) }
|
||||
});
|
||||
}) || [];
|
||||
isDataLoading.value = false;
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -9,17 +9,26 @@ export type CustomFetchOptions = {
|
||||
lazy?: boolean
|
||||
}
|
||||
|
||||
type OnResponseCallback<TData> = (data: Ref<TData | undefined>) => any
|
||||
|
||||
export function useCustomFetch<T>(url: NitroFetchRequest, getHeaders: () => Record<string, string>, options?: CustomFetchOptions) {
|
||||
|
||||
const pending = ref<boolean>(false);
|
||||
const data = ref<T | undefined>();
|
||||
const error = ref<Error | undefined>();
|
||||
|
||||
let onResponseCallback: OnResponseCallback<T> = () => { }
|
||||
|
||||
const onResponse = (callback: OnResponseCallback<T>) => {
|
||||
onResponseCallback = callback;
|
||||
}
|
||||
|
||||
const execute = async () => {
|
||||
pending.value = true;
|
||||
error.value = undefined;
|
||||
try {
|
||||
data.value = await $fetch<T>(url, { headers: getHeaders() });
|
||||
onResponseCallback(data);
|
||||
} catch (err) {
|
||||
error.value = err as Error;
|
||||
} finally {
|
||||
@@ -37,5 +46,7 @@ export function useCustomFetch<T>(url: NitroFetchRequest, getHeaders: () => Reco
|
||||
});
|
||||
}
|
||||
|
||||
return { pending, execute, data, error };
|
||||
const refresh = execute;
|
||||
|
||||
return { pending, execute, data, error, refresh, onResponse };
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import type { Slice } from "@services/DateService";
|
||||
import DateService from "@services/DateService";
|
||||
import type { MetricsCounts } from "~/server/api/metrics/[project_id]/counts";
|
||||
import type { BrowsersAggregated } from "~/server/api/metrics/[project_id]/data/browsers";
|
||||
import type { CountriesAggregated } from "~/server/api/metrics/[project_id]/data/countries";
|
||||
import type { DevicesAggregated } from "~/server/api/metrics/[project_id]/data/devices";
|
||||
import type { CustomEventsAggregated } from "~/server/api/metrics/[project_id]/data/events";
|
||||
import type { OssAggregated } from "~/server/api/metrics/[project_id]/data/oss";
|
||||
import type { ReferrersAggregated } from "~/server/api/metrics/[project_id]/data/referrers";
|
||||
import type { VisitsWebsiteAggregated } from "~/server/api/metrics/[project_id]/data/websites";
|
||||
import type { MetricsTimeline } from "~/server/api/metrics/[project_id]/timeline/generic";
|
||||
@@ -97,28 +102,61 @@ const getFromToHeaders = (headers: Record<string, string> = {}) => ({
|
||||
...headers
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
export function useWebsitesData(limit: number = 10) {
|
||||
const activeProject = useActiveProject();
|
||||
const res = useFetch<VisitsWebsiteAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/websites`, {
|
||||
...signHeaders({
|
||||
'x-query-limit': limit.toString(),
|
||||
'x-from': safeSnapshotDates.value.from,
|
||||
'x-to': safeSnapshotDates.value.to
|
||||
}),
|
||||
key: `websites_data:${limit}:${safeSnapshotDates.value.from}:${safeSnapshotDates.value.to}`,
|
||||
lazy: true,
|
||||
});
|
||||
const res = useCustomFetch<ReferrersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/websites`,
|
||||
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||
{ lazy: false, watchProps: [snapshot] }
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
export function useEventsData(limit: number = 10) {
|
||||
const res = useCustomFetch<CustomEventsAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/events`,
|
||||
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||
{ lazy: false, watchProps: [snapshot] }
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function useReferrersData(limit: number = 10) {
|
||||
|
||||
const res = useCustomFetch<ReferrersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/referrers`,
|
||||
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||
{ lazy: true, watchProps: [snapshot] }
|
||||
{ lazy: false, watchProps: [snapshot] }
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function useBrowsersData(limit: number = 10) {
|
||||
const res = useCustomFetch<BrowsersAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/browsers`,
|
||||
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||
{ lazy: false, watchProps: [snapshot] }
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function useOssData(limit: number = 10) {
|
||||
const res = useCustomFetch<OssAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/oss`,
|
||||
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||
{ lazy: false, watchProps: [snapshot] }
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function useGeolocationData(limit: number = 10) {
|
||||
const res = useCustomFetch<CountriesAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/countries`,
|
||||
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||
{ lazy: false, watchProps: [snapshot] }
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
export function useDevicesData(limit: number = 10) {
|
||||
const res = useCustomFetch<DevicesAggregated[]>(`/api/metrics/${activeProject.value?._id}/data/devices`,
|
||||
() => signHeaders(getFromToHeaders({ 'x-query-limit': limit.toString() })).headers,
|
||||
{ lazy: false, watchProps: [snapshot] }
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -1,16 +1,62 @@
|
||||
import type { TProjectSnapshot } from "@schema/ProjectSnapshot";
|
||||
|
||||
const snapshots = useFetch<TProjectSnapshot[]>('/api/project/snapshots', {
|
||||
const remoteSnapshots = useFetch<TProjectSnapshot[]>('/api/project/snapshots', {
|
||||
...signHeaders(),
|
||||
immediate: false
|
||||
});
|
||||
|
||||
const snapshot = ref<TProjectSnapshot>();
|
||||
|
||||
watch(snapshots.data, () => {
|
||||
if (!snapshots.data.value) return;
|
||||
snapshot.value = snapshots.data.value[0];
|
||||
});
|
||||
const snapshots = computed(() => {
|
||||
|
||||
const activeProject = useActiveProject();
|
||||
|
||||
const getDefaultSnapshots: () => TProjectSnapshot[] = () => [
|
||||
{
|
||||
project_id: activeProject.value?._id as any,
|
||||
_id: 'deafult0' as any,
|
||||
name: 'All',
|
||||
from: new Date(activeProject.value?.created_at || 0),
|
||||
to: new Date(Date.now()),
|
||||
color: '#CCCCCC'
|
||||
},
|
||||
{
|
||||
project_id: activeProject.value?._id as any,
|
||||
_id: 'deafult1' as any,
|
||||
name: 'Last month',
|
||||
from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30),
|
||||
to: new Date(Date.now()),
|
||||
color: '#00CC00'
|
||||
},
|
||||
{
|
||||
project_id: activeProject.value?._id as any,
|
||||
_id: 'deafult2' as any,
|
||||
name: 'Last week',
|
||||
from: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7),
|
||||
to: new Date(Date.now()),
|
||||
color: '#0F02D2'
|
||||
},
|
||||
{
|
||||
project_id: activeProject.value?._id as any,
|
||||
_id: 'deafult3' as any,
|
||||
name: 'Last day',
|
||||
from: new Date(Date.now() - 1000 * 60 * 60 * 24),
|
||||
to: new Date(Date.now()),
|
||||
color: '#CC11CC'
|
||||
}
|
||||
]
|
||||
|
||||
return [
|
||||
...getDefaultSnapshots(),
|
||||
...(remoteSnapshots.data.value || [])
|
||||
];
|
||||
})
|
||||
|
||||
const snapshot = ref<TProjectSnapshot>(snapshots.value[0]);
|
||||
|
||||
// watch(remoteSnapshots.data, () => {
|
||||
// if (!remoteSnapshots.data.value) return;
|
||||
// snapshot.value = remoteSnapshots.data.value[0];
|
||||
// });
|
||||
|
||||
const safeSnapshotDates = computed(() => {
|
||||
const from = new Date(snapshot.value?.from || 0).toISOString();
|
||||
@@ -19,8 +65,8 @@ const safeSnapshotDates = computed(() => {
|
||||
})
|
||||
|
||||
export function useSnapshot() {
|
||||
if (snapshots.status.value === 'idle') {
|
||||
snapshots.execute();
|
||||
if (remoteSnapshots.status.value === 'idle') {
|
||||
remoteSnapshots.execute();
|
||||
}
|
||||
return { snapshot, snapshots, safeSnapshotDates }
|
||||
}
|
||||
@@ -91,7 +91,7 @@ const selectLabels = [
|
||||
<DashboardTopCards></DashboardTopCards>
|
||||
|
||||
|
||||
<!-- <div class="mt-6 px-6 flex gap-6 flex-col 2xl:flex-row">
|
||||
<div class="mt-6 px-6 flex gap-6 flex-col 2xl:flex-row">
|
||||
|
||||
<CardTitled class="p-4 flex-1" title="Visits trends" sub="Shows trends in page visits.">
|
||||
<template #header>
|
||||
@@ -117,9 +117,9 @@ const selectLabels = [
|
||||
</div>
|
||||
</CardTitled>
|
||||
|
||||
</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">
|
||||
@@ -129,7 +129,7 @@ const selectLabels = [
|
||||
<DashboardEventsBarCard></DashboardEventsBarCard>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex w-full justify-center mt-6 px-6">
|
||||
@@ -143,7 +143,7 @@ const selectLabels = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="flex w-full justify-center mt-6 px-6">
|
||||
<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">
|
||||
<DashboardOssBarCard></DashboardOssBarCard>
|
||||
@@ -162,7 +162,7 @@ const selectLabels = [
|
||||
<div class="flex-1">
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -22,12 +22,26 @@ export default defineEventHandler(async event => {
|
||||
const limit = getRequestHeader(event, 'x-query-limit');
|
||||
const numLimit = parseInt(limit || '10');
|
||||
|
||||
|
||||
const from = getRequestHeader(event, 'x-from');
|
||||
const to = getRequestHeader(event, 'x-to');
|
||||
|
||||
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||
|
||||
return await Redis.useCache({
|
||||
key: `browsers:${project_id}:${numLimit}`,
|
||||
key: `browsers:${project_id}:${numLimit}:${from}:${to}`,
|
||||
exp: DATA_EXPIRE_TIME
|
||||
}, async () => {
|
||||
const browsers: BrowsersAggregated[] = await VisitModel.aggregate([
|
||||
{ $match: { project_id: project._id }, },
|
||||
{
|
||||
$match: {
|
||||
project_id: project._id,
|
||||
created_at: {
|
||||
$gte: new Date(from),
|
||||
$lte: new Date(to)
|
||||
}
|
||||
},
|
||||
},
|
||||
{ $group: { _id: "$browser", count: { $sum: 1, } } },
|
||||
{ $sort: { count: -1 } },
|
||||
{ $limit: numLimit }
|
||||
|
||||
@@ -21,13 +21,26 @@ export default defineEventHandler(async event => {
|
||||
const limit = getRequestHeader(event, 'x-query-limit');
|
||||
const numLimit = parseInt(limit || '10');
|
||||
|
||||
const from = getRequestHeader(event, 'x-from');
|
||||
const to = getRequestHeader(event, 'x-to');
|
||||
|
||||
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||
|
||||
return await Redis.useCache({
|
||||
key: `countries:${project_id}:${numLimit}`,
|
||||
key: `countries:${project_id}:${numLimit}:${from}:${to}`,
|
||||
exp: DATA_EXPIRE_TIME
|
||||
}, async () => {
|
||||
const countries: CountriesAggregated[] = await VisitModel.aggregate([
|
||||
{ $match: { project_id: project._id, country: { $ne: null } }, },
|
||||
{
|
||||
$match: {
|
||||
project_id: project._id,
|
||||
country: { $ne: null },
|
||||
created_at: {
|
||||
$gte: new Date(from),
|
||||
$lte: new Date(to)
|
||||
}
|
||||
},
|
||||
},
|
||||
{ $group: { _id: "$country", count: { $sum: 1, } } },
|
||||
{ $sort: { count: -1 } },
|
||||
{ $limit: numLimit }
|
||||
|
||||
@@ -20,13 +20,26 @@ export default defineEventHandler(async event => {
|
||||
const limit = getRequestHeader(event, 'x-query-limit');
|
||||
const numLimit = parseInt(limit || '10');
|
||||
|
||||
const from = getRequestHeader(event, 'x-from');
|
||||
const to = getRequestHeader(event, 'x-to');
|
||||
|
||||
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||
|
||||
return await Redis.useCache({
|
||||
key: `devices:${project_id}:${numLimit}`,
|
||||
key: `devices:${project_id}:${numLimit}:${from}:${to}`,
|
||||
exp: DATA_EXPIRE_TIME
|
||||
}, async () => {
|
||||
const devices: DevicesAggregated[] = await VisitModel.aggregate([
|
||||
{ $match: { project_id: project._id, device: { $ne: null } }, },
|
||||
{
|
||||
$match: {
|
||||
project_id: project._id,
|
||||
device: { $ne: null },
|
||||
created_at: {
|
||||
$gte: new Date(from),
|
||||
$lte: new Date(to)
|
||||
}
|
||||
},
|
||||
},
|
||||
{ $group: { _id: "$device", count: { $sum: 1, } } },
|
||||
{ $sort: { count: -1 } },
|
||||
{ $limit: numLimit }
|
||||
|
||||
49
dashboard/server/api/metrics/[project_id]/data/events.ts
Normal file
49
dashboard/server/api/metrics/[project_id]/data/events.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||
import { EventModel } from "@schema/metrics/EventSchema";
|
||||
import { DATA_EXPIRE_TIME, Redis } from "~/server/services/CacheService";
|
||||
|
||||
export type CustomEventsAggregated = {
|
||||
_id: string,
|
||||
count: number
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const project_id = getRequestProjectId(event);
|
||||
if (!project_id) return;
|
||||
|
||||
const user = getRequestUser(event);
|
||||
const project = await getUserProjectFromId(project_id, user);
|
||||
if (!project) return;
|
||||
|
||||
const from = getRequestHeader(event, 'x-from');
|
||||
const to = getRequestHeader(event, 'x-to');
|
||||
|
||||
|
||||
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||
|
||||
|
||||
return await Redis.useCache({
|
||||
key: `events:${project_id}:${from}:${to}`,
|
||||
exp: DATA_EXPIRE_TIME
|
||||
}, async () => {
|
||||
|
||||
const events: CustomEventsAggregated[] = await EventModel.aggregate([
|
||||
{
|
||||
$match: {
|
||||
project_id: project._id, created_at: {
|
||||
$gte: new Date(from),
|
||||
$lte: new Date(to)
|
||||
}
|
||||
},
|
||||
},
|
||||
{ $group: { _id: "$name", count: { $sum: 1, } } },
|
||||
{ $sort: { count: -1 } }
|
||||
]);
|
||||
|
||||
return events;
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
@@ -22,13 +22,25 @@ export default defineEventHandler(async event => {
|
||||
const limit = getRequestHeader(event, 'x-query-limit');
|
||||
const numLimit = parseInt(limit || '10');
|
||||
|
||||
const from = getRequestHeader(event, 'x-from');
|
||||
const to = getRequestHeader(event, 'x-to');
|
||||
|
||||
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||
|
||||
return await Redis.useCache({
|
||||
key: `oss:${project_id}:${numLimit}`,
|
||||
key: `oss:${project_id}:${numLimit}:${from}:${to}`,
|
||||
exp: DATA_EXPIRE_TIME
|
||||
}, async () => {
|
||||
const oss: OssAggregated[] = await VisitModel.aggregate([
|
||||
{ $match: { project_id: project._id }, },
|
||||
{
|
||||
$match: {
|
||||
project_id: project._id,
|
||||
created_at: {
|
||||
$gte: new Date(from),
|
||||
$lte: new Date(to)
|
||||
}
|
||||
},
|
||||
},
|
||||
{ $group: { _id: "$os", count: { $sum: 1, } } },
|
||||
{ $sort: { count: -1 } },
|
||||
{ $limit: numLimit }
|
||||
|
||||
@@ -22,13 +22,25 @@ export default defineEventHandler(async event => {
|
||||
const limit = getRequestHeader(event, 'x-query-limit');
|
||||
const numLimit = parseInt(limit || '10');
|
||||
|
||||
const from = getRequestHeader(event, 'x-from');
|
||||
const to = getRequestHeader(event, 'x-to');
|
||||
|
||||
if (!from || !to) return setResponseStatus(event, 400, 'x-from and x-to headers missing');
|
||||
|
||||
return await Redis.useCache({
|
||||
key: `referrers:${project_id}:${numLimit}`,
|
||||
key: `referrers:${project_id}:${numLimit}:${from}:${to}`,
|
||||
exp: DATA_EXPIRE_TIME
|
||||
}, async () => {
|
||||
const referrers: ReferrersAggregated[] = await VisitModel.aggregate([
|
||||
{ $match: { project_id: project._id }, },
|
||||
{
|
||||
$match: {
|
||||
project_id: project._id,
|
||||
created_at: {
|
||||
$gte: new Date(from),
|
||||
$lte: new Date(to)
|
||||
}
|
||||
},
|
||||
},
|
||||
{ $group: { _id: "$referrer", count: { $sum: 1, } } },
|
||||
{ $sort: { count: -1 } },
|
||||
{ $limit: numLimit }
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
import { getUserProjectFromId } from "~/server/LIVE_DEMO_DATA";
|
||||
import { ProjectModel } from "@schema/ProjectSchema";
|
||||
import { EventModel } from "@schema/metrics/EventSchema";
|
||||
|
||||
export type CustomEventsAggregated = {
|
||||
_id: string,
|
||||
count: number
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
const project_id = getRequestProjectId(event);
|
||||
if (!project_id) return;
|
||||
|
||||
const user = getRequestUser(event);
|
||||
const project = await getUserProjectFromId(project_id, user);
|
||||
if (!project) return;
|
||||
|
||||
|
||||
const websites: CustomEventsAggregated[] = await EventModel.aggregate([
|
||||
{ $match: { project_id: project._id }, },
|
||||
{ $group: { _id: "$name", count: { $sum: 1, } } },
|
||||
{ $sort: { count: -1 } }
|
||||
]);
|
||||
|
||||
return websites;
|
||||
|
||||
|
||||
});
|
||||
@@ -36,7 +36,7 @@ const VisitSchema = new Schema<TVisit>({
|
||||
website: { type: String, required: true },
|
||||
page: { type: String, required: true },
|
||||
referrer: { type: String, required: true },
|
||||
created_at: { type: Date, default: () => Date.now() },
|
||||
created_at: { type: Date, default: () => Date.now(), index: true },
|
||||
})
|
||||
|
||||
export const VisitModel = model<TVisit>('visits', VisitSchema);
|
||||
|
||||
Reference in New Issue
Block a user