-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathreportController.js
More file actions
144 lines (121 loc) · 5.05 KB
/
reportController.js
File metadata and controls
144 lines (121 loc) · 5.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { firestoreOld } from '../utils/db.js';
const firestore = firestoreOld;
import {
REQUIRED_PARAMS,
validateRequiredParams,
sendValidationError,
getLatestDate,
handleControllerError,
validateArrayParameter
} from '../utils/controllerHelpers.js';
/**
* Configuration for different report types
*/
const REPORT_CONFIGS = {
adoption: {
table: 'adoption',
dataField: 'adoption'
},
pageWeight: {
table: 'page_weight',
dataField: 'pageWeight' // TODO: change to page_weight once migrated to new Firestore DB
},
lighthouse: {
table: 'lighthouse',
dataField: 'lighthouse'
},
cwv: {
table: 'core_web_vitals',
dataField: 'vitals'
},
audits: {
table: 'audits',
dataField: 'audits'
}
};
/**
* Generic report data controller factory
* Creates controllers for adoption, pageWeight, lighthouse, and cwv data.
* Pass { crossGeo: true } to get a cross-geography snapshot (omits geo filter,
* includes geo in projection, returns a single month of data).
*/
const createReportController = (reportType, { crossGeo = false } = {}) => {
const config = REPORT_CONFIGS[reportType];
if (!config) {
throw new Error(`Unknown report type: ${reportType}`);
}
return async (req, res) => {
try {
const params = req.query;
/*
// Validate supported parameters
const supportedParams = ['technology', 'geo', 'rank', 'start', 'end'];
const providedParams = Object.keys(params);
const unsupportedParams = providedParams.filter(param => !supportedParams.includes(param));
if (unsupportedParams.length > 0) {
const error = new Error(`Unsupported parameters: ${unsupportedParams.join(', ')}.`);
error.statusCode = 400;
throw error;
}
*/
// Validate required parameters using shared utility
const errors = validateRequiredParams(params, []);
if (errors) {
sendValidationError(res, errors);
return;
}
// Default technology, geo, and rank to 'ALL' if missing or empty
const technologyParam = params.technology || 'ALL';
const geoParam = params.geo || 'ALL';
const rankParam = params.rank || 'ALL';
// Validate and process technology array
const techArray = validateArrayParameter(technologyParam, 'technology');
// Build Firestore query
let query = firestore.collection(config.table);
query = query.where('rank', '==', rankParam);
query = query.where('technology', 'in', techArray);
// Apply version filter with special handling for 'ALL' case
if (params.version && techArray.length === 1) {
//query = query.where('version', '==', params.version); // TODO: Uncomment when migrating to a new data schema
} else {
//query = query.where('version', '==', 'ALL');
}
if (crossGeo) {
// Cross-geo: single-month snapshot, all geographies included.
// Use 'end' param if provided, otherwise default to latest available date.
const snapshotDate = params.end || await getLatestDate(firestore, config.table);
query = query.where('date', '==', snapshotDate);
query = query.select('date', 'technology', 'geo', config.dataField);
} else {
// Normal time-series: filter by geo, apply date range, no geo in projection.
query = query.where('geo', '==', geoParam);
// Handle 'latest' date substitution
let startDate = params.start;
if (startDate === 'latest') {
startDate = await getLatestDate(firestore, config.table);
}
if (startDate) query = query.where('date', '>=', startDate);
if (params.end) query = query.where('date', '<=', params.end);
query = query.select('date', 'technology', config.dataField);
}
// Execute query
const snapshot = await query.get();
const data = [];
snapshot.forEach(doc => {
data.push(doc.data());
});
// Send response
res.statusCode = 200;
res.end(JSON.stringify(data));
} catch (error) {
handleControllerError(res, error, `fetching ${reportType} data`);
}
};
};
// Export individual controller functions
export const listAuditsData = createReportController('audits');
export const listAdoptionData = createReportController('adoption');
export const listCWVTechData = createReportController('cwv');
export const listLighthouseData = createReportController('lighthouse');
export const listPageWeightData = createReportController('pageWeight');
export const listGeoBreakdownData = createReportController('cwv', { crossGeo: true });