Skip to content

Commit d55585c

Browse files
authored
32884 analytics dashboard refactor state (dotCMS#33030)
### Proposed Changes This pull request refactors and standardizes how time ranges are handled throughout the Dot Analytics data access layer. The main improvements include centralizing time range constants, updating types and services to use these new constants, and enhancing granularity calculation for custom date ranges. It also updates UI components and tests to align with these changes. **Time Range Standardization and Refactoring** - Introduced `TIME_RANGE_OPTIONS` and `TIME_RANGE_CUBEJS_MAPPING` constants for consistent time range handling across the codebase, replacing hardcoded strings and scattered definitions. [[1]](diffhunk://#diff-0d3fae337a838afd84911fc8b3587e656c3c8ddc1e2b3f96bcd4113d9782aa89R1-R15) [[2]](diffhunk://#diff-9a6d064319b5b88cf3f0a012b5ca60b259eb4ee29e6c26ba78139a9a05a0f346R1) [[3]](diffhunk://#diff-adb26a280c70da63050345af049168a8480129fb4d4e8d72796fcf6e8590b519R10-R12) - Updated types (`TimeRange`, `TimeRangeCubeJS`, etc.) and removed legacy time range constants, ensuring all type definitions rely on the new centralized constants. **Service and Store Updates** - Refactored `DotAnalyticsService` methods to use `TIME_RANGE_OPTIONS.last7days` as the default and to map time ranges via a new private `#getTimeRange` method, ensuring proper Cube.js query formatting. [[1]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL11-R20) [[2]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL34-R42) [[3]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL54-R62) [[4]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL74-R75) [[5]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL83-R84) [[6]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL97-R108) [[7]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL122-R121) [[8]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL131-R130) [[9]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL145-R144) [[10]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bL155-R154) [[11]](diffhunk://#diff-c0a594cb6bf35693b60035c742263700865b55e180715509ac6b9377ddaeb95bR163-R172) - Updated the analytics dashboard store to use the new time range constants for initial state. [[1]](diffhunk://#diff-e3042fd9ba0c764b3ca7c9bcb6dfc598a9ffe7a1d69f7b6696b67b6bfd4e8e0aR25) [[2]](diffhunk://#diff-e3042fd9ba0c764b3ca7c9bcb6dfc598a9ffe7a1d69f7b6696b67b6bfd4e8e0aL46-R47) **Granularity Calculation Enhancements** - Improved `determineGranularityForTimeRange` utility to handle custom date ranges more accurately, using `date-fns` to determine if the range is within the same day, month, or spans multiple months, and returning the appropriate granularity (`hour`, `day`, or `month`). [[1]](diffhunk://#diff-3ca44ed41a4fb83cc93df2c0268f2f4a0d620b4a0794673bb62a5242aa26d841R1-R10) [[2]](diffhunk://#diff-3ca44ed41a4fb83cc93df2c0268f2f4a0d620b4a0794673bb62a5242aa26d841L28-R56) - Added comprehensive tests for custom date range granularity logic. **UI and Testing Updates** - Updated UI components to trigger handlers on dropdown and calendar changes, and to use the new date format and event bindings for custom date selection. [[1]](diffhunk://#diff-bc8d103344262ac3136ad2bbc9dd3042b062cbb008e7fdf9eb839bf46fe7e37bR17) [[2]](diffhunk://#diff-bc8d103344262ac3136ad2bbc9dd3042b062cbb008e7fdf9eb839bf46fe7e37bL27-R32) - Refactored component tests to use `queryParamMap` instead of `queryParams`, matching the updated Angular ActivatedRoute API and aligning with new time range handling. [[1]](diffhunk://#diff-e08307bf30a07105f6cca75ae1d9b863dc15b363e60cf35aed799ca8cfd7685dL18-R18) [[2]](diffhunk://#diff-e08307bf30a07105f6cca75ae1d9b863dc15b363e60cf35aed799ca8cfd7685dL227-R239) [[3]](diffhunk://#diff-e08307bf30a07105f6cca75ae1d9b863dc15b363e60cf35aed799ca8cfd7685dL253-R251) ### Checklist - [x] Tests - [x] Translations - [x] Security Implications Contemplated (add notes if applicable) ### Additional Info ** any additional useful context or info **
1 parent 6cc07f4 commit d55585c

21 files changed

+524
-1011
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ export * from './lib/services/dot-analytics.service';
77
// Utils
88
export * from './lib/utils/data/analytics-data.utils';
99

10+
// Constants
11+
export * from './lib/constants';
12+
1013
// Types (organized by category)
1114
export * from './lib/types';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const TIME_RANGE_OPTIONS = {
2+
today: 'today',
3+
yesterday: 'yesterday',
4+
last7days: 'last7days',
5+
last30days: 'last30days',
6+
custom: 'custom'
7+
} as const;
8+
9+
/** Reverse mapping for Internal → URL-friendly */
10+
export const TIME_RANGE_CUBEJS_MAPPING = {
11+
today: 'today',
12+
yesterday: 'yesterday',
13+
last7days: 'from 7 days ago to now',
14+
last30days: 'from 30 days ago to now'
15+
} as const;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './dot-analytics.constants';

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/
22

33
import { DotAnalyticsService } from './dot-analytics.service';
44

5-
import { DEFAULT_TIME_RANGE, type TimeRange } from '../../index';
5+
import { TIME_RANGE_OPTIONS } from '../constants';
66

77
const ANALYTICS_API_ENDPOINT = '/api/v1/analytics/content/_query/cube';
88
const TEST_SITE_ID = 'test-site-123';
9+
const DEFAULT_TIME_RANGE = TIME_RANGE_OPTIONS.last7days;
910

1011
describe('DotAnalyticsService', () => {
1112
let spectator: SpectatorHttp<DotAnalyticsService>;
@@ -44,13 +45,16 @@ describe('DotAnalyticsService', () => {
4445
});
4546

4647
it('should make POST request with custom timeRange', () => {
47-
const timeRange: TimeRange = 'from 30 days ago to now';
48-
spectator.service.totalPageViews(timeRange, TEST_SITE_ID).subscribe();
48+
spectator.service
49+
.totalPageViews(TIME_RANGE_OPTIONS.last30days, TEST_SITE_ID)
50+
.subscribe();
4951

5052
const req = spectator.expectOne(ANALYTICS_API_ENDPOINT, HttpMethod.POST);
5153
expect(req.request.url).toBe(ANALYTICS_API_ENDPOINT);
5254
expect(req.request.body).toBeDefined();
53-
expect(req.request.body.timeDimensions[0].dateRange).toBe(timeRange);
55+
expect(req.request.body.timeDimensions[0].dateRange).toBe(
56+
'from 30 days ago to now'
57+
);
5458
});
5559
});
5660

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

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import { map } from 'rxjs/operators';
88
import {
99
AnalyticsApiResponse,
1010
DEFAULT_COUNT_LIMIT,
11-
DEFAULT_TIME_RANGE,
1211
PageViewDeviceBrowsersEntity,
1312
PageViewTimeLineEntity,
13+
TimeRangeCubeJS,
1414
TimeRangeInput,
1515
TopPagePerformanceEntity,
1616
TopPerformaceTableEntity,
1717
TotalPageViewsEntity,
1818
UniqueVisitorsEntity
1919
} from '../../index';
20+
import { TIME_RANGE_CUBEJS_MAPPING, TIME_RANGE_OPTIONS } from '../constants';
2021
import { createCubeQuery } from '../utils/cube/cube-query-builder.util';
2122
import { determineGranularityForTimeRange } from '../utils/data/analytics-data.utils';
2223

@@ -31,14 +32,14 @@ export class DotAnalyticsService {
3132
* Total de pageviews en el período especificado
3233
*/
3334
totalPageViews(
34-
timeRange: TimeRangeInput = DEFAULT_TIME_RANGE,
35+
timeRange: TimeRangeInput = TIME_RANGE_OPTIONS.last7days,
3536
siteId: string | string[]
3637
): Observable<TotalPageViewsEntity> {
3738
const queryBuilder = createCubeQuery()
3839
.measures(['totalRequest'])
3940
.pageviews()
4041
.siteId(siteId)
41-
.timeRange('createdAt', timeRange);
42+
.timeRange('createdAt', this.#getTimeRange(timeRange));
4243

4344
const query = queryBuilder.build();
4445

@@ -51,14 +52,14 @@ export class DotAnalyticsService {
5152
* Visitantes únicos (sesiones únicas) en el período especificado
5253
*/
5354
uniqueVisitors(
54-
timeRange: TimeRangeInput = DEFAULT_TIME_RANGE,
55+
timeRange: TimeRangeInput = TIME_RANGE_OPTIONS.last7days,
5556
siteId: string | string[]
5657
): Observable<UniqueVisitorsEntity> {
5758
const queryBuilder = createCubeQuery()
5859
.measures(['totalUsers'])
5960
.pageviews()
6061
.siteId(siteId)
61-
.timeRange('createdAt', timeRange);
62+
.timeRange('createdAt', this.#getTimeRange(timeRange));
6263

6364
const query = queryBuilder.build();
6465

@@ -71,7 +72,7 @@ export class DotAnalyticsService {
7172
* Top page performance metric (total requests from the most visited page)
7273
*/
7374
topPagePerformance(
74-
timeRange: TimeRangeInput = DEFAULT_TIME_RANGE,
75+
timeRange: TimeRangeInput = TIME_RANGE_OPTIONS.last7days,
7576
siteId: string | string[]
7677
): Observable<TopPagePerformanceEntity> {
7778
const queryBuilder = createCubeQuery()
@@ -80,7 +81,7 @@ export class DotAnalyticsService {
8081
.pageviews()
8182
.siteId(siteId)
8283
.orderBy('totalRequest', 'desc')
83-
.timeRange('createdAt', timeRange)
84+
.timeRange('createdAt', this.#getTimeRange(timeRange))
8485
.limit(1);
8586

8687
const query = queryBuilder.build();
@@ -94,7 +95,7 @@ export class DotAnalyticsService {
9495
* Get page view timeline data
9596
*/
9697
pageViewTimeLine(
97-
timeRange: TimeRangeInput = DEFAULT_TIME_RANGE,
98+
timeRange: TimeRangeInput = TIME_RANGE_OPTIONS.last7days,
9899
siteId: string | string[]
99100
): Observable<PageViewTimeLineEntity[]> {
100101
// Determine granularity based on specific timeRange values
@@ -104,7 +105,7 @@ export class DotAnalyticsService {
104105
.measures(['totalRequest'])
105106
.pageviews()
106107
.siteId(siteId)
107-
.timeRange('createdAt', timeRange, granularity);
108+
.timeRange('createdAt', this.#getTimeRange(timeRange), granularity);
108109

109110
const query = queryBuilder.build();
110111

@@ -117,7 +118,7 @@ export class DotAnalyticsService {
117118
* Pageviews by device/browser for distribution chart
118119
*/
119120
pageViewDeviceBrowsers(
120-
timeRange: TimeRangeInput = DEFAULT_TIME_RANGE,
121+
timeRange: TimeRangeInput = TIME_RANGE_OPTIONS.last7days,
121122
siteId: string | string[]
122123
): Observable<PageViewDeviceBrowsersEntity[]> {
123124
const queryBuilder = createCubeQuery()
@@ -126,7 +127,7 @@ export class DotAnalyticsService {
126127
.pageviews()
127128
.siteId(siteId)
128129
.orderBy('totalRequest', 'desc')
129-
.timeRange('createdAt', timeRange)
130+
.timeRange('createdAt', this.#getTimeRange(timeRange))
130131
.limit(DEFAULT_COUNT_LIMIT);
131132

132133
const query = queryBuilder.build();
@@ -140,7 +141,7 @@ export class DotAnalyticsService {
140141
* Top pages table with title and pageviews
141142
*/
142143
getTopPagePerformanceTable(
143-
timeRange: TimeRangeInput = DEFAULT_TIME_RANGE,
144+
timeRange: TimeRangeInput = TIME_RANGE_OPTIONS.last7days,
144145
siteId: string | string[],
145146
limit = DEFAULT_COUNT_LIMIT
146147
): Observable<TopPerformaceTableEntity[]> {
@@ -150,7 +151,7 @@ export class DotAnalyticsService {
150151
.pageviews()
151152
.siteId(siteId)
152153
.orderBy('totalRequest', 'desc')
153-
.timeRange('createdAt', timeRange)
154+
.timeRange('createdAt', this.#getTimeRange(timeRange))
154155
.limit(limit);
155156

156157
const query = queryBuilder.build();
@@ -159,4 +160,14 @@ export class DotAnalyticsService {
159160
.post<AnalyticsApiResponse<TopPerformaceTableEntity>>(this.#BASE_URL, query)
160161
.pipe(map((response) => response.entity));
161162
}
163+
164+
#getTimeRange(timeRange: TimeRangeInput): TimeRangeCubeJS {
165+
if (Array.isArray(timeRange)) {
166+
return timeRange;
167+
}
168+
return (
169+
TIME_RANGE_CUBEJS_MAPPING[timeRange as keyof typeof TIME_RANGE_CUBEJS_MAPPING] ||
170+
TIME_RANGE_CUBEJS_MAPPING.last7days
171+
);
172+
}
162173
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
TotalPageViewsEntity,
2323
UniqueVisitorsEntity
2424
} from '../../index';
25+
import { TIME_RANGE_OPTIONS } from '../constants';
2526
import { DotAnalyticsService } from '../services/dot-analytics.service';
2627

2728
/**
@@ -43,7 +44,7 @@ export interface DotAnalyticsDashboardState {
4344
* Initial store state
4445
*/
4546
const initialState: DotAnalyticsDashboardState = {
46-
timeRange: 'from 7 days ago to now',
47+
timeRange: TIME_RANGE_OPTIONS.last7days,
4748
totalPageViews: { status: ComponentStatus.INIT, data: null, error: null },
4849
uniqueVisitors: { status: ComponentStatus.INIT, data: null, error: null },
4950
topPagePerformance: { status: ComponentStatus.INIT, data: null, error: null },

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

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,28 @@
44

55
import { ComponentStatus } from '@dotcms/dotcms-models';
66

7-
/**
8-
* Options for predefined time ranges in analytics
9-
* These correspond to common analytics reporting periods
10-
*/
11-
export const TimeRangeOptions = {
12-
TODAY: 'today',
13-
YESTERDAY: 'yesterday',
14-
LAST_7_DAYS: 'from 7 days ago to now',
15-
LAST_30_DAYS: 'from 30 days ago to now',
16-
CUSTOM_TIME_RANGE: 'CUSTOM_TIME_RANGE'
17-
} as const;
18-
19-
/**
20-
* Default time range for analytics queries (7 days)
21-
*/
22-
export const DEFAULT_TIME_RANGE: TimeRange = TimeRangeOptions.LAST_7_DAYS;
7+
import {
8+
TIME_RANGE_CUBEJS_MAPPING,
9+
TIME_RANGE_OPTIONS
10+
} from '../constants/dot-analytics.constants';
2311

2412
/**
2513
* Union type representing all possible time range values
2614
* Includes predefined ranges and custom time expressions
2715
*/
28-
export type TimeRange = (typeof TimeRangeOptions)[keyof typeof TimeRangeOptions];
16+
export type TimeRange = (typeof TIME_RANGE_OPTIONS)[keyof typeof TIME_RANGE_OPTIONS];
2917

3018
/** Date range for custom time period selection as ISO date strings */
3119
export type DateRange = [string, string];
3220

21+
/**
22+
* Union type representing all possible time range values for Cube.js
23+
* Includes predefined ranges and custom time expressions
24+
*/
25+
export type TimeRangeCubeJS =
26+
| (typeof TIME_RANGE_CUBEJS_MAPPING)[keyof typeof TIME_RANGE_CUBEJS_MAPPING]
27+
| DateRange;
28+
3329
/** Union type for time range inputs - supports both predefined ranges and custom date arrays */
3430
export type TimeRangeInput = TimeRange | DateRange;
3531

0 commit comments

Comments
 (0)