[NOT READY] start change aggregation timeline

This commit is contained in:
Emily
2024-12-06 17:04:29 +01:00
parent 06768b6cdc
commit ad8e9e1ead
10 changed files with 77 additions and 77 deletions

View File

@@ -139,7 +139,6 @@ const { snapshotDuration } = useSnapshot();
const selectLabels: { label: string, value: Slice }[] = [ const selectLabels: { label: string, value: Slice }[] = [
{ label: 'Hour', value: 'hour' }, { label: 'Hour', value: 'hour' },
{ label: 'Day', value: 'day' }, { label: 'Day', value: 'day' },
// { label: 'Week', value: 'week' },
{ label: 'Month', value: 'month' }, { label: 'Month', value: 'month' },
]; ];
@@ -159,7 +158,11 @@ const allDatesFull = ref<string[]>([]);
function transformResponse(input: { _id: string, count: number }[]) { function transformResponse(input: { _id: string, count: number }[]) {
const data = input.map(e => e.count); const data = input.map(e => e.count);
const labels = input.map(e => DateService.getChartLabelFromISO(e._id, navigator.language, selectedSlice.value));
console.log('RESPONSE', input);
const labels = input.map(e => DateService.getChartLabelFromISO(e._id, new Date().getTimezoneOffset(), selectedSlice.value));
console.log('LABELS', input);
if (input.length > 0) allDatesFull.value = input.map(e => e._id.toString()); if (input.length > 0) allDatesFull.value = input.map(e => e._id.toString());
return { data, labels } return { data, labels }
} }
@@ -223,9 +226,6 @@ function onDataReady() {
const maxChartY = Math.max(...visitsData.data.value.data, ...sessionsData.data.value.data); const maxChartY = Math.max(...visitsData.data.value.data, ...sessionsData.data.value.data);
const maxEventSize = Math.max(...eventsData.data.value.data) const maxEventSize = Math.max(...eventsData.data.value.data)
const currentDateTime = Date.now();
chartData.value.datasets[0].data = visitsData.data.value.data; chartData.value.datasets[0].data = visitsData.data.value.data;
chartData.value.datasets[1].data = sessionsData.data.value.data; chartData.value.datasets[1].data = sessionsData.data.value.data;
chartData.value.datasets[2].data = eventsData.data.value.data.map(e => { chartData.value.datasets[2].data = eventsData.data.value.data.map(e => {

View File

@@ -10,7 +10,7 @@ const { safeSnapshotDates } = useSnapshot()
function transformResponse(input: { _id: string, count: number }[]) { function transformResponse(input: { _id: string, count: number }[]) {
const data = input.map(e => e.count); const data = input.map(e => e.count);
const labels = input.map(e => DateService.getChartLabelFromISO(e._id, navigator.language, props.slice)); const labels = input.map(e => DateService.getChartLabelFromISO(e._id, new Date().getTimezoneOffset(), props.slice));
return { data, labels } return { data, labels }
} }

View File

@@ -18,7 +18,7 @@ function transformResponse(input: { _id: string, count: number }[]) {
const data = input.map(e => e.count || 0); const data = input.map(e => e.count || 0);
const labels = input.map(e => DateService.getChartLabelFromISO(e._id, navigator.language, chartSlice.value)); const labels = input.map(e => DateService.getChartLabelFromISO(e._id, new Date().getTimezoneOffset(), chartSlice.value));
const pool = [...input.map(e => e.count || 0)]; const pool = [...input.map(e => e.count || 0)];

View File

@@ -10,7 +10,7 @@ const { safeSnapshotDates } = useSnapshot()
function transformResponse(input: { _id: string, count: number }[]) { function transformResponse(input: { _id: string, count: number }[]) {
const data = input.map(e => e.count); const data = input.map(e => e.count);
const labels = input.map(e => DateService.getChartLabelFromISO(e._id, navigator.language, props.slice)); const labels = input.map(e => DateService.getChartLabelFromISO(e._id, new Date().getTimezoneOffset(), props.slice));
return { data, labels } return { data, labels }
} }

View File

@@ -44,16 +44,16 @@ const selfhosted = useSelfhosted();
<BannerLimitsInfo v-if="!selfhosted" :key="refreshKey"></BannerLimitsInfo> <BannerLimitsInfo v-if="!selfhosted" :key="refreshKey"></BannerLimitsInfo>
<BannerOffer v-if="!selfhosted" :key="refreshKey"></BannerOffer> <BannerOffer v-if="!selfhosted" :key="refreshKey"></BannerOffer>
</div> </div>
<!--
<div> <div>
<DashboardTopSection :key="refreshKey"></DashboardTopSection> <DashboardTopSection :key="refreshKey"></DashboardTopSection>
<DashboardTopCards :key="refreshKey"></DashboardTopCards> <DashboardTopCards :key="refreshKey"></DashboardTopCards>
</div> </div> -->
<div class="mt-6 px-6 flex gap-6 flex-col 2xl:flex-row w-full"> <div class="mt-6 px-6 flex gap-6 flex-col 2xl:flex-row w-full">
<DashboardActionableChart :key="refreshKey"></DashboardActionableChart> <DashboardActionableChart :key="refreshKey"></DashboardActionableChart>
</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 w-full gap-6 flex-col xl:flex-row">
<div class="flex-1"> <div class="flex-1">
@@ -85,7 +85,7 @@ const selfhosted = useSelfhosted();
<BarCardOperatingSystems :key="refreshKey"></BarCardOperatingSystems> <BarCardOperatingSystems :key="refreshKey"></BarCardOperatingSystems>
</div> </div>
</div> </div>
</div> </div> -->
</div> </div>

View File

@@ -13,17 +13,12 @@ export default defineEventHandler(async event => {
const cacheExp = 60; const cacheExp = 60;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => { return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
const timelineData = await executeTimelineAggregation({ const timelineData = await executeTimelineAggregation({
projectId: project_id, projectId: project_id,
model: EventModel, model: EventModel,
from, to, slice, from, to, slice,
}); });
return timelineData;
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, slice, from, to);
return timelineFilledMerged;
}); });

View File

@@ -18,9 +18,7 @@ export default defineEventHandler(async event => {
model: SessionModel, model: SessionModel,
from, to, slice, from, to, slice,
}); });
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, slice, from, to); return timelineData;
return timelineFilledMerged;
}); });

