Skip to content

Commit f5efce4

Browse files
committed
Shorten time aggregation labels to the relevant year/month/day granularity
1 parent 5e95f58 commit f5efce4

2 files changed

Lines changed: 117 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- Ignore stale drilldown column-selection indices that no longer map to dimensions after report dimension changes
2020
- Normalize GitHub datasource HTTP error payloads to always return an empty data array
2121
- Stop report loading spinner and show an explicit error when `/data` requests fail
22+
- Shorten time aggregation labels to the relevant year/month/day granularity
2223

2324
## 6.1.0 - 2026-02-18
2425
### Added

js/visualization.js

Lines changed: 116 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ var myMoment = moment;
2323
*/
2424
OCA.Analytics.Visualization = {
2525
defaultColorPalette: ["#1A366C", "#EA6A47", "#a3acb9", "#6AB187", "#39a7db", "#c85200", "#57606c", "#a3cce9", "#ffbc79", "#c8d0d9"],
26+
timeAggregationFormats: {
27+
day: 'YYYY-MM-DD',
28+
week: 'YYYY-MM-DD',
29+
month: 'YYYY-MM',
30+
year: 'YYYY',
31+
},
2632

2733
_hexToRgbCache: {},
2834

@@ -184,7 +190,17 @@ OCA.Analytics.Visualization = {
184190
},
185191
};
186192

