Skip to content

Add panorama filter proposal #451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions PANORAMA_FILTER_PROPOSAL.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<p align="center"><img src="https://raw.githubusercontent.com/rello/data/master/screenshots/architecture.png" alt="Main" width="610" title="Analytics"></p>
Expand Down
6 changes: 5 additions & 1 deletion js/filter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
},

Expand Down
46 changes: 45 additions & 1 deletion js/panorama.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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');

Expand Down
3 changes: 3 additions & 0 deletions templates/part.content_panorama.php
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,9 @@
<div id="optionBtn" class="analytics-options icon-analytics-more has-tooltip"
title="<?php p($l->t('Options')); ?>"></div>
<div id="fullscreenToggle" class="analytics-options icon-analytics-fullscreen"></div>
<div id="addFilterIcon" class="analytics-options icon-analytics-filter-add has-tooltip"
title="<?php p($l->t('Filter')); ?>"></div>
<div id="filterVisualisation" style="display: inline-block; float: right;"></div>
</div>

<div id="panoramaHeaderRow" class="panoramaHeaderRow">
Expand Down