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
GroupByVariable: Sync label to URL #705
Conversation
public getValueOptions(args: VariableGetOptionsArgs): Observable<VariableValueOption[]> { | ||
const urlOptions = this.state.urlOptions ?? []; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
confused, thought group by variables are lazy so we don't call this function unless when the dropdown opens. So why do we need URL options here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also validateAndUpdate
which calls getValueOptions
.
Sometimes changeValueTo
is called without texts
and this.state.options
is used to determine the value of texts
. Check this out: https://github.com/grafana/scenes/blob/main/packages/scenes/src/variables/variants/MultiValueVariable.ts#L257
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but how does they relate to url sync?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused by this as well, we never pass available options via URL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I should have given a more in-depth explanation for this.
So, the URL values are technically custom values until the options are loaded. After the options are loaded:
- some of these values are normal values (they can be found in the received options)
- some of these values are traditional custom values (the label is the same as the value and they are not found in the received options)
- and some of these values are a special case of custom values (the label is different from the value and they are not found in the received options).
urlOptions
was needed because of the last type of values as there are situations where we can lose the label of a custom value. For example, when calling changeValueTo
method without a texts
parameter, the text
is extracted from the available options. In order to overcome this, we can either require the texts
parameter (which is a huge breaking change) or make the custom value/label pairs available to it.
In the case of GroupByVariable
this can be easily solved because changeValueTo
is called only from the renderer but for other types of MultiValueVariable
this gets tricky to solve.
A neat side-effect of the urlOptions
was that the 3rd type of custom values could also live as an independent option. For example, we receive a custom value with a label via the URL: value is MyValue
and label is MyLabel
. This custom value does not exist in the options that we load from the API. The user removes it by clicking the "X" button but realizes it should have stayed there. When opening the menu, the user could simply pick MyLabel
from the list. Because they only saw MyLabel
, they didn't know what was the actual value of it. Indeed, this is an edge case tho'.
I'll push an update to the PR in a couple of minutes with an implementation w/o urlOptions
.
this._sceneObject.setState({ | ||
urlOptions: values.map((value, idx) => ({ | ||
value, | ||
text: labels[idx], | ||
})), | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't really understand why we need this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the urlOptions
in order not to lose the label of a custom value when we select an item from the available options.
Having them as MetricFindValue
as opposed to a map or even SelectableValue
directly makes it easier to reuse it without additional processing in the actual variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the urlOptions in order not to lose the label of a custom value when we select an item from the available options.
not sure I follow, what do you mean "loose the label of a custom value" ? a custom value can never have different value/label
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provided more details here: #705 (comment)
packages/scenes/src/variables/groupby/GroupByVariableUrlSyncHandler.ts
Outdated
Show resolved
Hide resolved
import { GroupByVariable } from './GroupByVariable'; | ||
import { toUrlCommaDelimitedString } from '../utils'; | ||
|
||
export class GroupByVariableUrlSyncHandler implements SceneObjectUrlSyncHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can reuse MultiValueUrlSyncHandler but with an option to sync label? since they are so close to identical
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, I can merge them into one. I'll implement it in a subsequent commit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started working on this but to be honest it kinda makes it weird to bring urlOptions
to multi-value variable. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bfmatei I think the whole concept of urlOptions is strange, need to figure out a way we can avoid it.
the URL state should only be concerned with setting the current value/text not the options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pushing an update without it now.
} | ||
|
||
urlValue = Array.isArray(urlValue) ? urlValue : [urlValue]; | ||
const valuesLabelsPairs = urlValue.map((value) => (value ? value.split(',') : [value])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would like to change this so that we implement it for all variables with value/label (ie in default MultiValueVariable), and only sync label when value != label.
I am worried though, this could be a breaking change, all current saved URLs with variable values with commas would break? (I assume new URL values we escape commas, but not existing saved links)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides breaking the URLs, taking care of the texts is a problem that will need to be solved as well. I described it in the long comment above (i.e. require the texts parameter for changeValueTo
method.
97eba21
to
91edd05
Compare
🚀 PR was released in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bfmatei it looks and works good!
I've noticed a minor difference in the filters and group by rendering.
Let's assume both filter and group by use the same dimensions set:
label1, v1
label2, v2
label3, v3
Then let's assume an old url was preserved that did not contain label in the url. So we have url like this:
?var-Filter=%20v1%7C%3D%7Caaa&var-Group%20by=%20v1
Now, unless the select was opened, what's synced into the renderer in both filter and group by is v1
. But then when openening the Group by v1
is replaced with label1
. Which is fine IMO. But it doesn't work like this for filters - the correct dimension is selected, but the value of the select does not change to label1
when opened. See the video:
Screen.Recording.2024-05-13.at.14.26.05.mov
Not a deal breaker, but something that we can improve in the followup PR.
This PR syncs labels of the values in the group by variable similarly to what we introduced for ad-hoc filters.
Unlike ad-hoc filters, group by is a little bit more of a special case hence the reason why some of the changes below. The solution might not be optimal, so please feel free to suggested changes and improvements.
Changes:
urlOptions
where we store the options that we received via the URLurlOptions
with what we receive from the datasource (or with thedefaultOptions
)validateAndUpdate
and also becausechangeValueTo
is sometimes called without the texts arrayMultiSelect
can be aSelectableValue
hence we can enforce the labels on valuesThe gotcha: let's assume we pass a value via URL. This value is not returned by the datasource hence we consider it a custom value. We then remove the custom value using the UI button. When opening the dropdown again, the custom value can be found in the dropdown. Is this a behavior we want to keep? You can also see this behavior in the demo below.
PS: tests will come in a subsequent push. For the time being I'm trying to decide upon edge cases.
Demo:
Screen.Recording.2024-04-23.at.14.07.53.mov
📦 Published PR as canary version:
4.20.0--canary.705.9031064372.0
✨ Test out this PR locally via:
npm install @grafana/scenes@4.20.0--canary.705.9031064372.0 # or yarn add @grafana/scenes@4.20.0--canary.705.9031064372.0