From bf01d9b6201d97868441cda9e8555865811d7030 Mon Sep 17 00:00:00 2001 From: Rello Date: Sun, 18 May 2025 12:18:37 +0200 Subject: [PATCH] Add panorama filter UI and logic --- PANORAMA_FILTER_PROPOSAL.md | 53 +++++++++++++++++++++++++++++ README.md | 1 + js/filter.js | 6 +++- js/panorama.js | 46 ++++++++++++++++++++++++- templates/part.content_panorama.php | 3 ++ 5 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 PANORAMA_FILTER_PROPOSAL.md diff --git a/PANORAMA_FILTER_PROPOSAL.md b/PANORAMA_FILTER_PROPOSAL.md new file mode 100644 index 00000000..f2f01469 --- /dev/null +++ b/PANORAMA_FILTER_PROPOSAL.md @@ -0,0 +1,53 @@ +# Panorama Filter Proposal + +## Goal +Enable filtering across all reports within a panorama. The filter modal should +list all unique dimensions gathered from the underlying reports. When a filter is +applied, every report that contains the selected dimension receives the filter +and updates accordingly. + +## Overview of Changes +1. **UI Trigger** + - Add a filter button to the panorama header next to existing edit controls. + - The button opens the existing filter modal from `OCA.Analytics.Filter`. + +2. **Dimension Collection** + - While loading a panorama, collect each report's dimension list and build a + unique dimension set. + - Provide this list to the filter dialog so users can pick any dimension + available across the dashboard. + +3. **Filter Storage** + - Store selected filters on the panorama object, e.g. under + `OCA.Analytics.Panorama.currentPanorama.filters`. + - When saving a panorama, persist these filters similarly to existing report + filter options. + +4. **Filter Application** + - When retrieving report data (`getReportData`) include matching panorama + filters in the request if the report supports the dimension. + - After each update, call `OCA.Analytics.Filter.refreshFilterVisualisation` to + show active filters in the header. + +5. **Compatibility** + - Reuse the existing modal and processing logic from `filter.js`. Only the + dimension source and propagation mechanism differ from single reports. + +## Implementation Steps +1. **Collect dimensions** + - Extend `OCA.Analytics.Panorama.getReportData` to push each report's + `dimensions` property into a global set. +2. **Open dialog** + - Implement `OCA.Analytics.Panorama.openFilterDialog` that populates the + dimension dropdown with the collected set and invokes + `OCA.Analytics.Filter.openFilterDialog` for value selection. +3. **Apply filter** + - Update `OCA.Analytics.Panorama.getReportData` so that panorama filters are + appended to the request for reports containing the chosen dimension. +4. **Visual feedback** + - Show applied filters in the panorama header using the same DOM elements as + single reports. + +This approach mirrors the filtering workflow of individual reports while +allowing a panorama-wide view. All reports continue to honour their own filters; +the panorama filter simply adds additional criteria where applicable. diff --git a/README.md b/README.md index 7699872e..c3e31a14 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Visualize and share anything, from financial analysis to IoT logs. - Integration: Files, Activity, Notifications, [Flow](https://github.com/Rello/analytics/wiki/Flow-integration), Dashboard, Search, Smart picker, Translation - AI Assistant: Context Chat integration +- [Panorama filter proposal](PANORAMA_FILTER_PROPOSAL.md) ## Architecture

Main

diff --git a/js/filter.js b/js/filter.js index 69e3d794..ca8aea62 100644 --- a/js/filter.js +++ b/js/filter.js @@ -101,7 +101,11 @@ OCA.Analytics.Filter = { OCA.Analytics.currentReportData.options.filteroptions = filterOptions; OCA.Analytics.unsavedFilters = true; - OCA.Analytics.Backend.getData(); + if (OCA.Analytics.isPanorama) { + OCA.Analytics.Panorama.updateFiltersFromDialog(filterOptions); + } else { + OCA.Analytics.Backend.getData(); + } OCA.Analytics.Filter.close(); }, diff --git a/js/panorama.js b/js/panorama.js index ba313118..314834cc 100644 --- a/js/panorama.js +++ b/js/panorama.js @@ -164,6 +164,7 @@ OCA.Analytics.Panorama = { document.getElementById("infoBoxReport").addEventListener('click', OCA.Analytics.Panorama.newPanorama); document.getElementById('fullscreenToggle').addEventListener('click', OCA.Analytics.Visualization.toggleFullscreen); + document.getElementById('addFilterIcon').addEventListener('click', OCA.Analytics.Panorama.openFilterDialog); document.getElementById('layoutModalClose').addEventListener('click', function () { document.getElementById('layoutModal').style.display = 'none'; @@ -207,6 +208,43 @@ OCA.Analytics.Panorama = { OCA.Analytics.Panorama.getPanorama('next'); OCA.Analytics.Panorama.updateNavButtons(); }, + + openFilterDialog: function () { + let dimensions = {}; + if (OCA.Analytics.Panorama.currentPanorama.pages) { + OCA.Analytics.Panorama.currentPanorama.pages.forEach(page => { + page.reports.forEach(rep => { + if (rep.type === OCA.Analytics.Panorama.TYPE_REPORT) { + let report = OCA.Analytics.reports.find(r => parseInt(r.id) === parseInt(rep.value)); + if (report && report.dimensions) { + Object.entries(report.dimensions).forEach(([key, val]) => { + dimensions[key] = val; + }); + } + } + }); + }); + } + + OCA.Analytics.currentReportData = OCA.Analytics.currentReportData || {}; + OCA.Analytics.currentReportData.dimensions = dimensions; + OCA.Analytics.currentReportData.options = OCA.Analytics.currentReportData.options || {}; + OCA.Analytics.currentReportData.options.filteroptions = OCA.Analytics.Panorama.currentPanorama.filters || {}; + OCA.Analytics.Filter.openFilterDialog(); + }, + + updateFiltersFromDialog: function (filterOptions) { + OCA.Analytics.Panorama.currentPanorama.filters = filterOptions; + OCA.Analytics.currentReportData.options.filteroptions = filterOptions; + OCA.Analytics.Filter.refreshFilterVisualisation(); + + document.querySelectorAll('.flex-item').forEach(item => { + const reportId = item.getAttribute('data-chart'); + if (reportId) { + OCA.Analytics.Backend.getReportData(reportId, item.id); + } + }); + }, // get the panorama and loop all widgets getPanorama: function (targetPage) { @@ -1157,8 +1195,14 @@ OCA.Analytics.Backend = { getReportData: function (datasetId, itemId) { const url = OC.generateUrl('apps/analytics/data/pa/' + datasetId, true); + let params = {}; + if (OCA.Analytics.Panorama.currentPanorama.filters) { + params.filteroptions = JSON.stringify(OCA.Analytics.Panorama.currentPanorama.filters); + } + let requestUrl = `${url}?${new URLSearchParams(params)}`; + let xhr = new XMLHttpRequest(); - xhr.open('GET', url); + xhr.open('GET', requestUrl); xhr.setRequestHeader('requesttoken', OC.requestToken); xhr.setRequestHeader('OCS-APIREQUEST', 'true'); diff --git a/templates/part.content_panorama.php b/templates/part.content_panorama.php index fb112235..eabd79d8 100644 --- a/templates/part.content_panorama.php +++ b/templates/part.content_panorama.php @@ -368,6 +368,9 @@
+
+