Skip to content

AdHocFilters: Show reason for non-applicable filters #1181

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

Merged
merged 9 commits into from
Jul 23, 2025
Merged
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
1 change: 1 addition & 0 deletions packages/scenes/src/locales/en-US/grafana-scenes.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"adhoc-filter-pill": {
"edit-filter-with-key": "Edit filter with key {{keyLabel}}",
"managed-filter": "{{origin}} managed filter",
"non-applicable": "Filter is not applicable",
"remove-filter-with-key": "Remove filter with key {{keyLabel}}"
},
"adhoc-filters-combobox": {
Expand Down
2 changes: 2 additions & 0 deletions packages/scenes/src/querying/SceneQueryRunner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,8 @@ describe.each(['11.1.2', '11.1.1'])('SceneQueryRunner', (v) => {
scene.setState({ $variables: new SceneVariableSet({ variables: [filtersVar] }) });
deactivationHandlers.push(filtersVar.activate());

await new Promise((r) => setTimeout(r, 1));

filtersVar.setState({
filters: [
{ key: 'A', operator: '=', value: 'B' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ exports[`SceneQueryRunner when running query should build DataQueryRequest objec
"from": "now-6h",
"to": "now",
},
"requestId": "SQR185",
"requestId": "SQR186",
"scopes": undefined,
"startTime": 1689063488000,
"targets": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export function AdHocFilterPill({ filter, model, readOnly, focusOnWipInputRef }:
}
};

// filters that are in a clean, original state that are applicable and not readonly
const cleanFilter = !filter.restorable && !filter.readOnly && !filter.nonApplicable;

if (viewMode) {
const pillTextContent = `${keyLabel} ${filter.operator} ${valueLabel}`;
const pillText = (
Expand Down Expand Up @@ -172,7 +175,7 @@ export function AdHocFilterPill({ filter, model, readOnly, focusOnWipInputRef }:
</Tooltip>
)}

{filter.origin && !filter.restorable && !filter.readOnly && (
{filter.origin && cleanFilter && (
<Tooltip content={getOriginFilterTooltips(filter.origin).info} placement={'bottom'}>
<Icon name="info-circle" size="md" className={styles.infoPillIcon} />
</Tooltip>
Expand All @@ -197,6 +200,18 @@ export function AdHocFilterPill({ filter, model, readOnly, focusOnWipInputRef }:
tooltip={getOriginFilterTooltips(filter.origin).restore}
/>
)}

{filter.nonApplicable && (
<Tooltip
content={
filter.nonApplicableReason ??
t('grafana-scenes.components.adhoc-filter-pill.non-applicable', 'Filter is not applicable')
}
placement={'bottom'}
>
<Icon name="info-circle" size="md" className={styles.infoPillIcon} />
</Tooltip>
)}
</div>
);
}
Expand Down
86 changes: 61 additions & 25 deletions packages/scenes/src/variables/adhoc/AdHocFiltersVariable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

scopesVariable.update();

await new Promise((r) => setTimeout(r, 1));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to add this to all scopes updating tests since now it goes through _setStateWithFiltersApplicabilityCheck which awaits for the ds query


act(() => {
filtersVar._updateFilter(filtersVar.state.originFilters![0], {
value: 'newValue',
Expand Down Expand Up @@ -868,6 +870,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

scopesVariable.update();

await new Promise((r) => setTimeout(r, 1));

act(() => {
filtersVar._updateFilter(filtersVar.state.originFilters![0], {
value: 'newValue1',
Expand All @@ -881,7 +885,7 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
);
});

it('will properly escape injected filter hash delimiter', () => {
it('will properly escape injected filter hash delimiter', async () => {
const scopesVariable = newScopesVariableFromScopeFilters([
{
key: 'baseKey1',
Expand All @@ -900,6 +904,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

scopesVariable.update();

await new Promise((r) => setTimeout(r, 1));

act(() => {
filtersVar._updateFilter(filtersVar.state.originFilters![0], {
value: 'newValue1#',
Expand Down Expand Up @@ -1067,6 +1073,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

scopesVariable.update();

await new Promise((r) => setTimeout(r, 1));

const urlValues = {
'var-filters': [
'dbFilterKey|!=|newDbFilterValue#dashboard#restorable',
Expand Down Expand Up @@ -1282,7 +1290,7 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
expect(filtersVar.state.originFilters![0].restorable).toBe(false);
});

it('will save the original value and set filter as restorable if it has an origin', () => {
it('will save the original value and set filter as restorable if it has an origin', async () => {
const scopesVariable = newScopesVariableFromScopeFilters([
{
key: 'originKey1',
Expand All @@ -1302,6 +1310,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

scopesVariable.update();

await new Promise((r) => setTimeout(r, 1));

act(() => {
filtersVar._updateFilter(filtersVar.state.originFilters![0], {
value: 'newValue1',
Expand All @@ -1312,7 +1322,7 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
expect(filtersVar.state.originFilters![0].restorable).toEqual(true);
});

it('will save the original multi values if it has origin so it can be later restored', () => {
it('will save the original multi values if it has origin so it can be later restored', async () => {
const scopesVariable = newScopesVariableFromScopeFilters([
{
key: 'originKey1',
Expand All @@ -1332,6 +1342,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

scopesVariable.update();

await new Promise((r) => setTimeout(r, 1));

act(() => {
filtersVar._updateFilter(filtersVar.state.originFilters![0], {
value: 'newValue1',
Expand Down Expand Up @@ -1362,6 +1374,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

scopesVariable.update();

await new Promise((r) => setTimeout(r, 1));

act(() => {
// same value, so no change
filtersVar._updateFilter(filtersVar.state.originFilters![0], {
Expand All @@ -1375,7 +1389,7 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
expect(filtersVar.state.originFilters![0].restorable).toEqual(false);
});

it('sets filter as non restorable if we set the original value manually', () => {
it('sets filter as non restorable if we set the original value manually', async () => {
const scopesVariable = newScopesVariableFromScopeFilters([
{
key: 'originKey1',
Expand All @@ -1389,6 +1403,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

scopesVariable.update();

await new Promise((r) => setTimeout(r, 1));

act(() => {
filtersVar._updateFilter(filtersVar.state.originFilters![0], {
value: 'newValue1',
Expand All @@ -1412,7 +1428,7 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
expect(filtersVar.state.originFilters![0].restorable).toEqual(false);
});

it('restores original value if it exists', () => {
it('restores original value if it exists', async () => {
const scopesVariable = newScopesVariableFromScopeFilters([
{
key: 'originalKey1',
Expand All @@ -1426,6 +1442,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

scopesVariable.update();

await new Promise((r) => setTimeout(r, 1));

act(() => {
filtersVar._updateFilter(filtersVar.state.originFilters![0], {
value: 'newValue1',
Expand Down Expand Up @@ -1622,7 +1640,7 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
[[], [], []],
])(
'maintains correct filters and scope originated filters on activation',
(originFilters, scopeFilters, expected) => {
async (originFilters, scopeFilters, expected) => {
// we need to preserve either edited scope injected filters or directly filters pulled from scopes
const scopes: Scope[] = [];

Expand Down Expand Up @@ -1653,6 +1671,8 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
scopesVar.updateStateFromContext({ value: scopes, loading: false });
});

await new Promise((r) => setTimeout(r, 1));

filtersVar.state.originFilters?.forEach((filter, index) => {
expect(filter).toEqual(expected[index]);
});
Expand Down Expand Up @@ -2228,7 +2248,7 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
describe('non-applicable filters', () => {
it('should set non-applicable filters on activation', async () => {
//pod and static are non-applicable
const { filtersVar, getApplicableFiltersSpy } = setup(
const { filtersVar, getFiltersApplicabilitySpy } = setup(
{
filters: [
{
Expand Down Expand Up @@ -2263,10 +2283,11 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

await new Promise((r) => setTimeout(r, 1));

expect(getApplicableFiltersSpy).toHaveBeenCalled();
expect(filtersVar.state.filters[0].nonApplicable).toBe(undefined);
expect(filtersVar.state.filters[1].nonApplicable).toBe(undefined);
expect(getFiltersApplicabilitySpy).toHaveBeenCalled();
expect(filtersVar.state.filters[0].nonApplicable).toBe(false);
expect(filtersVar.state.filters[1].nonApplicable).toBe(false);
expect(filtersVar.state.filters[2].nonApplicable).toBe(true);
expect(filtersVar.state.filters[2].nonApplicableReason).toBe('reason');
expect(filtersVar.state.originFilters?.[0].nonApplicable).toBe(true);
});

Expand Down Expand Up @@ -2315,18 +2336,22 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
key: 'cluster',
value: '1',
operator: '=',
nonApplicable: false,
nonApplicableReason: undefined,
},
{
key: 'container',
value: '2',
operator: '=',
nonApplicable: false,
nonApplicableReason: undefined,
},
]);
});

it('should maintain default filter as non-applicable if we turn filter to match-all and then restore', async () => {
//pod and static are non-applicable
const { filtersVar, getApplicableFiltersSpy } = setup(
const { filtersVar, getFiltersApplicabilitySpy } = setup(
{
filters: [
{
Expand Down Expand Up @@ -2361,7 +2386,7 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {

await new Promise((r) => setTimeout(r, 1));

expect(getApplicableFiltersSpy).toHaveBeenCalled();
expect(getFiltersApplicabilitySpy).toHaveBeenCalled();
expect(filtersVar.state.filters[2].nonApplicable).toBe(true);
expect(filtersVar.state.originFilters?.[0].nonApplicable).toBe(true);

Expand Down Expand Up @@ -2406,9 +2431,9 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
{
filters: [
{ key: 'pod', operator: '=', value: 'val1' },
{ key: 'static', operator: '=', value: 'val2' },
{ key: 'container', operator: '=', value: 'val3' },
],
originFilters: [{ key: 'static', operator: '=', value: 'val2', origin: 'dashboard' }],
layout: 'combobox',
},
undefined,
Expand Down Expand Up @@ -2512,10 +2537,14 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
});

it('does not display hidden filters', async () => {
act(() => {
const { filtersVar } = setup();
const { filtersVar } = setup({
filters: [
{ key: 'key1', operator: '=', value: 'val1', valueLabels: ['valLabel1'] },
{ key: 'key2', operator: '=', value: 'val2', valueLabels: ['valLabel2'] },
],
});

// @todo this test does not work! The setState isn't updating the render.
act(() => {
filtersVar.setState({
filters: [
...filtersVar.state.filters,
Expand All @@ -2525,10 +2554,12 @@ describe.each(['11.1.2', '11.1.1'])('AdHocFiltersVariable', (v) => {
});
});

await new Promise((r) => setTimeout(r, 1));

expect(await screen.findByText('key1 = valLabel1')).toBeInTheDocument();
expect(await screen.findByText('key2 = valLabel2')).toBeInTheDocument();
expect(await screen.queryAllByText('hidden_key = hidden_val')).toEqual([]);
expect(await screen.queryAllByText('visible_key = visible_val')).toEqual([]);
expect(screen.queryAllByText('hidden_key = hidden_val')).toEqual([]);
expect(screen.queryAllByText('visible_key = visible_val')).toEqual([]);
});

it('focusing the input opens the key dropdown', async () => {
Expand Down Expand Up @@ -2782,11 +2813,11 @@ function setup(
overrides?: Partial<AdHocFiltersVariableState>,
filtersRequestEnricher?: FiltersRequestEnricher['enrichFiltersRequest'],
scopesVariable?: ScopesVariable,
useGetApplicableFilters?: boolean
useGetFiltersApplicability?: boolean
) {
const getTagKeysSpy = jest.fn();
const getTagValuesSpy = jest.fn();
const getApplicableFiltersSpy = jest.fn();
const getFiltersApplicabilitySpy = jest.fn();
setDataSourceSrv({
get() {
return {
Expand All @@ -2801,10 +2832,15 @@ function setup(
getRef() {
return { uid: 'my-ds-uid' };
},
...(useGetApplicableFilters && {
getApplicableFilters(options: any) {
getApplicableFiltersSpy(options);
return ['cluster', 'container'];
...(useGetFiltersApplicability && {
getFiltersApplicability(options: any) {
getFiltersApplicabilitySpy(options);
return [
{ key: 'cluster', applicable: true },
{ key: 'container', applicable: true },
{ key: 'pod', applicable: false, reason: 'reason' },
{ key: 'static', applicable: false, origin: 'dashboard' },
];
},
}),
};
Expand Down Expand Up @@ -2885,7 +2921,7 @@ function setup(
runRequest: runRequestMock.fn,
getTagKeysSpy,
getTagValuesSpy,
getApplicableFiltersSpy,
getFiltersApplicabilitySpy,
timeRange,
};
}
Expand Down
Loading