diff --git a/dashboard/components/dashboard/ActionableChart.vue b/dashboard/components/dashboard/ActionableChart.vue index b7c8100..a6bb901 100644 --- a/dashboard/components/dashboard/ActionableChart.vue +++ b/dashboard/components/dashboard/ActionableChart.vue @@ -139,7 +139,6 @@ const { snapshotDuration } = useSnapshot(); const selectLabels: { label: string, value: Slice }[] = [ { label: 'Hour', value: 'hour' }, { label: 'Day', value: 'day' }, - // { label: 'Week', value: 'week' }, { label: 'Month', value: 'month' }, ]; @@ -159,7 +158,11 @@ const allDatesFull = ref([]); function transformResponse(input: { _id: string, count: number }[]) { 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()); return { data, labels } } @@ -223,9 +226,6 @@ function onDataReady() { const maxChartY = Math.max(...visitsData.data.value.data, ...sessionsData.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[1].data = sessionsData.data.value.data; chartData.value.datasets[2].data = eventsData.data.value.data.map(e => { diff --git a/dashboard/components/dashboard/SessionsLineChart.vue b/dashboard/components/dashboard/SessionsLineChart.vue index b4bed31..043f0e9 100644 --- a/dashboard/components/dashboard/SessionsLineChart.vue +++ b/dashboard/components/dashboard/SessionsLineChart.vue @@ -10,7 +10,7 @@ const { safeSnapshotDates } = useSnapshot() function transformResponse(input: { _id: string, count: number }[]) { 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 } } diff --git a/dashboard/components/dashboard/TopCards.vue b/dashboard/components/dashboard/TopCards.vue index 5414d2b..62984d4 100644 --- a/dashboard/components/dashboard/TopCards.vue +++ b/dashboard/components/dashboard/TopCards.vue @@ -18,7 +18,7 @@ function transformResponse(input: { _id: string, count: number }[]) { 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)]; diff --git a/dashboard/components/dashboard/VisitsLineChart.vue b/dashboard/components/dashboard/VisitsLineChart.vue index 3adbff1..320cf60 100644 --- a/dashboard/components/dashboard/VisitsLineChart.vue +++ b/dashboard/components/dashboard/VisitsLineChart.vue @@ -10,7 +10,7 @@ const { safeSnapshotDates } = useSnapshot() function transformResponse(input: { _id: string, count: number }[]) { 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 } } diff --git a/dashboard/pages/index.vue b/dashboard/pages/index.vue index b877ff5..b1ceb87 100644 --- a/dashboard/pages/index.vue +++ b/dashboard/pages/index.vue @@ -44,16 +44,16 @@ const selfhosted = useSelfhosted(); - +
- + diff --git a/dashboard/server/api/timeline/events.ts b/dashboard/server/api/timeline/events.ts index 87dfe53..524dd37 100644 --- a/dashboard/server/api/timeline/events.ts +++ b/dashboard/server/api/timeline/events.ts @@ -13,17 +13,12 @@ export default defineEventHandler(async event => { const cacheExp = 60; return await Redis.useCacheV2(cacheKey, cacheExp, async () => { - const timelineData = await executeTimelineAggregation({ projectId: project_id, model: EventModel, from, to, slice, }); - - const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, slice, from, to); - - return timelineFilledMerged; - + return timelineData; }); diff --git a/dashboard/server/api/timeline/sessions.ts b/dashboard/server/api/timeline/sessions.ts index cc49943..cb06aaf 100644 --- a/dashboard/server/api/timeline/sessions.ts +++ b/dashboard/server/api/timeline/sessions.ts @@ -18,9 +18,7 @@ export default defineEventHandler(async event => { model: SessionModel, from, to, slice, }); - const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, slice, from, to); - return timelineFilledMerged; - + return timelineData; }); diff --git a/dashboard/server/api/timeline/visits.ts b/dashboard/server/api/timeline/visits.ts index d52e861..a03ce35 100644 --- a/dashboard/server/api/timeline/visits.ts +++ b/dashboard/server/api/timeline/visits.ts @@ -12,18 +12,14 @@ export default defineEventHandler(async event => { const cacheKey = `timeline:visits:${pid}:${slice}:${from}:${to}`; const cacheExp = 60; - return await Redis.useCacheV2(cacheKey, cacheExp, async () => { - + return await Redis.useCacheV2(cacheKey, cacheExp, async () => { const timelineData = await executeTimelineAggregation({ projectId: project_id, model: VisitModel, - from, to, slice, + from, to, slice }); - - const timelineFilledMerged = fillAndMergeTimelineAggregationV2(timelineData, slice, from, to); - return timelineFilledMerged; - - }); + return timelineData; + }); diff --git a/dashboard/server/services/TimelineService.ts b/dashboard/server/services/TimelineService.ts index bea4261..c1e093a 100644 --- a/dashboard/server/services/TimelineService.ts +++ b/dashboard/server/services/TimelineService.ts @@ -10,6 +10,7 @@ export type TimelineAggregationOptions = { from: string | number, to: string | number, slice: Slice, + dateOffset?: number, debug?: boolean } @@ -27,9 +28,9 @@ export async function executeAdvancedTimelineAggregation(options: Advanc options.customProjection = options.customProjection || {}; 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); @@ -40,31 +41,60 @@ export async function executeAdvancedTimelineAggregation(options: Advanc { $match: { 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 } }, + { + $addFields: { + tmpDate: { + $dateSubtract: { + startDate: "$created_at", + unit: "minute", + amount: options.dateOffset || -60 + } + } + } + }, + { + $addFields: { isoDate: { $dateFromParts: dateFromParts } } + }, { $group: { - _id: { ...group, ...options.customIdGroup }, + _id: { isoDate: "$isoDate", ...options.customIdGroup }, count: { $sum: 1 }, - firstDate: { $first: '$created_at' }, ...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: { - _id: "$firstDate", - count: "$count", + _id: '$_id.isoDate', + count: '$count', ...options.customProjection } } ] as any; if (options.debug === true) { - console.log('---------- SORT ----------') - console.log(JSON.stringify(sort, null, 2)); console.log('---------- AGGREAGATION ----------') console.log(JSON.stringify(aggregation, null, 2)); } diff --git a/shared/services/DateService.ts b/shared/services/DateService.ts index 412322b..1b68547 100644 --- a/shared/services/DateService.ts +++ b/shared/services/DateService.ts @@ -32,12 +32,13 @@ class DateService { public slicesData = slicesData; - getChartLabelFromISO(iso: string, locale: string, slice: Slice) { - if (slice === 'hour') return fns.format(iso, 'HH:mm'); - if (slice === 'day') return fns.format(iso, 'dd/MM'); - if (slice === 'week') return fns.format(iso, 'dd/MM'); - if (slice === 'month') return fns.format(iso, 'MM MMMM'); - if (slice === 'year') return fns.format(iso, 'YYYY'); + getChartLabelFromISO(iso: string, offset: number, slice: Slice) { + const date = new Date(new Date(iso).getTime() - offset * 1000 * 60); + if (slice === 'hour') return fns.format(date, 'HH:mm'); + if (slice === 'day') return fns.format(date, 'dd/MM'); + if (slice === 'week') return fns.format(date, 'dd/MM'); + if (slice === 'month') return fns.format(date, 'MM MMMM'); + if (slice === 'year') return fns.format(date, 'YYYY'); return iso; } @@ -71,47 +72,27 @@ class DateService { } - getQueryDateRange(slice: Slice) { + getGranularityData(slice: Slice, dateField: string) { - const group: Record = {} - const sort: Record = {} + const dateFromParts: Record = {}; + let granularity = ''; switch (slice) { case 'hour': - group.hour = { $hour: '$created_at' } + dateFromParts.hour = { $hour: { date: dateField } } + granularity = 'hour'; case 'day': - group.day = { $dayOfMonth: '$created_at' } - case 'week': - group.week = { $isoWeek: '$created_at' } + dateFromParts.day = { $dayOfMonth: { date: dateField } } + granularity = 'day'; case 'month': - group.month = { $month: '$created_at' } + dateFromParts.month = { $month: { date: dateField } } + granularity = 'month'; case 'year': - group.year = { $year: '$created_at' } + dateFromParts.year = { $year: { date: dateField } } + granularity = 'year'; } - switch (slice) { - 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 } + return { dateFromParts, granularity } } /**