-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[GS] add application result provider #68488
[GS] add application result provider #68488
Conversation
// remove optional on fields populated with default values | ||
status: AppStatus; | ||
navLinkStatus: AppNavLinkStatus; | ||
appRoute: string; |
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.
Ease consumers usage by transforming optional fields into non-optional as we are injecting default values during registration.
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.
Minor thing, but you could use the Required
type, but this may be simpler to read.
@@ -0,0 +1,10 @@ | |||
{ | |||
"id": "globalSearchProviders", |
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.
Unless anyone is against it, I was planning to create a single globalSearchProviders
plugin for both the application
and (in the following PR) the savedObjects
providers.
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.
SGTM. Once #68811 is merged, we should group this plugin with the global_search plugin in the same directory.
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.
Was thinking about this more and maybe this should live in the same plugin. Where do we expect the search UI code to go? Since it needs to be under the Elastic License, it must be in a plugin. I see two options:
- A 3rd plugin, like
global_search_ui
that depends onglobal_search
andglobal_search_providers
- Putting everything in one plugin
cc @myasonik
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 a terrible person to ask in this case. I've got little context on the tradeoffs of this... Either options seems fine.
I'd maybe err towards separate plugins in case we ever want to experiment with different search strategies, it might be easier to swap between two separate plugins rather than handling it all in one.
But, really, I'm just guessing at a bunch of this so other opinions encouraged.
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.
Was thinking about this more and maybe this should live in the same plugin. Where do we expect the search UI code to go
I was thinking of something like global_search_bar
or something like that tbh.
I added a new plugin for the GS providers basically only because I could (tm) and it was maybe a slightly better isolation. But I have no strong feeling toward this. I can move the providers into the global_search
plugin if we want to.
For the searchbar
/ search_ui
, I still thinks it makes more sense to have it as a separate plugin, as it's supposed to be 'just' a consumer of the GS
API. But here too, I will follow the majority if we think it's better to have everything in a single plugin.
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.
Let's just go with separate plugins for everything and then group them in a single directory once #68811 merges.
// remove disabled and chromeless app (to avoid returning the login page for example) | ||
.filter((app) => app.status === 0 && (app.legacy === true || app.chromeless !== true)) |
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 filtering chromeless
apps because I don't think it makes any sense to find the login
or logout
application. I think it's acceptable to declare that our chromeless
should all be considered as hidden
.
If it's not the case, we would probably need to introduce an additional field in App
, as there is currently no way to differenciate chromeless apps that should appear in the results from the one that shouldn't.
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.
If we ever need to change our mind in the future, it seems easiest to assume chromeless
is also hidden
unless there's also some showInSearch
field present too which we could add whenever the need comes up.
// shortcuts to avoid calculating the distance when there is an exact match somewhere. | ||
if (title === term) { | ||
return 100; | ||
} | ||
if (title.startsWith(term)) { | ||
return 90; | ||
} | ||
if (title.includes(term)) { | ||
return 75; | ||
} | ||
const length = Math.max(term.length, title.length); | ||
const distance = levenshtein(term, title); | ||
|
||
// maximum lev distance is length, we compute the match ratio (lower distance is better) | ||
const ratio = Math.floor((1 - distance / length) * 100); | ||
if (ratio >= 60) { | ||
return ratio; | ||
} | ||
return 0; |
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.
Note that all the scoring logic in scoreApp
is totally arbitrary, as we have to implement it ourselves.
I'm still unsure about the need to rely on levenshtein distance when there is not an exact match. There are not a lot of apps, and titles are +- max 15chars, so the computation time should be negligible though.
@elastic/kibana-core-ui WDYT? Are exact (sub-)matches sufficient when searching for applications, or do you think the levenshtein scoring makes sense 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.
Given that the number of apps, I suspect exact matches will be sufficient. It would be good to verify that by actually trying it out, but that's my quick take.
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.
It doesn't seem to complicate much, so I'd rather not use exact match...
Thinking about non-English/non-native English speakers trying to use Kibana when we don't translate our app names, this seems like a nice way to catch typos.
Some examples:
- Visualize vs Visualise
- Machine Learning vs Mashine vs Machien (actually, thinking about it, there are like a million ways to spell machine so it still sounds right)
- Swapping any hard "c" for a "k" (e.g., Discover, Canvas, Metrics)
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 not an immediate thing, but maybe worth opening a ticket for, it could be nice to translate the app names for search (e.g., someone translates the word "Discover" into their language and searches that, maybe we could still surface Discover then).
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 haven't dug in deep, but I think our existing setup should work with translated app title strings as well. Those are translated before they enter the Core system so we should always be comparing search terms to the translated strings. Is that right @pgayvallet ?
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.
Those are translated before they enter the Core system so we should always be comparing search terms to the translated strings
That is correct. As long as the title of the app is a translated string, we will be performing the comparison on the translated title (and would not really be able to do otherwise without adding a new field to AppBase
).
Pinging @elastic/kibana-platform (Team:Platform) |
@@ -0,0 +1,10 @@ | |||
{ | |||
"id": "globalSearchProviders", |
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.
Was thinking about this more and maybe this should live in the same plugin. Where do we expect the search UI code to go? Since it needs to be under the Elastic License, it must be in a plugin. I see two options:
- A 3rd plugin, like
global_search_ui
that depends onglobal_search
andglobal_search_providers
- Putting everything in one plugin
cc @myasonik
export const plugin: PluginInitializer< | ||
GlobalSearchProvidersPluginSetup, | ||
GlobalSearchProvidersPluginStart, | ||
GlobalSearchProvidersPluginSetupDeps, | ||
GlobalSearchProvidersPluginStartDeps | ||
> = (context) => new GlobalSearchProvidersPlugin(); |
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.
nit: you could just leave these generics empty. Honestly they don't really provide any value, maybe we should just remove them from the PluginInitializer type.
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.
My java background probably. Will remove them.
// shortcuts to avoid calculating the distance when there is an exact match somewhere. | ||
if (title === term) { | ||
return 100; | ||
} | ||
if (title.startsWith(term)) { | ||
return 90; | ||
} | ||
if (title.includes(term)) { | ||
return 75; | ||
} | ||
const length = Math.max(term.length, title.length); | ||
const distance = levenshtein(term, title); | ||
|
||
// maximum lev distance is length, we compute the match ratio (lower distance is better) | ||
const ratio = Math.floor((1 - distance / length) * 100); | ||
if (ratio >= 60) { | ||
return ratio; | ||
} | ||
return 0; |
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 haven't dug in deep, but I think our existing setup should work with translated app title strings as well. Those are translated before they enter the Core system so we should always be comparing search terms to the translated strings. Is that right @pgayvallet ?
return from(applicationPromise).pipe( | ||
mergeMap((application) => application.applications$), |
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 wonder if we should cache this value so we don't need to recompute on each search. Same with the filtering that is done in getAppResults
for chromeless apps, etc. I know the perf impact here is probably minimal, but since this may be executed on each keypress, we should go ahead and do the easy wins if doesn't make the code harder to read.
// maximum lev distance is length, we compute the match ratio (lower distance is better) | ||
const ratio = Math.floor((1 - distance / length) * 100); | ||
if (ratio >= 60) { | ||
return ratio; | ||
} |
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 it make sense for all matches that reach this part to be ranked lower than the exact matches above? If so, we should be multiplying the ratio by 74 instead of 100 to lower the ceiling.
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.
If we do that, we will have all 'searchable' apps returned on every search. We can tweak/weight the final ratio differently if we want to, but we will still need a score floor behind which we are excluding results.
Just impacting the final score could look like something like
const ratio = Math.floor((1 - distance / length) * 100);
if (ratio >= 60) {
return ratio * 0.74;
}
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.
Ah yes, that's more what I meant. Let's just keep as-is for now and we can tweak this once we have a UI to test this out with.
// remove optional on fields populated with default values | ||
status: AppStatus; | ||
navLinkStatus: AppNavLinkStatus; | ||
appRoute: string; |
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.
Minor thing, but you could use the Required
type, but this may be simpler to read.
@@ -0,0 +1,10 @@ | |||
{ | |||
"id": "globalSearchProviders", |
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.
Let's just go with separate plugins for everything and then group them in a single directory once #68811 merges.
// maximum lev distance is length, we compute the match ratio (lower distance is better) | ||
const ratio = Math.floor((1 - distance / length) * 100); | ||
if (ratio >= 60) { | ||
return ratio; | ||
} |
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.
Ah yes, that's more what I meant. Let's just keep as-is for now and we can tweak this once we have a UI to test this out with.
💚 Build SucceededBuild metrics@kbn/optimizer bundle module count
History
To update your PR or re-run it, just comment with: |
* add application result provider * remove empty contracts & cache searchable apps * fix types
* add application result provider * remove empty contracts & cache searchable apps * fix types
Summary
Fix #65633
Create a new
globalSearchProviders
plugin and implement theapplication
client-side provider.Checklist