187-
({data, columns} = this.convertDataToDataTableFormat(jsondata.data, tableOptions, jsondata.header));
193+
const timeAggregationDisplayConfig = this.getTimeAggregationTableDisplayConfig(
194+
jsondata.options?.filteroptions,
195+
jsondata.options?.chartoptions
196+
);
197+
198+
({data, columns} = this.convertDataToDataTableFormat(
199+
jsondata.data,
200+
tableOptions,
201+
jsondata.header,
202+
timeAggregationDisplayConfig
203+
));
188204
const calculatedResult = this.dataTableCalculatedColumns(data, columns, tableOptions);
189205
if (Array.isArray(calculatedResult)) {
190206
data = calculatedResult;
@@ -261,9 +277,10 @@ OCA.Analytics.Visualization = {
261277
* @param {Array} originalData - Raw matrix style data rows
262278
* @param {Object} tableOptions - DataTable related options
263279
* @param {Array} header - Original header row
280+
* @param {Object|null} timeAggregationDisplayConfig - Optional table-only formatting config for aggregated time labels
264281
* @returns {{data: Array, columns: Array}}
265282
*/
266-
convertDataToDataTableFormat: function (originalData, tableOptions, header) {
283+
convertDataToDataTableFormat: function (originalData, tableOptions, header, timeAggregationDisplayConfig = null) {
267284
let layoutConfig = tableOptions.layout !== undefined ? tableOptions.layout : false;
268285
let uniqueHeaders = new Set();
269286
let transformedData = {};
@@ -275,8 +292,14 @@ OCA.Analytics.Visualization = {
275292

276293
// create the columns. default alignment is left
277294
columns = header.map((header, index) => ({title: _.escape(header), className: ''}));
295+
if (timeAggregationDisplayConfig) {
296+
this.applyTimeAggregationDisplayRenderer(columns, timeAggregationDisplayConfig.dimension, timeAggregationDisplayConfig);
297+
}
278298
data = originalData.map(row =>
279299
row.map((value, index) => {
300+
if (timeAggregationDisplayConfig && index === timeAggregationDisplayConfig.dimension) {
301+
return value;
302+
}
280303
if (!isNaN(parseFloat(value)) && index !== 0 && tableOptions.formatLocales === undefined) {
281304
// Any number gets aligned to the right and formated with locales
282305
if (parseInt(value) > 1950 && parseInt(value) < 2050 && index < 2) {
@@ -299,34 +322,55 @@ OCA.Analytics.Visualization = {
299322
// Use titles from the headers array based on the reordered sequence (indices)
300323
columns = layoutConfig.rows.map((index, i) => ({
301324
title: _.escape(header[index]),
302-
className: i > 0 ? 'dt-right' : ''
325+
className: i > 0 && (!timeAggregationDisplayConfig || index !== timeAggregationDisplayConfig.dimension) ? 'dt-right' : ''
303326
}));
327+
if (timeAggregationDisplayConfig) {
328+
const displayIndex = layoutConfig.rows.indexOf(timeAggregationDisplayConfig.dimension);
329+
if (displayIndex !== -1) {
330+
this.applyTimeAggregationDisplayRenderer(columns, displayIndex, timeAggregationDisplayConfig);
331+
}
332+
}
304333

305334
const rowsLength = layoutConfig.rows.length; // Cache length to avoid repeated property access
306335

307336
// Reorder the data according to the new column sequence
308337
data = originalData.map(row =>
309338
layoutConfig.rows.map((index, i) =>
310-
i === rowsLength - 1 ? _.escape(parseFloat(row[index]).toLocaleString()) : _.escape(row[index])
339+
(timeAggregationDisplayConfig && index === timeAggregationDisplayConfig.dimension)
340+
? row[index]
341+
: (i === rowsLength - 1 ? _.escape(parseFloat(row[index]).toLocaleString()) : _.escape(row[index]))
311342
)
312343
);
313344
} else {
314345
// Case for pivot like rearrangement of the table
315346

316347
// 1. Extract unique headers and initialize transformedData
317348
originalData.forEach(row => {
318-
const columnHeader = row[layoutConfig.columns[0]];
349+
let columnHeader = row[layoutConfig.columns[0]];
350+
if (timeAggregationDisplayConfig && layoutConfig.columns[0] === timeAggregationDisplayConfig.dimension) {
351+
columnHeader = this.formatTimeAggregationDisplayValue(columnHeader, timeAggregationDisplayConfig);
352+
}
319353
uniqueHeaders.add(columnHeader);
320-
transformedData[row[layoutConfig.rows[0]]] = {};
354+
let rowHeader = row[layoutConfig.rows[0]];
355+
if (timeAggregationDisplayConfig && layoutConfig.rows[0] === timeAggregationDisplayConfig.dimension) {
356+
rowHeader = this.formatTimeAggregationDisplayValue(rowHeader, timeAggregationDisplayConfig);
357+
}
358+
transformedData[rowHeader] = {};
321359
});
322360

323361
// Sort the headers for consistent ordering
324362
uniqueHeaders = Array.from(uniqueHeaders).sort();
325363

326364
// 2. Transform the data
327365
originalData.forEach(row => {
328-
const rowHeader = row[layoutConfig.rows[0]];
329-
const columnHeader = row[layoutConfig.columns[0]];
366+
let rowHeader = row[layoutConfig.rows[0]];
367+
let columnHeader = row[layoutConfig.columns[0]];
368+
if (timeAggregationDisplayConfig && layoutConfig.rows[0] === timeAggregationDisplayConfig.dimension) {
369+
rowHeader = this.formatTimeAggregationDisplayValue(rowHeader, timeAggregationDisplayConfig);
370+
}
371+
if (timeAggregationDisplayConfig && layoutConfig.columns[0] === timeAggregationDisplayConfig.dimension) {
372+
columnHeader = this.formatTimeAggregationDisplayValue(columnHeader, timeAggregationDisplayConfig);
373+
}
330374
const dataValue = row[layoutConfig.measures[0]];
331375
transformedData[rowHeader][columnHeader] = dataValue;
332376
});
@@ -1229,6 +1273,70 @@ OCA.Analytics.Visualization = {
12291273
return idx;
12301274
},
12311275

1276+
getTimeAggregationFormat: function (grouping) {
1277+
return this.timeAggregationFormats[grouping] || null;
1278+
},
1279+
1280+
getTimeAggregationTableDisplayConfig: function (filteroptions, chartoptions) {
1281+
const grouping = filteroptions?.timeAggregation?.grouping;
1282+
if (!grouping || grouping === 'none') {
1283+
return null;
1284+
}
1285+
1286+
const format = this.getTimeAggregationFormat(grouping);
1287+
if (!format) {
1288+
return null;
1289+
}
1290+
1291+
const dimension = this.resolveDimensionIndex(
1292+
filteroptions.timeAggregation.dimension,
1293+
filteroptions?.drilldown
1294+
);
1295+
if (dimension < 0) {
1296+
return null;
1297+
}
1298+
1299+
const parser = chartoptions?.scales?.x?.time?.parser || null;
1300+
return {dimension, format, parser};
1301+
},
1302+
1303+
formatTimeAggregationDisplayValue: function (value, config) {
1304+
if (value === null || value === undefined || value === '') {
1305+
return '';
1306+
}
1307+
1308+
let date;
1309+
if (!isNaN(value) && value !== '') {
1310+
const rawValue = String(value);
1311+
const numericValue = parseInt(rawValue, 10);
1312+
date = rawValue.length > 10 ? new Date(numericValue) : new Date(numericValue * 1000);
1313+
} else if (config?.parser) {
1314+
date = myMoment(value, config.parser).toDate();
1315+
} else {
1316+
date = new Date(value);
1317+
}
1318+
1319+
if (isNaN(date?.valueOf?.())) {
1320+
return value;
1321+
}
1322+
1323+
return myMoment(date).format(config.format);
1324+
},
1325+
1326+
applyTimeAggregationDisplayRenderer: function (columns, columnIndex, config) {
1327+
if (!columns[columnIndex]) {
1328+
return;
1329+
}
1330+
1331+
columns[columnIndex].render = function (data, type) {
1332+
if (type !== 'display' && type !== 'filter') {
1333+
return data;
1334+
}
1335+
1336+
return _.escape(OCA.Analytics.Visualization.formatTimeAggregationDisplayValue(data, config));
1337+
};
1338+
},
1339+
12321340
/**
12331341
* Group time based dimensions by day/week/month/year.
12341342
*

0 commit comments

Comments
 (0)