From 58cc9e56fc43bff420a23bed90edb0d0eeb67512 Mon Sep 17 00:00:00 2001 From: Zhihe Li Date: Tue, 11 Nov 2025 11:57:04 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9B=20Fix=20filters=20sometimes?= =?UTF-8?q?=20not=20being=20displayed=20(#3050)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One of the changes that was made was not retrieving all resources for the dropdown filters. This had an unintended consequence when displaying the selected filters as it's possible for the selected filter to not be in the list of allFilters. There's two main scenarios this happens (the prerequisite is having more filter options than we display which is 250): * When filtering down the dropdown filters which means it might not be in the allFilters options. This was fixed by keeping track of all the searched options to be able to display. * A user uses a query string or bookmarked a URL and goes to it directly. If the option isn't in the initial load, we won't have the actual option. In this case, I changed it to at least just display the ID instead of not showing up at all. Otherwise I'd have to load these selected options separately from the initial load which doesn't seem worth for this edge case. --- .../addon/components/filter-tags/index.js | 29 +++++++++++++------ .../components/filter-tags/index-test.js | 10 +++++++ .../scopes/scope/session-recordings/index.js | 26 ++++++++++++++--- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/addons/core/addon/components/filter-tags/index.js b/addons/core/addon/components/filter-tags/index.js index 2882b1e74..dec96a3ca 100644 --- a/addons/core/addon/components/filter-tags/index.js +++ b/addons/core/addon/components/filter-tags/index.js @@ -17,16 +17,27 @@ export default class FilterTagsIndexComponent extends Component { get filters() { const { allFilters, selectedFilters } = this.args.filters; - return Object.entries(allFilters).flatMap(([key, value]) => { - assert(`Tags must be an array for key ${key}`, Array.isArray(value)); - const paramsSet = new Set(selectedFilters[key]); - const filters = value.filter((item) => paramsSet.has(item.id)); + return Object.entries(allFilters).flatMap(([key, values]) => { + assert(`Tags must be an array for key ${key}`, Array.isArray(values)); + const uniqueSelectedFilters = [...new Set(selectedFilters[key])]; + if (!uniqueSelectedFilters.length) { + return []; + } - return filters.map((item) => ({ - id: item.id, - name: item.name, - type: key, - })); + const valueMap = new Map(values.map((value) => [value.id, value.name])); + + return uniqueSelectedFilters.filter(Boolean).map((item) => { + const filter = { + id: item, + type: key, + }; + + if (valueMap.has(item)) { + filter.name = valueMap.get(item); + } + + return filter; + }); }); } diff --git a/addons/core/tests/integration/components/filter-tags/index-test.js b/addons/core/tests/integration/components/filter-tags/index-test.js index 93cc8bfff..95349d7f4 100644 --- a/addons/core/tests/integration/components/filter-tags/index-test.js +++ b/addons/core/tests/integration/components/filter-tags/index-test.js @@ -22,4 +22,14 @@ module('Integration | Component | filter-tags/index', function (hooks) { assert.dom('.hds-tag__text').hasText('Project 1'); }); + + test('it renders id if allFilter does not have corresponding value', async function (assert) { + this.set('filter', { + allFilters: { scopes: [{ id: '2', name: 'Project 2' }] }, + selectedFilters: { scopes: ['1'] }, + }); + await render(hbs``); + + assert.dom('.hds-tag__text').hasText('1'); + }); }); diff --git a/ui/admin/app/controllers/scopes/scope/session-recordings/index.js b/ui/admin/app/controllers/scopes/scope/session-recordings/index.js index 658458fb5..cde82dc9a 100644 --- a/ui/admin/app/controllers/scopes/scope/session-recordings/index.js +++ b/ui/admin/app/controllers/scopes/scope/session-recordings/index.js @@ -12,7 +12,25 @@ import { restartableTask } from 'ember-concurrency'; class FilterOptions { @tracked search; - @tracked options = []; + @tracked _options = []; + #allOptions = new Map(); + + get options() { + return this._options; + } + + set options(newOptions) { + this._options = newOptions; + newOptions.forEach((option) => { + this.#allOptions.set(option.id, option); + }); + } + + // Keep track of all filter options that are loaded so they can be + // displayed in the selected filters regardless of search input + get allOptions() { + return Array.from(this.#allOptions.values()); + } } export default class ScopesScopeSessionRecordingsIndexController extends Controller { @@ -60,9 +78,9 @@ export default class ScopesScopeSessionRecordingsIndexController extends Control return { allFilters: { time: this.timeOptions, - users: this.userFilters.options, - scopes: this.scopeFilters.options, - targets: this.targetFilters.options, + users: this.userFilters.allOptions, + scopes: this.scopeFilters.allOptions, + targets: this.targetFilters.allOptions, }, selectedFilters: { time: [this.time],