-
Notifications
You must be signed in to change notification settings - Fork 885
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
[Feature anywhere] Add annotation click handler #3777
[Feature anywhere] Add annotation click handler #3777
Conversation
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
@amsiglan There are some unit test failures to check and fix: https://github.com/opensearch-project/OpenSearch-Dashboards/actions/runs/4605088099/jobs/8136698336?pr=3777 |
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
Codecov Report
@@ Coverage Diff @@
## feature/feature-anywhere #3777 +/- ##
============================================================
- Coverage 66.52% 66.50% -0.02%
============================================================
Files 3244 3246 +2
Lines 62448 62508 +60
Branches 9642 9654 +12
============================================================
+ Hits 41541 41574 +33
- Misses 18600 18626 +26
- Partials 2307 2308 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
|
LGTM regarding the clicking event handling framework! As part of the issue #3317 , can you also add a handler for custom tooltips? The content of the tooltips can be TODO for now and I can wrap up in a separate PR. And as a follow-up, can we add a flag somewhere to disable a particular event (e.g., clicking) from happening via the vis config somewhere? As part of the view events flyout, we will want to disable clicking on the charts to prevent endless flyout opening. |
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.
This mostly looks good on first read-through - just some minor nits about naming, and I agree with @ohltyler's suggestions.
How can this be tested or verified, though? There don't really seem to be any test cases, but maybe this is something targeted for functional tests. In any case some screencasts would help for other reviewers.
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
For the disable piece, I think it would be more convenient to check whether the flyout is already open and then no-op inside the flyout code than trying to disable it via config. Thoughts? |
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
Given some of the framework needed as part of the View Events flyout, we will be able to pass sufficient context to the chart here where we can disable the action. |
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
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.
LGTM! Thanks again for helping out here!
I have a high level concern with this approach. Expressions today pass interaction data through This is how all VisBuilder events for example are handled today irrespective of the underlying renderer:https://github.com/opensearch-project/OpenSearch-Dashboards/blob/main/src/plugins/vis_builder/public/application/components/workspace.tsx#L119. It then takes this event data and retransmits it using UI actions that and UI action handler can then listen to and perform an action. The flow that i would expect here is something like this:
|
Also based on what i learnt from @joshuarrrr it looks like the vega expression function is already setup to handle the the click and brush events, and my gut feeling is that its using a pipeline similar to the one ive described above. Doing a deep dive on how that works could probably answer how we want to implement the same here. |
…dd-annotation-click-handler
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
1093a3f
to
ed817b2
Compare
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
execute: async (context: ExternalActionActionContext) => { | ||
if (isPointInTimeAnnotation(context.data.item)) { | ||
if (context.data.event === 'click') { | ||
// TODO: show events flyout | ||
} else if (context.data.event === 'mouseover') { | ||
// TODO: show custom tooltip | ||
} | ||
} | ||
}, |
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.
Should we have these actions split into 2? One for triggering the flyout opening, and another for triggering the custom tooltip? Curious of thoughts from @ashwin-pc on this as well.
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.
Depends on whether we want to keep events from the visualization generic and have the handler distinguish based off of the data. This way we will keep the events minimal and avoid adding specific info into the general framework.
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.
Yeah - I don't have a strong preference either way. Agreed we don't want to overload too specific of events here. Maybe one per VisLayer
implementation makes sense - in this particular case, handling click/mouseover/mouseout actions for the PointInTimeEventsVisLayer
.
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.
On second thought, perhaps we do want to have these split up. We will need a mechanism for disabling the click event behavior, but still enabling the custom tooltip behavior in the view events flyout. Do you see a way we could pass that logic here? Or if we split into 2 events maybe (1 for custom click event, 1 for custom hover event), be able to disable one and enable the other?
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.
The actions dont need to be defined here. The plugin listen for the trigger directly. If we have a standard contract for triggers (Which this Pr is adding), the plugin can simply listen for that trigger and do as they please.
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.
@amsiglan thanks. This makes sense to me in the context of the click event; I can defined a UIAction that listens for this trigger to open the view events flyout. But there's 2 things I'd like to clarify:
- For the scenario of disabling the clicking, but still enabling the custom tooltips, how do you propose I do this? One solution I could see is altering
addVisEventSignalsToSpecConfig()
to only haveon
for the mouse events, and just omit the click event. - This is maybe a bit of a miss on my part when reviewing the latest revisions made, but for the tooltips, how should we get sufficient context passed such that a UIAction can render the tooltip correctly? The original proposal was making changes to the implemented TooltipHandler class, which is using the out-of-the-box
tooltip()
function provided by vega to have a customized tooltip to render it OUI-style. It requires a lot of extra context, such as the container the vega view is in, the view itself, and other positioning parameters. I actually still think we may want to treat the tooltip action as just a param within the vis itself, that determines what tooltip html we render. The example I originally had is seen 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 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 don't need to disable the clicks, we could just check if the flyout is already open then just no-op in the event listener.
How would you propose this? We don't persist global flyout state so how would we be able to make such a check within the UIAction?
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.
@ohltyler Why do we need to disable click? what is the interaction pattern that we want to achieve with that?
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.
So user can't infinitely re-open the flyout when clicking - details in the issue #3317
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.
Based on the code change and some personal digging into this I think you might be able to make this wayy less specific to your implementation and a safer change. The way Kibana had done this seems to be using signals and vega expression functions. This is what i was able to figure out:
- They register each of the vega expression functions in the base view file (But not in the class): https://github.com/ashwin-pc/OpenSearch-Dashboards/blob/main/src/plugins/vis_type_vega/public/vega_view/vega_base_view.js#L56-L63
- The registered functions when called by the signals api in the spec forward the event to the instance specific handler defined in the base class Ref. This is usually done using the
on -> update
props for signals. I presume this is done so that the specific functions like brush have access to the correctthis
value. - The specific handler function then calls
vis
expression function that has access to the event bus and theeventSubject
object that then passes the data to the event bus using the appropriate event name (e.g.applyFilter)
For your implementation you can keep it quite simple, something like this:
- Add a new function here like
opensearchDashboardsTrigger: 'triggerEventHandler'
and define the event handler in the base class like
async triggerEventHandler(triggerName, data) {
this._triggerEvent({ name: triggerName, data });
}
and also add a prop for the Vega Base view to pass _triggerEvent
similar to _applyFilter
- Then in
vega_siualization.js
pass thetriggerEvent
fromthis._vis.API.events.
the same wayapplyFilter
does. - Then in the
vis.ts
file in expressions, add thetriggerEvent
function that can pass the name and data to the expression event bus (eventSubject) - From then on it should just be a matter of listening for the trigger in the AD plugin and showing the flyout based on it :)
Now to use this new function you will have to use signals. You can refer to the vega functional test to see how these signals are written and used: https://github.com/ashwin-pc/OpenSearch-Dashboards/blob/main/test/functional/apps/visualize/_vega_chart.ts#L35-L48
execute: async (context: ExternalActionActionContext) => { | ||
if (isPointInTimeAnnotation(context.data.item)) { | ||
if (context.data.event === 'click') { | ||
// TODO: show events flyout | ||
} else if (context.data.event === 'mouseover') { | ||
// TODO: show custom tooltip | ||
} | ||
} | ||
}, |
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.
The actions dont need to be defined here. The plugin listen for the trigger directly. If we have a standard contract for triggers (Which this Pr is adding), the plugin can simply listen for that trigger and do as they please.
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
Signed-off-by: Amardeepsingh Siglani <amardeep7194@gmail.com>
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.
LGTM! We can keep the tracking issue open until tooltip handling is merged :)
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.
Nice this is definitely almost ready, just had a few questions
const config = get(spec, 'config', { kibana: {} }); | ||
const signals = { | ||
...(config.kibana.signals || {}), | ||
[`${VisAnnotationType.POINT_IN_TIME_ANNOTATION}`]: [ |
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.
Arent signals an array?
e.g.
"signals": [
{
"name": "indexDate",
"description": "A date value that updates in response to mousemove.",
"update": "datetime(2005, 0, 1)",
"on": [{"events": "mousemove", "update": "invert('xscale', x())"}]
}
],
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.
This is just a signals
config that isn't directly tied to the spec. It's embedded within the existing kibana
field in the vega lite spec that's processed to handle OSD-specific items in the vega parser.
From my understanding signals
here is just a mapping of mark ID to a signals
array (that will be added to the compiled vega
spec later on)
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.
Yes, that is correct @ohltyler . In the vega parser we use the key "markId" to find the mark and then add then update the signal with that mark name.
@@ -323,6 +324,52 @@ The URL is an identifier only. OpenSearch Dashboards and your browser will never | |||
delete this.spec.autosize; | |||
} | |||
} | |||
|
|||
if (this._config?.signals) { | |||
Object.entries(this._config?.signals).forEach(([markId, signals]: [string, any]) => { |
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.
Just to clarify will this break any existing spec that uses signals?
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.
No. Since we control this signals
value here, as it's set by us in addVisEventSignalsToSpecConfig()
. signals
here isn't the actual signals
field per the vega spec, but just the config field name embedded within kibana
config obj. See comment above.
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.
Nice this is definitely almost ready, just had a few questions
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.
Thanks for all the context and changes @amsiglan, this looks good to me!
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 could use more testing of edge case handling.
@@ -99,6 +100,10 @@ export class ExprVis extends EventEmitter { | |||
if (!this.eventsSubject) return; | |||
this.eventsSubject.next({ name: 'applyFilter', data }); | |||
}, | |||
externalAction: (data: any) => { |
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.
Is this planned for functional testing, or can it be unit tested?
* This method recursively looks for a mark that includes the given style. | ||
* Returns undefined if it doesn't find it. | ||
*/ | ||
getMarkWithStyle(marks: any[], style: string): any { |
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.
Seems like some unit tests here would be nice to validate all the conditional branching.
Description
This PR adds framework to register event handers with vega based visualizations by adding config and providing handler in the vis_augmenter plugin.
Issues Resolved
#3317
Check List
yarn test:jest
yarn test:jest_integration
yarn test:ftr