Skip to content

Commit 65df9d6

Browse files
authored
fix: Legacy filters without filterId cannot be deleted in data browser (#2946)
1 parent 1dd9ba5 commit 65df9d6

File tree

2 files changed

+150
-46
lines changed

2 files changed

+150
-46
lines changed

src/components/BrowserFilter/BrowserFilter.react.js

Lines changed: 125 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -74,31 +74,37 @@ export default class BrowserFilter extends React.Component {
7474
return false;
7575
}
7676

77-
// Fallback only when no filterId in URL: Check if current filter structure matches any saved filter
78-
// This is for legacy compatibility
79-
const preferences = ClassPreferences.getPreferences(
80-
this.context.applicationId,
81-
this.props.className
82-
);
77+
// Check for legacy filters (filters parameter without filterId)
78+
const filtersParam = urlParams.get('filters');
79+
if (filtersParam && this.props.filters.size > 0) {
80+
const preferences = ClassPreferences.getPreferences(
81+
this.context.applicationId,
82+
this.props.className
83+
);
8384

84-
if (!preferences.filters || this.props.filters.size === 0) {
85-
return false;
86-
}
85+
if (preferences.filters) {
86+
// Try to find a saved filter that matches the current filter content
87+
const currentFiltersString = JSON.stringify(this.props.filters.toJS());
8788

88-
const currentFiltersString = JSON.stringify(this.props.filters.toJS());
89+
const matchingFilter = preferences.filters.find(savedFilter => {
90+
try {
91+
const savedFiltersString = JSON.stringify(JSON.parse(savedFilter.filter));
92+
return savedFiltersString === currentFiltersString;
93+
} catch {
94+
return false;
95+
}
96+
});
8997

90-
return preferences.filters.some(savedFilter => {
91-
try {
92-
const savedFiltersString = JSON.stringify(JSON.parse(savedFilter.filter));
93-
return savedFiltersString === currentFiltersString;
94-
} catch {
95-
return false;
98+
return !!matchingFilter;
9699
}
97-
});
100+
}
101+
102+
return false;
98103
} getCurrentFilterInfo() {
99104
// Extract filterId from URL if present
100105
const urlParams = new URLSearchParams(window.location.search);
101106
const filterId = urlParams.get('filterId');
107+
const filtersParam = urlParams.get('filters');
102108

103109
if (filterId) {
104110
const preferences = ClassPreferences.getPreferences(
@@ -132,11 +138,57 @@ export default class BrowserFilter extends React.Component {
132138
}
133139
}
134140

141+
// Check for legacy filters (filters parameter without filterId)
142+
if (filtersParam && this.props.filters.size > 0) {
143+
const preferences = ClassPreferences.getPreferences(
144+
this.context.applicationId,
145+
this.props.className
146+
);
147+
148+
if (preferences.filters) {
149+
// Try to find a saved filter that matches the current filter content
150+
const currentFiltersString = JSON.stringify(this.props.filters.toJS());
151+
152+
const matchingFilter = preferences.filters.find(savedFilter => {
153+
try {
154+
const savedFiltersString = JSON.stringify(JSON.parse(savedFilter.filter));
155+
const matches = savedFiltersString === currentFiltersString;
156+
return matches;
157+
} catch {
158+
return false;
159+
}
160+
});
161+
162+
if (matchingFilter) {
163+
// Check if the filter has relative dates
164+
let hasRelativeDates = false;
165+
try {
166+
const filterData = JSON.parse(matchingFilter.filter);
167+
hasRelativeDates = filterData.some(filter =>
168+
filter.compareTo && filter.compareTo.__type === 'RelativeDate'
169+
);
170+
} catch (error) {
171+
console.warn('Failed to parse saved filter:', error);
172+
hasRelativeDates = false;
173+
}
174+
175+
return {
176+
id: matchingFilter.id || null, // Legacy filters might not have an id
177+
name: matchingFilter.name,
178+
isApplied: true,
179+
hasRelativeDates: hasRelativeDates,
180+
isLegacy: !matchingFilter.id // Mark as legacy if no id
181+
};
182+
}
183+
}
184+
}
185+
135186
return {
136187
id: null,
137188
name: '',
138189
isApplied: false,
139-
hasRelativeDates: false
190+
hasRelativeDates: false,
191+
isLegacy: false
140192
};
141193
}
142194

@@ -182,9 +234,22 @@ export default class BrowserFilter extends React.Component {
182234
);
183235

184236
if (preferences.filters && name) {
185-
return preferences.filters.some(filter =>
186-
filter.name === name && filter.id !== this.getCurrentFilterInfo().id
187-
);
237+
const currentFilterInfo = this.getCurrentFilterInfo();
238+
return preferences.filters.some(filter => {
239+
// For filters with the same name, check if it's not the current filter
240+
if (filter.name === name) {
241+
// If current filter has an ID, exclude it by ID
242+
if (currentFilterInfo.id && filter.id === currentFilterInfo.id) {
243+
return false;
244+
}
245+
// If current filter is legacy (no ID), exclude it by name match
246+
if (currentFilterInfo.isLegacy && !filter.id && filter.name === currentFilterInfo.name) {
247+
return false;
248+
}
249+
return true;
250+
}
251+
return false;
252+
});
188253
}
189254
return false;
190255
}
@@ -298,41 +363,50 @@ export default class BrowserFilter extends React.Component {
298363

299364
deleteCurrentFilter() {
300365
const currentFilterInfo = this.getCurrentFilterInfo();
301-
if (!currentFilterInfo.id) {
302-
this.setState({ confirmDelete: false });
303-
return;
304-
}
305366

306-
// Delete the filter from ClassPreferences
307-
const preferences = ClassPreferences.getPreferences(
308-
this.context.applicationId,
309-
this.props.className
310-
);
311-
312-
if (preferences.filters) {
313-
const updatedFilters = preferences.filters.filter(filter => filter.id !== currentFilterInfo.id);
314-
ClassPreferences.updatePreferences(
315-
this.context.applicationId,
316-
this.props.className,
317-
{ ...preferences, filters: updatedFilters }
318-
);
367+
// Use parent's onDeleteFilter method which handles everything including force update
368+
if (this.props.onDeleteFilter) {
369+
// For legacy filters, we need to pass the entire filter object for the parent to match
370+
if (currentFilterInfo.isLegacy) {
371+
const preferences = ClassPreferences.getPreferences(this.context.applicationId, this.props.className);
372+
if (preferences.filters) {
373+
const currentFiltersString = JSON.stringify(this.props.filters.toJS());
374+
const matchingFilter = preferences.filters.find(filter => {
375+
if (!filter.id && filter.name === currentFilterInfo.name) {
376+
try {
377+
const savedFiltersString = JSON.stringify(JSON.parse(filter.filter));
378+
return savedFiltersString === currentFiltersString;
379+
} catch {
380+
return false;
381+
}
382+
}
383+
return false;
384+
});
385+
386+
if (matchingFilter) {
387+
this.props.onDeleteFilter(matchingFilter);
388+
}
389+
}
390+
} else if (currentFilterInfo.id) {
391+
// For modern filters with ID, just pass the ID
392+
this.props.onDeleteFilter(currentFilterInfo.id);
393+
}
319394
}
320395

321-
// Remove filterId from URL
396+
// Remove filterId from URL if present
322397
const urlParams = new URLSearchParams(window.location.search);
323398
urlParams.delete('filterId');
399+
// For legacy filters, also remove the filters parameter
400+
if (currentFilterInfo.isLegacy) {
401+
urlParams.delete('filters');
402+
}
324403
const newUrl = `${window.location.pathname}${urlParams.toString() ? '?' + urlParams.toString() : ''}`;
325404
window.history.replaceState({}, '', newUrl);
326405

327406
// Clear current filters and close dialog
328407
this.props.onChange(new ImmutableMap());
329408
this.setState({ confirmDelete: false });
330409
this.toggle();
331-
332-
// Call onDeleteFilter prop if provided
333-
if (this.props.onDeleteFilter) {
334-
this.props.onDeleteFilter(currentFilterInfo.id);
335-
}
336410
}
337411

338412
copyCurrentFilter() {
@@ -458,7 +532,13 @@ export default class BrowserFilter extends React.Component {
458532

459533
// If we're in showMore mode, we're editing an existing filter
460534
const currentFilterInfo = this.getCurrentFilterInfo();
461-
const filterId = this.state.showMore ? currentFilterInfo.id : null;
535+
let filterId = this.state.showMore ? currentFilterInfo.id : null;
536+
537+
// For legacy filters (no ID), pass a special identifier so the save handler can convert them
538+
if (this.state.showMore && currentFilterInfo.isLegacy && !filterId) {
539+
// Pass the filter name as a special legacy identifier
540+
filterId = `legacy:${currentFilterInfo.name}`;
541+
}
462542

463543
const savedFilterId = this.props.onSaveFilter(formatted, this.state.name, this.state.relativeDates, filterId);
464544

src/dashboard/Data/Browser/Browser.react.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1259,7 +1259,31 @@ class Browser extends DashboardView {
12591259

12601260
let newFilterId = filterId;
12611261

1262-
if (filterId) {
1262+
if (filterId && filterId.startsWith('legacy:')) {
1263+
// Handle legacy filter update by converting to modern filter
1264+
const legacyFilterName = filterId.substring(7); // Remove 'legacy:' prefix
1265+
const existingLegacyFilterIndex = preferences.filters.findIndex(filter =>
1266+
!filter.id && filter.name === legacyFilterName
1267+
);
1268+
1269+
if (existingLegacyFilterIndex !== -1) {
1270+
// Convert legacy filter to modern filter by adding an ID
1271+
newFilterId = crypto.randomUUID();
1272+
preferences.filters[existingLegacyFilterIndex] = {
1273+
name,
1274+
id: newFilterId,
1275+
filter: _filters,
1276+
};
1277+
} else {
1278+
// Legacy filter not found, create new one
1279+
newFilterId = crypto.randomUUID();
1280+
preferences.filters.push({
1281+
name,
1282+
id: newFilterId,
1283+
filter: _filters,
1284+
});
1285+
}
1286+
} else if (filterId) {
12631287
// Update existing filter
12641288
const existingFilterIndex = preferences.filters.findIndex(filter => filter.id === filterId);
12651289
if (existingFilterIndex !== -1) {

0 commit comments

Comments
 (0)