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

[GS] add application result provider #68488

Merged
merged 9 commits into from
Jun 29, 2020

Conversation

pgayvallet
Copy link
Contributor

@pgayvallet pgayvallet commented Jun 8, 2020

Summary

Fix #65633

Create a new globalSearchProviders plugin and implement the application client-side provider.

Checklist

@pgayvallet pgayvallet added release_note:skip Skip the PR/issue when compiling release notes v7.9.0 v8.0.0 labels Jun 8, 2020
@pgayvallet pgayvallet added the Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc label Jun 8, 2020
Comment on lines +253 to +256
// remove optional on fields populated with default values
status: AppStatus;
navLinkStatus: AppNavLinkStatus;
appRoute: string;
Copy link
Contributor Author

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.

Copy link
Contributor

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",
Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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 on global_search and global_search_providers
  • Putting everything in one plugin

cc @myasonik

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Comment on lines 17 to 18
// remove disabled and chromeless app (to avoid returning the login page for example)
.filter((app) => app.status === 0 && (app.legacy === true || app.chromeless !== true))
Copy link
Contributor Author

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.

Copy link
Contributor

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.

Comment on lines +29 to +47
// 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;
Copy link
Contributor Author

@pgayvallet pgayvallet Jun 8, 2020

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?

Copy link
Contributor

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.

Copy link
Contributor

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)

Copy link
Contributor

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).

Copy link
Contributor

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 ?

Copy link
Contributor Author

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).

@pgayvallet pgayvallet marked this pull request as ready for review June 8, 2020 10:44
@pgayvallet pgayvallet requested a review from a team as a code owner June 8, 2020 10:44
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-platform (Team:Platform)

@@ -0,0 +1,10 @@
{
"id": "globalSearchProviders",
Copy link
Contributor

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 on global_search and global_search_providers
  • Putting everything in one plugin

cc @myasonik

Comment on lines 15 to 20
export const plugin: PluginInitializer<
GlobalSearchProvidersPluginSetup,
GlobalSearchProvidersPluginStart,
GlobalSearchProvidersPluginSetupDeps,
GlobalSearchProvidersPluginStartDeps
> = (context) => new GlobalSearchProvidersPlugin();
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Comment on lines +29 to +47
// 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;
Copy link
Contributor

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 ?

Comment on lines 19 to 20
return from(applicationPromise).pipe(
mergeMap((application) => application.applications$),
Copy link
Contributor

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.

Comment on lines +42 to +46
// 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;
}
Copy link
Contributor

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.

Copy link
Contributor Author

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;
  }

Copy link
Contributor

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.

Comment on lines +253 to +256
// remove optional on fields populated with default values
status: AppStatus;
navLinkStatus: AppNavLinkStatus;
appRoute: string;
Copy link
Contributor

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",
Copy link
Contributor

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.

Comment on lines +42 to +46
// 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;
}
Copy link
Contributor

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.

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Build metrics

@kbn/optimizer bundle module count

id value diff baseline
globalSearchProviders 3 +3 -

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@pgayvallet pgayvallet merged commit 7e5cff4 into elastic:master Jun 29, 2020
pgayvallet added a commit to pgayvallet/kibana that referenced this pull request Jun 29, 2020
* add application result provider

* remove empty contracts & cache searchable apps

* fix types
pgayvallet added a commit that referenced this pull request Jun 29, 2020
* add application result provider

* remove empty contracts & cache searchable apps

* fix types
Bamieh pushed a commit to Bamieh/kibana that referenced this pull request Jul 1, 2020
* add application result provider

* remove empty contracts & cache searchable apps

* fix types
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release_note:skip Skip the PR/issue when compiling release notes Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v7.9.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[GS] Application results provider
6 participants