Skip to content
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

AdHocVariable: Edit injected filters #1062

Merged
merged 35 commits into from
Mar 19, 2025

Conversation

mdvictor
Copy link
Collaborator

@mdvictor mdvictor commented Feb 26, 2025

Allows adhocs to show and edit base filters if they have an origin.

Holds base filters into url sync to allow for passing them over from one dashboard to another, alongside the source and original value of a injected filter.

📦 Published PR as canary version: 6.5.0--canary.1062.13950672491.0

✨ Test out this PR locally via:

npm install @grafana/[email protected]
npm install @grafana/[email protected]
# or 
yarn add @grafana/[email protected]
yarn add @grafana/[email protected]

@mdvictor mdvictor marked this pull request as draft February 26, 2025 13:29
@mdvictor mdvictor added minor Increment the minor version when merged release Create a release when this pr is merged labels Feb 26, 2025
Base automatically changed from mdvictor/show-base-filters-in-ui to main February 27, 2025 09:43
@mdvictor mdvictor requested a review from dprokop February 28, 2025 10:36
@mdvictor mdvictor marked this pull request as ready for review February 28, 2025 10:44
@mdvictor mdvictor marked this pull request as draft February 28, 2025 10:44
filtersVar._updateFilter(filtersVar.state.filters[0], { key: 'newKey', keyLabel: 'newKey' });
});

// injected filters stored in the following format: normal|adhoc|values\original|values\filterOrigin
Copy link
Member

Choose a reason for hiding this comment

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

I think the format is fine, question is if you sync them always? I think we should probably sync them only when they have changed, otherwise one will navigate to a dashboard in the same scope.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think you are right. I kept thinking about this and initially thought that both normal filters and baseFilters with an origin should be treated the same, thus having these baseFilters in the URL, same as your average filter, but I agree that there is no point in having both unmodified scope injected filter and the scope itself in there

Comment on lines 14 to 16
private getInjectedKey(): string {
return `var-injected-${this._variable.state.name}`;
}
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to do this? I think we could sync the injected alongside the regular filters (keys that are injected can't be really overwritten by the user) and figure out which filters were scope-injected based on keys matching and origin. WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Indeed, no need for another key other than for verbosity, we can identify injected filters based on origin. Removing the extra key

@mdvictor mdvictor marked this pull request as ready for review March 5, 2025 10:08
@mdvictor mdvictor requested a review from Sergej-Vlasov March 5, 2025 10:08
@mdvictor mdvictor changed the base branch from main to mdvictor/scopes-adhoc-inject March 11, 2025 13:17
@mdvictor mdvictor requested a review from bfmatei March 11, 2025 13:26
Copy link
Contributor

@Sergej-Vlasov Sergej-Vlasov left a comment

Choose a reason for hiding this comment

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

Looking great!
Some safeguards are needed for editing scope injected filters:

  1. prevent editing operator using shift+tab keyboard shortcut
  2. prevent editing operator using backspace (when deleting last value)
  3. deleting operator triggers focus shift to wip (add new) filter and scopes filter stays in broken edit mode
  4. clicking on scope inject filter to edit and then clicking on operator pill leaves scope injected filter in edit mode and focuses on wip filter
  5. backspace from wip filter does not start editing scope injected filters
Screen.Recording.2025-03-13.at.16.07.45.mov
Screen.Recording.2025-03-13.at.16.07.04.mov

Other than those, looks good ✅

Comment on lines 46 to 60
if (baseFilters?.length) {
value.push(
...baseFilters
?.filter(isFilterComplete)
.filter((filter) => !filter.hidden && filter.origin && filter.originalValue)
.map((filter) =>
toArray(filter)
.map(escapeInjectedFilterUrlDelimiters)
.join('|')
.concat(
`#${filter.originalValue?.map(escapeInjectedFilterUrlDelimiters).join('|') ?? ''}#${filter.origin}`
)
)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This part could use some comments

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added a comment explaining the url sync format for injected filters

Comment on lines 118 to 138
{filter.origin && !filter.originalValue && (
<IconButton
name="info-circle"
size="md"
className={styles.pillIcon}
tooltip={`This is a ${filter.origin} injected filter`}
/>
)}

{filter.origin && filter.originalValue && (
<IconButton
onClick={(e) => {
e.stopPropagation();
model.restoreOriginalFilter(filter);
}}
name="history"
size="md"
className={styles.pillIcon}
tooltip={`Restore filter to its original value`}
/>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: could be combined into:

{filter.origin && filter.originalValue ? (
      <IconButton
            onClick={(e) => {
              e.stopPropagation();
              model.restoreOriginalFilter(filter);
            }}
            name="history"
            size="md"
            className={styles.pillIcon}
            tooltip={`Restore filter to its original value`}
          />
       ) : (
       <IconButton
            name="info-circle"
            size="md"
            className={styles.pillIcon}
            tooltip={`This is a ${filter.origin} injected filter`}
          />
       )}

@@ -37,6 +38,7 @@ export interface AdHocFilterWithLabels<M extends Record<string, any> = {}> exten
// filter origin, it can be either scopes, dashboards or undefined,
// which means it won't appear in the UI
origin?: FilterOrigin;
originalValue?: string[];
Copy link
Contributor

Choose a reason for hiding this comment

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

I find it hard to understand what this is and why it should display the history icon when it is defined. My initial thought was that it is defined when not edited, not the other way around.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see what you mean, this could use better naming, maybe something like savedOriginalValue? The point of the property is indeed to hold the initial filter value before it is being edited

Comment on lines +222 to +223
...(state.baseFilters?.filter((filter) => filter.origin) ?? []),
...(state.filters ?? []),
Copy link
Contributor

Choose a reason for hiding this comment

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

This is being used a three different places? Would it make sense to extract this into a helper that describes what it does?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good call!

Comment on lines 275 to 278
// if the scope filters contain the key of an edited scope one, we replace
// with the edited one, otherwise if an edited filter is not found in
// the scope filters (this can happend when changing scopes alltogether)
// we drop the edited one and use the new filters from the new scopes
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't this similar to what's being done in _updateFilter? I am not familiar with the order of things, how come it is needed to be done in both places?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure I follow what you mean by being similar. This one sets the entire filters list based on scope changes.

Copy link
Contributor

@tskarhed tskarhed left a comment

Choose a reason for hiding this comment

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

LGTM! Nice with linking working! 👍

Base automatically changed from mdvictor/scopes-adhoc-inject to main March 19, 2025 15:25
@mdvictor mdvictor merged commit 5424dfe into main Mar 19, 2025
5 checks passed
@mdvictor mdvictor deleted the mdvictor/allow-injected-filters-edit branch March 19, 2025 15:49
@scenes-repo-bot-access-token
Copy link

🚀 PR was released in v6.5.0 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor Increment the minor version when merged release Create a release when this pr is merged released
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants