Skip to content

Commit e764479

Browse files
authored
32884 analytics dashboard fill dates (dotCMS#33034)
### Proposed Changes This pull request refactors the handling of time range options for analytics queries to improve consistency, maintainability, and type safety. The main changes include centralizing time range constants, updating service and store logic to use these new constants, and adjusting related types and tests accordingly. **Time Range Constants Refactoring:** * Introduced a new `TIME_RANGE_OPTIONS` constant for internal time range keys and a `TIME_RANGE_CUBEJS_MAPPING` for Cube.js-compatible values in `dot-analytics.constants.ts`. These are now exported via a new `constants` barrel file. [[1]](diffhunk://#diff-0d3fae337a838afd84911fc8b3587e656c3c8ddc1e2b3f96bcd4113d9782aa89R1-R15) [[2]](diffhunk://#diff-9a6d064319b5b88cf3f0a012b5ca60b259eb4ee29e6c26ba78139a9a05a0f346R1) [[3]](diffhunk://#diff-adb26a280c70da63050345af049168a8480129fb4d4e8d72796fcf6e8590b519R10-R12) * Removed the old `TimeRangeOptions` and `DEFAULT_TIME_RANGE` from `common.types.ts`, updating the `TimeRange` type to use the new constants, and added a new `TimeRangeCubeJS` type for Cube.js compatibility. **Service and Store Updates:** * Updated all methods in `dot-analytics.service.ts` to use `TIME_RANGE_OPTIONS.last7days` as the default time range and to convert internal time range keys to Cube.js values using a new private `#getTimeRange` method. [[1]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL34-R45) [[2]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL54-R65) [[3]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL74-R78) [[4]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL83-R87) [[5]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL97-R101) [[6]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL107-R124) [[7]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL129-R133) [[8]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL143-R147) [[9]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL153-R157) [[10]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bR166-R175) * Updated the initial state in `dot-analytics-dashboard.store.ts` to use `TIME_RANGE_OPTIONS.last7days`. [[1]](diffhunk://#diff-e3042fd9ba0c764b3ca7c9bcb6dfc598a9ffe7a1d69f7b6696b67b6bfd4e8e0aR25) [[2]](diffhunk://#diff-e3042fd9ba0c764b3ca7c9bcb6dfc598a9ffe7a1d69f7b6696b67b6bfd4e8e0aL46-R47) **Testing and Utility Adjustments:** * Updated all related tests and utility usages to reference the new constants, ensuring that both internal and Cube.js time range values are used appropriately in assertions and query building. [[1]](diffhunk://#diff-99331c74e1a5318c227da2286205a09c838813f9e2e732e45c6734cdde4b1793L5-R9) [[2]](diffhunk://#diff-99331c74e1a5318c227da2286205a09c838813f9e2e732e45c6734cdde4b1793L47-R57) [[3]](diffhunk://#diff-a11851d401e09fa4b1ba675b0febfb10a15425a8b0e7375b55b6dc0f479d25adL3-R3) [[4]](diffhunk://#diff-a11851d401e09fa4b1ba675b0febfb10a15425a8b0e7375b55b6dc0f479d25adL157-R175) [[5]](diffhunk://#diff-a11851d401e09fa4b1ba675b0febfb10a15425a8b0e7375b55b6dc0f479d25adL205-R207) [[6]](diffhunk://#diff-a11851d401e09fa4b1ba675b0febfb10a15425a8b0e7375b55b6dc0f479d25adL225-R227) [[7]](diffhunk://#diff-a11851d401e09fa4b1ba675b0febfb10a15425a8b0e7375b55b6dc0f479d25adL236-R238) [[8]](diffhunk://#diff-a11851d401e09fa4b1ba675b0febfb10a15425a8b0e7375b55b6dc0f479d25adL256-R258) [[9]](diffhunk://#diff-a11851d401e09fa4b1ba675b0febfb10a15425a8b0e7375b55b6dc0f479d25adL268-R270) [[10]](diffhunk://#diff-a11851d401e09fa4b1ba675b0febfb10a15425a8b0e7375b55b6dc0f479d25adL290-R292) ### Checklist - [x] Tests - [x] Translations - [x] Security Implications Contemplated (add notes if applicable)
1 parent f071314 commit e764479

File tree

4 files changed

+196
-5
lines changed

4 files changed

+196
-5
lines changed

core-web/libs/portlets/dot-analytics/data-access/src/lib/services/dot-analytics.service.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import {
1919
} from '../../index';
2020
import { TIME_RANGE_CUBEJS_MAPPING, TIME_RANGE_OPTIONS } from '../constants';
2121
import { createCubeQuery } from '../utils/cube/cube-query-builder.util';
22-
import { determineGranularityForTimeRange } from '../utils/data/analytics-data.utils';
22+
import {
23+
determineGranularityForTimeRange,
24+
fillMissingDates
25+
} from '../utils/data/analytics-data.utils';
2326

2427
@Injectable({
2528
providedIn: 'root'
@@ -111,7 +114,7 @@ export class DotAnalyticsService {
111114

112115
return this.#http
113116
.post<AnalyticsApiResponse<PageViewTimeLineEntity>>(this.#BASE_URL, query)
114-
.pipe(map((response) => response.entity));
117+
.pipe(map((response) => fillMissingDates(response.entity, timeRange, granularity)));
115118
}
116119

117120
/**

core-web/libs/portlets/dot-analytics/data-access/src/lib/utils/data/analytics-data.utils.spec.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { startOfDay, endOfDay, addHours } from 'date-fns';
1+
import { startOfDay, endOfDay, addHours, format } from 'date-fns';
22

33
import {
44
determineGranularityForTimeRange,
55
extractPageTitle,
66
extractPageViews,
77
extractSessions,
88
extractTopPageValue,
9+
getDateRange,
910
transformDeviceBrowsersData,
1011
transformPageViewTimeLineData,
1112
transformTopPagesTableData
@@ -740,4 +741,95 @@ describe('Analytics Data Utils', () => {
740741
});
741742
});
742743
});
744+
745+
describe('getDateRange', () => {
746+
beforeEach(() => {
747+
jest.useFakeTimers();
748+
jest.setSystemTime(new Date('2024-01-15T04:00:00.000'));
749+
});
750+
751+
afterEach(() => {
752+
jest.useRealTimers();
753+
});
754+
755+
describe('success cases', () => {
756+
it('should return today range correctly', () => {
757+
const result = getDateRange('today');
758+
const [startDate, endDate] = result;
759+
760+
expect(format(startDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-15 00:00:00');
761+
expect(format(endDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-15 23:59:59');
762+
});
763+
764+
it('should return yesterday range correctly', () => {
765+
const result = getDateRange('yesterday');
766+
const [startDate, endDate] = result;
767+
768+
expect(format(startDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-14 00:00:00');
769+
expect(format(endDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-14 23:59:59');
770+
});
771+
772+
it('should return last 7 days range correctly', () => {
773+
const result = getDateRange('last7days');
774+
const [startDate, endDate] = result;
775+
776+
expect(format(startDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-09 00:00:00');
777+
expect(format(endDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-15 23:59:59');
778+
});
779+
780+
it('should return last 30 days range correctly', () => {
781+
const result = getDateRange('last30days');
782+
const [startDate, endDate] = result;
783+
784+
expect(format(startDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2023-12-17 00:00:00');
785+
expect(format(endDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-15 23:59:59');
786+
});
787+
788+
it('should handle custom date range array correctly', () => {
789+
const result = getDateRange(['2024-01-01', '2024-01-31']);
790+
const [startDate, endDate] = result;
791+
792+
expect(format(startDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-01 00:00:00');
793+
expect(format(endDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-31 23:59:59');
794+
});
795+
796+
it('should handle single day custom range correctly', () => {
797+
const result = getDateRange(['2024-01-15', '2024-01-15']);
798+
const [startDate, endDate] = result;
799+
800+
expect(format(startDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-15 00:00:00');
801+
expect(format(endDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-01-15 23:59:59');
802+
});
803+
804+
it('should handle leap year dates correctly', () => {
805+
jest.setSystemTime(new Date('2024-02-15T12:00:00.000Z'));
806+
807+
const result = getDateRange('yesterday');
808+
const [startDate, endDate] = result;
809+
810+
expect(format(startDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-02-14 00:00:00');
811+
expect(format(endDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2024-02-14 23:59:59');
812+
});
813+
814+
it('should handle month boundary dates correctly', () => {
815+
jest.setSystemTime(new Date('2024-01-01T12:00:00.000Z'));
816+
817+
const result = getDateRange('yesterday');
818+
const [startDate, endDate] = result;
819+
820+
expect(format(startDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2023-12-31 00:00:00');
821+
expect(format(endDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2023-12-31 23:59:59');
822+
});
823+
824+
it('should handle year boundary dates correctly', () => {
825+
jest.setSystemTime(new Date('2024-01-01T12:00:00.000Z'));
826+
827+
const result = getDateRange('yesterday');
828+
const [startDate, endDate] = result;
829+
830+
expect(format(startDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2023-12-31 00:00:00');
831+
expect(format(endDate, 'yyyy-MM-dd HH:mm:ss')).toEqual('2023-12-31 23:59:59');
832+
});
833+
});
834+
});
743835
});

core-web/libs/portlets/dot-analytics/data-access/src/lib/utils/data/analytics-data.utils.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import { format, isSameDay, isSameMonth, parse } from 'date-fns';
1+
import {
2+
addDays,
3+
addHours,
4+
endOfDay,
5+
format,
6+
isSameDay,
7+
isSameMonth,
8+
parse,
9+
startOfDay,
10+
subDays
11+
} from 'date-fns';
212

313
import { TIME_RANGE_OPTIONS } from '../../constants';
414
import {
@@ -279,3 +289,86 @@ export const transformDeviceBrowsersData = (
279289
]
280290
};
281291
};
292+
293+
/**
294+
* Fills missing dates in the data array based on the granularity
295+
* @param data - The data array to fill missing dates
296+
* @param granularity - The granularity of the data
297+
* @returns The data array with missing dates filled
298+
*/
299+
export const fillMissingDates = (
300+
data: PageViewTimeLineEntity[],
301+
timeRange: TimeRangeInput,
302+
granularity: Granularity
303+
): PageViewTimeLineEntity[] => {
304+
if (!data || !Array.isArray(data)) {
305+
return [];
306+
}
307+
308+
const [startDate, endDate] = getDateRange(timeRange);
309+
310+
const dataMap = new Map();
311+
data.forEach((item) => {
312+
const dateKey = new Date(item['request.createdAt']).toISOString();
313+
dataMap.set(dateKey, item);
314+
});
315+
316+
const filledData = [];
317+
let currentDate = startDate;
318+
while (currentDate <= endDate) {
319+
const currentDateKey = currentDate.toISOString();
320+
321+
if (dataMap.has(currentDateKey)) {
322+
filledData.push(dataMap.get(currentDateKey));
323+
} else {
324+
filledData.push({
325+
'request.createdAt': currentDateKey,
326+
'request.totalRequest': '0'
327+
});
328+
}
329+
currentDate = granularity === 'hour' ? addHours(currentDate, 1) : addDays(currentDate, 1);
330+
}
331+
332+
return filledData;
333+
};
334+
335+
/**
336+
* Get the date range for the given time range
337+
* @param timeRange - The time range to get the date range for
338+
* @returns The date range
339+
*/
340+
export const getDateRange = (timeRange: TimeRangeInput): [Date, Date] => {
341+
const today = new Date();
342+
343+
if (Array.isArray(timeRange)) {
344+
const startDate = startOfDay(parse(timeRange[0], 'yyyy-MM-dd', today));
345+
const endDate = endOfDay(parse(timeRange[1], 'yyyy-MM-dd', today));
346+
347+
return [startDate, endDate];
348+
}
349+
350+
switch (timeRange) {
351+
case TIME_RANGE_OPTIONS.today:
352+
return [startOfDay(today), endOfDay(today)];
353+
case TIME_RANGE_OPTIONS.yesterday: {
354+
const yesterday = subDays(today, 1);
355+
356+
return [startOfDay(yesterday), endOfDay(yesterday)];
357+
}
358+
359+
case TIME_RANGE_OPTIONS.last7days: {
360+
const sevenDaysAgo = subDays(today, 6);
361+
362+
return [startOfDay(sevenDaysAgo), endOfDay(today)];
363+
}
364+
365+
case TIME_RANGE_OPTIONS.last30days: {
366+
const thirtyDaysAgo = subDays(today, 29);
367+
368+
return [startOfDay(thirtyDaysAgo), endOfDay(today)];
369+
}
370+
371+
default:
372+
return [startOfDay(today), endOfDay(today)];
373+
}
374+
};

core-web/libs/portlets/dot-analytics/portlet/src/lib/dot-analytics-dashboard/components/dot-analytics-dashboard-chart/dot-analytics-dashboard-chart.component.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,13 @@ export class DotAnalyticsDashboardChartComponent {
151151
x: {
152152
ticks: {
153153
maxTicksLimit: isMobile ? 6 : 10,
154-
autoSkip: isMobile,
154+
autoSkip: true,
155155
maxRotation: 45,
156156
minRotation: 0
157157
}
158+
},
159+
y: {
160+
beginAtZero: true
158161
}
159162
}
160163
: undefined

0 commit comments

Comments
 (0)