Skip to content

fix(dashboard): adds dependent filter select first value fixes #34137

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
dataTestChartName,
} from 'cypress/support/directories';

import { waitForChartLoad } from 'cypress/utils';
import {
addParentFilterWithValue,
applyNativeFilterValueWithIndex,
Expand Down Expand Up @@ -160,6 +161,74 @@ describe('Native filters', () => {
);
});

it('Dependent filter selects first item based on parent filter selection', () => {
prepareDashboardFilters([
{ name: 'region', column: 'region', datasetId: 2 },
{ name: 'country_name', column: 'country_name', datasetId: 2 },
]);

enterNativeFilterEditModal();

selectFilter(0);
cy.get(nativeFilters.filterConfigurationSections.displayedSection).within(
() => {
cy.contains('Select first filter value by default')
.should('be.visible')
.click();
},
);
cy.get(nativeFilters.filterConfigurationSections.displayedSection).within(
() => {
cy.contains('Can select multiple values ')
.should('be.visible')
.click();
},
);

selectFilter(1);
cy.get(nativeFilters.filterConfigurationSections.displayedSection).within(
() => {
cy.contains('Values are dependent on other filters')
.should('be.visible')
.click();
},
);
cy.get(nativeFilters.filterConfigurationSections.displayedSection).within(
() => {
cy.contains('Can select multiple values ')
.should('be.visible')
.click();
},
);
addParentFilterWithValue(0, testItems.topTenChart.filterColumnRegion);
cy.get(nativeFilters.filterConfigurationSections.displayedSection).within(
() => {
cy.contains('Select first filter value by default')
.should('be.visible')
.click();
},
);

// cannot use saveNativeFilterSettings because there is a bug which
// sometimes does not allow charts to load when enabling the 'Select first filter value by default'
// to be saved when using dependent filters so,
// you reload the window.
cy.get(nativeFilters.modal.footer)
.contains('Save')
.should('be.visible')
.click({ force: true });

cy.get(nativeFilters.modal.container).should('not.exist');
cy.reload();

applyNativeFilterValueWithIndex(0, 'North America');

// Check that dependent filter auto-selects the first item
cy.get(nativeFilters.filterFromDashboardView.filterContent)
.eq(1)
.should('contain.text', 'Bermuda');
});

it('User can create filter depend on 2 other filters', () => {
prepareDashboardFilters([
{ name: 'region', column: 'region', datasetId: 2 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,37 @@ const FilterValue: FC<FilterControlProps> = ({
dashboardId,
});
const filterOwnState = filter.dataMask?.ownState || {};
// if (Object.keys(dataMaskSelected))
if (filter?.cascadeParentIds?.length) {
// Prevent unnecessary backend requests by validating parent filter selections first

let selectedParentFilterValueCounts = 0;

filter?.cascadeParentIds?.forEach(pId => {
if (
dataMaskSelected?.[pId]?.extraFormData?.filters ||
dataMaskSelected?.[pId]?.extraFormData?.time_range
) {
selectedParentFilterValueCounts +=
dataMaskSelected?.[pId]?.extraFormData?.filters?.length ?? 1;
}
});
Comment on lines +162 to +172

This comment was marked as resolved.


// check if all parent filters with defaults have a value selected

let depsCount = dependencies.filters?.length ?? 0;

if (dependencies?.time_range) {
depsCount += 1;
}

if (selectedParentFilterValueCounts !== depsCount) {
// child filter should not request backend until it
// has all the required information from parent filters
return;
}
}

// TODO: We should try to improve our useEffect hooks to depend more on
// granular information instead of big objects that require deep comparison.
const customizer = (
Expand Down Expand Up @@ -226,6 +257,7 @@ const FilterValue: FC<FilterControlProps> = ({
hasDataSource,
isRefreshing,
shouldRefresh,
dataMaskSelected,
]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
const [col] = groupby;
const [initialColtypeMap] = useState(coltypeMap);
const [search, setSearch] = useState('');
const isChangedByUser = useRef(false);
const prevDataRef = useRef(data);
const [dataMask, dispatchDataMask] = useImmerReducer(reducer, {
extraFormData: {},
filterState,
Expand Down Expand Up @@ -271,6 +273,8 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
} else {
updateDataMask(values);
}

isChangedByUser.current = true;
},
[updateDataMask, formData.nativeFilterId, clearAllTrigger],
);
Expand Down Expand Up @@ -368,6 +372,57 @@ export default function PluginFilterSelect(props: PluginFilterSelectProps) {
inverseSelection,
]);

useEffect(() => {
const prev = prevDataRef.current;
const curr = data;

const stringifySafe = (val: unknown) =>
typeof val === 'bigint' ? val.toString() : val;
Comment on lines +379 to +380

This comment was marked as resolved.


const hasDataChanged =
prev?.length !== curr?.length ||
JSON.stringify(prev?.map(row => stringifySafe(row[col]))) !==
JSON.stringify(curr?.map(row => stringifySafe(row[col])));
Comment on lines +379 to +385

This comment was marked as resolved.


// If data actually changed (e.g., due to parent filter), reset flag
if (hasDataChanged) {
isChangedByUser.current = false;
prevDataRef.current = data;
}
}, [data, col]);

useEffect(() => {
if (
isChangedByUser.current &&
filterState.value &&
data.some(row => row[col] === filterState.value[0])
)
return;
Comment on lines +394 to +400

This comment was marked as resolved.


const firstItem: SelectValue = data[0]
? (groupby.map(col => data[0][col]) as string[])
: null;

if (
defaultToFirstItem &&
Object.keys(formData?.extraFormData || {}).length &&
filterState.value !== undefined &&
firstItem !== null &&
filterState.value !== firstItem
) {
if (firstItem?.[0] !== undefined) {
updateDataMask(firstItem);
}
}
}, [
defaultToFirstItem,
updateDataMask,
formData,
data,
JSON.stringify(filterState.value),
isChangedByUser.current,
]);

useEffect(() => {
setDataMask(dataMask);
}, [JSON.stringify(dataMask)]);
Expand Down
Loading