Conversation
torkelo
left a comment
There was a problem hiding this comment.
I think this is really cool, but until we have more examples / stronger need for this for many contexts I would recommend a more simple specific implementation for the ScopeContext specifically (maybe a base class that can handle some generic stuff).
Like a ScopeContextConsumer scene object that you can add to your scene (we would add it to DashboardScene), maybe we add it as an optional state prop to EmbeddeddScene or SceneApp (DashboardSceneRenderer would have render the ScopeContextConsumer component to make the context => scene state active).
Adding this to all scene objects, and to ComponentWrapper feels a bit too drastic until we know how much this will be used
|
But I do think something like ScopesContextConsumer or ScopesSceneBridge (whatever we call it), could be something we add to scenes lib (and it depends on the context in runtime). And then scene app plugins can use it |
dprokop
left a comment
There was a problem hiding this comment.
Agree with Torkel overall, this is a neat addition, but maybe would be worth talking it trough more widely if there's really a library need for this. It introduces quite a bit of complexity, and I'm not sure if we should solve this problem in classic scenes, but rather in react-scenes.
| function SceneComponentWrapperWithoutMemo<T extends SceneObject>({ model, ...otherProps }: SceneComponentProps<T>) { | ||
| return [ | ||
| <ContextsConsumer key="contexts" model={model} />, | ||
| <ComponentRenderer key="component" model={model} {...otherProps} />, | ||
| ]; | ||
| } | ||
|
|
There was a problem hiding this comment.
Took me a bit to udnerstand this 🤔
|
I refactored this to only use React Context for Scopes (added a bridge between scopes and scenes). A couple of notes:
useEffect(() => {
console.log(context);
}, [context]);
|
torkelo
left a comment
There was a problem hiding this comment.
I think this looking really good
I think the isScopesLoading code added to AdHocFilters and GroupByVarable feel a bit on the "too complex" / detail that it should not have to care so deeply about (ie subscribe to state etc).
Wonder if we can skip that for now, and if it becomes a problem find a less invasive solution (like a loading spinner on the whole scene until scopes have initialized)
| private _onActivate() { | ||
| if (this.isQueryModeAuto()) { | ||
| const timeRange = sceneGraph.getTimeRange(this); | ||
| const scopesBridge = sceneGraph.getScopesBridge(this); |
There was a problem hiding this comment.
Long term I think it would be cool to generalize this concept with a special variable type that all query runners and variables depend on by default (so will block initial query execution), and the variable can then modify the query.
I tried to start a poc around it, a QueryEnhancerVariable but did not get very far.
But I think this implementation is also ok, just a shame it adds more complexity to this already complex class :)
There was a problem hiding this comment.
I have something in mind regarding this. I'll start a PoC in the next weeks 🤔 .
bfmatei
left a comment
There was a problem hiding this comment.
I'll just continue doing testing for this and see how it goes. I also need to fix the code in core and test that out as well.
| private _onActivate() { | ||
| if (this.isQueryModeAuto()) { | ||
| const timeRange = sceneGraph.getTimeRange(this); | ||
| const scopesBridge = sceneGraph.getScopesBridge(this); |
There was a problem hiding this comment.
I have something in mind regarding this. I'll start a PoC in the next weeks 🤔 .
| layout?: PageLayoutType; | ||
|
|
||
| // Whether to use scopes for this page | ||
| useScopes?: boolean; |
There was a problem hiding this comment.
do we really need this as a page level prop?
There was a problem hiding this comment.
I'm thinking that some pages might not want scopes to be shown for various reasons 🤔
| } | ||
|
|
||
| this.runWithTimeRange(timeRange); | ||
| this.runWithTimeRangeAndScopes(timeRange, scopesBridge); |
There was a problem hiding this comment.
Let's add tests for making sure the SQR works as expected w/ scopes.
6265009 to
c3a4b47
Compare
6412637 to
6fbea37
Compare
| if (this._pendingScopes && newContext) { | ||
| setTimeout(() => { | ||
| newContext?.changeScopes(this._pendingScopes!); | ||
| this._pendingScopes = null; |
There was a problem hiding this comment.
Should we just return here and end the update? It seems like the _pendingScopes have priority (cause they are from the URL) so we need to propagate them into the context, so it does not seem important what is the current newContext value.
There was a problem hiding this comment.
Haven't tested out this case. At a first glance I would say we still need to save the new context to get access to other props (i.e. enabled or loading) even if scopes themselves were not loaded.
Something in the lines of:
- load page with scopes in URL
- trigger scopes loading in the context which results in setting the
loadingflag totrue - the bridge prevents the query runner to perform queries given that scopes are loading
|
|
||
| public updateContext(newContext: ScopesContextValue | undefined) { | ||
| if (this._pendingScopes && newContext) { | ||
| setTimeout(() => { |
There was a problem hiding this comment.
Why is this timeout needed?
There was a problem hiding this comment.
IIRC it was a race condition between setting new scopes which triggers scopes loading hence setting the loading flag to true and pushing the new context value through _contextSubject.
| this.context?.setReadOnly(readOnly); | ||
| } | ||
|
|
||
| public updateContext(newContext: ScopesContextValue | undefined) { |
There was a problem hiding this comment.
When setting new scopes, do I have to call this? Wouldn't it be easier with an updateScopes method?
There was a problem hiding this comment.
updateScopes was deliberately skipped until we have a need to expose it publicly. This should never be called by plugins. However, marking it as private would prevent us from accessing it in the model.
An alternative would be to expose a SceneScopesBridgeLike interface that only contains public methods and use that as a return value for getScopesBridge or for the state prop in AppComponent.
There was a problem hiding this comment.
We can't set this to private, as it is used when setting the context from useScope
ea428ac to
5411c17
Compare
mdvictor
left a comment
There was a problem hiding this comment.
This is amazing work! Small comment about removing the filter enriching call in adhocs
| queries, | ||
| timeRange, | ||
| scopes: this._scopesBridge?.getValue(), | ||
| ...getEnrichedFiltersRequest(this), |
There was a problem hiding this comment.
Should we still have calls to getEnrichedFiltersRequest now that scopes are pulled directly here? Also other related filter enriching code? I'd reckon there's no use for them anymore, e.g.: packages/scenes/src/variables/getEnrichedFiltersRequest.ts
There was a problem hiding this comment.
Since the filters enrichers have all been removed here that call won't do anything so I guess we could leave it there for when/if we will ever enrich filters that way again?
| ([prevContext, newContext]) => | ||
| [prevContext?.state.value ?? [], newContext?.state.value ?? []] as [Scope[], Scope[]] | ||
| ), | ||
| filter(([prevScopes, newScopes]) => !isEqual(prevScopes, newScopes)) |
There was a problem hiding this comment.
Do we need this filtering here? I've not encountered a scenario where this might happen (e.g.: applying the same scopes from the selector would be one, I think)
But it does cause issues when trying to inject scope filters into adhoc. Basically when the dashboard loads, the scopes aren't yet loaded so they are not pushed to the adhoc, and when subscribeToValue triggers it compares prev and new scopes as being the same since it's the first value anyway. And because of this filtering we don't pass the first scopes values and the adhocs don't get populated
There was a problem hiding this comment.
Can we get around this by not filtering for undefined context above? I think having this safeguard can prevent unnecessary accidental updates. Maybe we can do a check if it is the first update here? We can do a distinctUntilChanged before mapping to the values. So it will end up emitting [] and [], but at least it will emit a value when going from an undefined context and the loading has finished..
There was a problem hiding this comment.
when subscribeToValue triggers it compares prev and new scopes as being the same since it's the first value anyway.
I think you can do something like this though right:
current = SceneScopesBridge.context
SceneScopesBridge.subscribeToValue(() => do something with value)
I feel like both the pairwise and the filters would mess up a bit the BehaviourSubject contract here. pairwise seem to need 2 events until it emits something, if we filter out undefined and loading context values this needs 3 changes to emit first time.
So I would agree that removing the first filter probably makes sense as you want to know if it was undefined before or goes to undefined value just has to be mapped right. Also as this suggests https://stackoverflow.com/questions/50059622/rxjs-observable-which-emits-both-previous-and-current-value-starting-from-first I would start it off with some empty value see this actually emits something from the start.
|
🚀 PR was released in |
Continuing the discussion from here: grafana/grafana#97176
So what this PR brings: allow SceneObjects to consume React Contexts.
📦 Published PR as canary version:
6.3.1--canary.990.13785017927.0✨ Test out this PR locally via:
npm install @grafana/scenes-react@6.3.1--canary.990.13785017927.0 npm install @grafana/scenes@6.3.1--canary.990.13785017927.0 # or yarn add @grafana/scenes-react@6.3.1--canary.990.13785017927.0 yarn add @grafana/scenes@6.3.1--canary.990.13785017927.0