Skip to content
Open
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
2 changes: 1 addition & 1 deletion frontend/src/context/Theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ const theme = createTheme({
},
neutrals: {
main: '#646566',
light: '#A9AEB1',
light: '#939294',
dark: '#2F2F30',
white: '#FFFFFF',
black: '#000000'
Expand Down
34 changes: 9 additions & 25 deletions frontend/src/pages/Organizations/Organizations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,22 +196,6 @@ export const Organizations: React.FC = () => {
disableExport: true,
hasActiveFilters: hasActiveFilters
} as any,
panel: {
onClose: () => {
// Clear any incomplete filters when the panel closes and fetch unfiltered data.
// Prevents mismatch between filter model and applied filters.
const hasIncompleteFilters = filterModel.items.some(
(item) => item.value === undefined
);

if (hasIncompleteFilters) {
setFilterModel({ items: [] });
setFilters({ items: [] });
setHasActiveFilters(false);
setPaginationModel((prev) => ({ ...prev, page: 0 }));
}
}
},
columnsManagement: {
disableResetButton: true,
getTogglableColumns: (columns) => {
Expand All @@ -233,35 +217,35 @@ export const Organizations: React.FC = () => {
filterModel={filterModel}
onFilterModelChange={(model) => {
const cleanedModel = cleanFilterModelItems(model, filterModel);
setFilterModel(cleanedModel);
const emptyModel = isFilterModelEmpty(cleanedModel);
if (emptyModel) {
setHasActiveFilters(false);
return;
}
setFilterModel(cleanedModel);
setHasActiveFilters(!emptyModel);

const shouldUpdate = shouldTriggerFilterUpdate(
cleanedModel.items,
filterModel.items
);

setHasActiveFilters(cleanedModel.items.length !== 0);
if (!shouldUpdate) {
return;
}

if (filterTimerRef.current) {
clearTimeout(filterTimerRef.current);
filterTimerRef.current = null;
}

if (emptyModel) {
setFilters({ items: [] });
return;
}

filterTimerRef.current = window.setTimeout(() => {
setIsLoading(true);

setFilters({ items: cleanedModel.items });

setPaginationModel((prev) => ({ ...prev, page: 0 }));
filterTimerRef.current = null;
}, 1000);
}, 500);
}}
sortingMode="server"
sortModel={sortModel}
Expand Down
47 changes: 28 additions & 19 deletions frontend/src/pages/Vulnerabilities/Vulnerabilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ import { getSeverityColor } from 'utils/getSeverityColor';
import { truncateString } from 'utils/stringUtils';
import {
cleanFilterModelItems,
extractInitialFilters,
formatSeverity,
isFilterModelEmpty,
normalizeVulnFilters,
extractInitialFilters,
shouldTriggerFilterUpdate
} from '@/utils/tableUtils';

Expand Down Expand Up @@ -721,6 +722,8 @@ export const Vulnerabilities: React.FC<VulnerabilitiesProps> = ({
filterModel={filterModel}
onFilterModelChange={(model) => {
const cleanedModel = cleanFilterModelItems(model, filterModel);
const emptyModel = isFilterModelEmpty(cleanedModel);
setHasActiveFilters(!emptyModel);
setFilterModel(cleanedModel);

const shouldUpdate = shouldTriggerFilterUpdate(
Expand All @@ -734,12 +737,18 @@ export const Vulnerabilities: React.FC<VulnerabilitiesProps> = ({

if (filterTimerRef.current) {
clearTimeout(filterTimerRef.current);
filterTimerRef.current = null;
}

if (emptyModel) {
setFilters([]);
return;
}

filterTimerRef.current = window.setTimeout(() => {
setIsLoading(true);
setFilters(cleanedModel.items);
setHasActiveFilters(cleanedModel.items.length > 0);
// setHasActiveFilters(cleanedModel.items.length > 0);
setPaginationModel((prev) => ({ ...prev, page: 0 }));
filterTimerRef.current = null;
}, 1000);
Expand Down Expand Up @@ -777,23 +786,23 @@ export const Vulnerabilities: React.FC<VulnerabilitiesProps> = ({
basePopper: {
placement: 'bottom-start'
},
panel: {
onClose: () => {
// Clear any incomplete filters when the panel closes and fetch unfiltered data.
// Prevents mismatch between filter model and applied filters.

const hasIncompleteFilters = filterModel.items.some(
(item) => item.value === undefined
);

if (hasIncompleteFilters) {
setFilterModel({ items: [] });
setFilters([]);
setHasActiveFilters(false);
setPaginationModel((prev) => ({ ...prev, page: 0 }));
}
}
},
// panel: {
// onClose: () => {
// // Clear any incomplete filters when the panel closes and fetch unfiltered data.
// // Prevents mismatch between filter model and applied filters.

// const hasIncompleteFilters = filterModel.items.some(
// (item) => item.value === undefined
// );

// if (hasIncompleteFilters) {
// setFilterModel({ items: [] });
// setFilters([]);
// setHasActiveFilters(false);
// setPaginationModel((prev) => ({ ...prev, page: 0 }));
// }
// }
// },
columnsManagement: {
disableResetButton: true,
getTogglableColumns: (columns) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`Header component > matches snapshot 1`] = `
<DocumentFragment>
<header
class="MuiPaper-root MuiPaper-elevation MuiPaper-elevation0 MuiAppBar-root MuiAppBar-colorPrimary MuiAppBar-positionSticky css-11d1y2g-MuiPaper-root-MuiAppBar-root"
class="MuiPaper-root MuiPaper-elevation MuiPaper-elevation0 MuiAppBar-root MuiAppBar-colorPrimary MuiAppBar-positionSticky css-56idr-MuiPaper-root-MuiAppBar-root"
style="--Paper-shadow: none;"
>
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ exports[`Layout component > matches snapshot 1`] = `
HEADER
</div>
<header
class="MuiPaper-root MuiPaper-elevation MuiPaper-elevation0 MuiAppBar-root MuiAppBar-colorPrimary MuiAppBar-positionSticky css-1duqi3m-MuiPaper-root-MuiAppBar-root"
class="MuiPaper-root MuiPaper-elevation MuiPaper-elevation0 MuiAppBar-root MuiAppBar-colorPrimary MuiAppBar-positionSticky css-v4s29r-MuiPaper-root-MuiAppBar-root"
style="--Paper-shadow: none;"
>
<div
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/tests/utils/getActiveItems.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest';
import { getActiveItems } from '@/utils/tableUtils';

describe('getActiveItems', () => {
it('returns only active filter items with defined, non-null, non-empty values', () => {
const items = [
{ field: 'name', operator: 'contains', value: 'Test' },
{ field: 'severity', operator: 'equals', value: '' },
{ field: 'status', operator: 'equals', value: null },
{ field: 'type', operator: 'equals', value: undefined },
{ field: 'date', operator: 'after', value: '2024-01-01' }
];

const activeItems = getActiveItems(items);
expect(activeItems).toEqual([
{ field: 'name', operator: 'contains', value: 'Test' },
{ field: 'date', operator: 'after', value: '2024-01-01' }
]);
});
});
4 changes: 2 additions & 2 deletions frontend/src/tests/utils/shouldTriggerFilterUpdate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('shouldTriggerFilterUpdate', () => {
expect(result).toBe(false);
});

it('returns false when transitioning from filters to no filters (intermediate state)', () => {
it('returns true when transitioning from previous filter value to deleted value', () => {
const prevModel = [
{
field: 'severity',
Expand All @@ -51,7 +51,7 @@ describe('shouldTriggerFilterUpdate', () => {
];

const result = shouldTriggerFilterUpdate(newModel, prevModel);
expect(result).toBe(false);
expect(result).toBe(true);
});

it('returns true when transitioning from no filters to filters', () => {
Expand Down
37 changes: 16 additions & 21 deletions frontend/src/utils/tableUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,37 +161,32 @@ export const normalizeVulnFilters = (
return result;
};

export const shouldTriggerFilterUpdate = (
newItems: GridFilterItem[],
previousItems: GridFilterItem[]
): boolean => {
const newComplete = newItems.filter(
export const getActiveItems = (items: GridFilterItem[]) =>
items.filter(
(item) =>
item.value !== undefined && item.value !== null && item.value !== ''
);

const prevComplete = previousItems.filter(
(item) =>
item.value !== undefined && item.value !== null && item.value !== ''
);
export const shouldTriggerFilterUpdate = (
newItems: GridFilterItem[],
previousItems: GridFilterItem[]
): boolean => {
const newActive = getActiveItems(newItems);
const prevActive = getActiveItems(previousItems);

// Check intermediate state
if (
prevComplete.length > 0 &&
newComplete.length === 0 &&
newItems.length > 0
) {
return false;
// If both are empty, only trigger if previous had values and new does not (i.e., a value was cleared)
if (prevActive.length > 0 && newActive.length === 0) {
return true;
}

// Different lengths = different filters
if (newComplete.length !== prevComplete.length) {
// If different lengths, trigger
if (newActive.length !== prevActive.length) {
return true;
}

// Compare each filter item
return newComplete.some((newItem, index) => {
const prevItem = prevComplete[index];
// Compare each active filter item
return newActive.some((newItem, index) => {
const prevItem = prevActive[index];
return (
newItem.field !== prevItem.field ||
newItem.operator !== prevItem.operator ||
Expand Down
Loading