View File

@@ -13,16 +13,12 @@ export default defineEventHandler(async event => {
const cacheExp = 60; const cacheExp = 60;
return await Redis.useCacheV2(cacheKey, cacheExp, async () => { return await Redis.useCacheV2(cacheKey, cacheExp, async () => {
const timelineData = await executeTimelineAggregation({ const timelineData = await executeTimelineAggregation({
projectId: project_id, projectId: project_id,
model: VisitModel, model: VisitModel,
from, to, slice, from, to, slice
}); });
return timelineData;
const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, slice, from, to);
return timelineFilledMerged;
}); });

View File

@@ -10,6 +10,7 @@ export type TimelineAggregationOptions = {
from: string | number, from: string | number,
to: string | number, to: string | number,
slice: Slice, slice: Slice,
dateOffset?: number,
debug?: boolean debug?: boolean
} }
@@ -27,9 +28,9 @@ export async function executeAdvancedTimelineAggregation<T = {}>(options: Advanc
options.customProjection = options.customProjection || {}; options.customProjection = options.customProjection || {};
options.customIdGroup = options.customIdGroup || {}; options.customIdGroup = options.customIdGroup || {};
const { group, sort } = DateService.getQueryDateRange(options.slice); const { dateFromParts, granularity } = DateService.getGranularityData(options.slice, '$tmpDate');
if (!sort) throw Error('Slice is probably not correct'); if (!dateFromParts) throw Error('Slice is probably not correct');
const [sliceValid, errorOrDays] = checkSliceValidity(options.from, options.to, options.slice); const [sliceValid, errorOrDays] = checkSliceValidity(options.from, options.to, options.slice);
@@ -40,31 +41,60 @@ export async function executeAdvancedTimelineAggregation<T = {}>(options: Advanc
{ {
$match: { $match: {
project_id: options.projectId, project_id: options.projectId,
created_at: { $gte: new Date(options.from), $lte: new Date(options.to) }, created_at: {
$gte: new Date(options.from),
$lte: new Date(options.to)
},
...options.customMatch ...options.customMatch
} }
}, },
{
$addFields: {
tmpDate: {
$dateSubtract: {
startDate: "$created_at",
unit: "minute",
amount: options.dateOffset || -60
}
}
}
},
{
$addFields: { isoDate: { $dateFromParts: dateFromParts } }
},
{ {
$group: { $group: {
_id: { ...group, ...options.customIdGroup }, _id: { isoDate: "$isoDate", ...options.customIdGroup },
count: { $sum: 1 }, count: { $sum: 1 },
firstDate: { $first: '$created_at' },
...options.customGroup ...options.customGroup
} }
}, },
{ $sort: { firstDate: 1 } }, {
$sort: { "_id.isoDate": 1 }
},
{
$densify: {
field: "_id.isoDate",
range: {
step: 1,
unit: granularity,
bounds: "full"
}
}
},
{
$addFields: { count: { $ifNull: ["$count", 0] }, }
},
{ {
$project: { $project: {
_id: "$firstDate", _id: '$_id.isoDate',
count: "$count", count: '$count',
...options.customProjection ...options.customProjection
} }
} }
] as any; ] as any;
if (options.debug === true) { if (options.debug === true) {
console.log('---------- SORT ----------')
console.log(JSON.stringify(sort, null, 2));
console.log('---------- AGGREAGATION ----------') console.log('---------- AGGREAGATION ----------')
console.log(JSON.stringify(aggregation, null, 2)); console.log(JSON.stringify(aggregation, null, 2));
} }

View File

@@ -32,12 +32,13 @@ class DateService {
public slicesData = slicesData; public slicesData = slicesData;
getChartLabelFromISO(iso: string, locale: string, slice: Slice) { getChartLabelFromISO(iso: string, offset: number, slice: Slice) {
if (slice === 'hour') return fns.format(iso, 'HH:mm'); const date = new Date(new Date(iso).getTime() - offset * 1000 * 60);
if (slice === 'day') return fns.format(iso, 'dd/MM'); if (slice === 'hour') return fns.format(date, 'HH:mm');
if (slice === 'week') return fns.format(iso, 'dd/MM'); if (slice === 'day') return fns.format(date, 'dd/MM');
if (slice === 'month') return fns.format(iso, 'MM MMMM'); if (slice === 'week') return fns.format(date, 'dd/MM');
if (slice === 'year') return fns.format(iso, 'YYYY'); if (slice === 'month') return fns.format(date, 'MM MMMM');
if (slice === 'year') return fns.format(date, 'YYYY');
return iso; return iso;
} }
@@ -71,47 +72,27 @@ class DateService {
} }
getQueryDateRange(slice: Slice) { getGranularityData(slice: Slice, dateField: string) {
const group: Record<string, any> = {} const dateFromParts: Record<string, any> = {};
const sort: Record<string, any> = {} let granularity = '';
switch (slice) { switch (slice) {
case 'hour': case 'hour':
group.hour = { $hour: '$created_at' } dateFromParts.hour = { $hour: { date: dateField } }
granularity = 'hour';
case 'day': case 'day':
group.day = { $dayOfMonth: '$created_at' } dateFromParts.day = { $dayOfMonth: { date: dateField } }
case 'week': granularity = 'day';
group.week = { $isoWeek: '$created_at' }
case 'month': case 'month':
group.month = { $month: '$created_at' } dateFromParts.month = { $month: { date: dateField } }
granularity = 'month';
case 'year': case 'year':
group.year = { $year: '$created_at' } dateFromParts.year = { $year: { date: dateField } }
granularity = 'year';
} }
switch (slice) { return { dateFromParts, granularity }
case 'year':
sort['_id.year'] = 1;
break;
case 'month':
sort['_id.year'] = 1;
sort['_id.month'] = 1;
break;
case 'week':
case 'day':
sort['_id.year'] = 1;
sort['_id.month'] = 1;
sort['_id.day'] = 1;
break;
case 'hour':
sort['_id.year'] = 1;
sort['_id.month'] = 1;
sort['_id.day'] = 1;
sort['_id.hour'] = 1;
break;
}
return { group, sort }
} }
/** /**