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

Add UiSettings validation & Kibana default route redirection #59694

Merged

Conversation

mshustov
Copy link
Contributor

@mshustov mshustov commented Mar 9, 2020

Summary

UiSettings validation

Closes #50658
Covers 1-4 cases from #46717

There are several ways to configure an advanced setting today:

Through the Advanced Settings UI
Locked via kibana.yml's uiSettings.overrides.<key>
Through the client-side UI Settings Service
Through the server-side UI Settings Service
Through manual changes to the underlying config object

Through the Advanced Settings UI - a value is validated on the server-side, Kibana server rejects the request, UI reverts changes and shows a notification popup with an error message.
Locked via kibana.yml's uiSettings.overrides.<key> - validated on the server-side on the start
Through the client-side UI Settings Service a value is validated on the server-side, Kibana server rejects request
Through the server-side UI Settings Service - uiSettings service validates value on write and throws an exception if validation fails.
Through manual changes to the underlying config object - uiSettings service validates values on reading, ignores it if validation fails and logs a warning message.

As a bonus, uiSettings service validates on start defaults values. doesn't work currently since all declarations are done in LP

Default route validation

I got rid of all validation logic and moved the logic to NP

Checklist

Delete any items that are not applicable to this PR.

For maintainers

Dev docs

UiSettings definition allows to specify validation functions:

import { schema } from '@kbn/config-schema';

uiSettings.register({
   myUiSetting: {
      name: ...
      value: 'value',
      schema: schema.string()
  }
})

@mshustov mshustov added Feature:New Platform Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc labels Mar 9, 2020
@elasticmachine
Copy link
Contributor

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

Comment on lines +156 to +160
if (err.response.status === 400) {
throw new Error(err.body.message);
}
if (err.response.status > 400) {
throw new Error(`Request failed with status code: ${err.response.status}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably related to the changes in src/core/server/ui_settings/routes/set.ts , but why the difference between 400 and 4xx?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request failed with status code: ${err.response.status} is not really useful. I'd rather remove them completely and provide a more descriptive message, but BWC...So I added this for BadRequest only to provide an actionable message.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We really need typing for our errors so that we can evolve these 😢

src/core/server/server.ts Outdated Show resolved Hide resolved
src/core/server/ui_settings/ui_settings_client.ts Outdated Show resolved Hide resolved
Comment on lines +105 to +118
for (const [key, definition] of this.uiSettingsDefaults) {
if (definition.schema) {
definition.schema.validate(definition.value, {}, `ui settings defaults [${key}]`);
}
}
}

private validatesOverrides() {
for (const [key, value] of Object.entries(this.overrides)) {
const definition = this.uiSettingsDefaults.get(key);
if (definition?.schema) {
definition.schema.validate(value, {}, `ui settings overrides [${key}]`);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe try/catch to collect all validation errors to throw an aggregated error to show them all at once in case of multiple errors?

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'd avoid rethrowing an error since it changes the error type and stack trace.

defaults: ${JSON.stringify(
defaultUiSettings,
(key, value) => {
if (value instanceof Type) return null;
Copy link
Contributor Author

@mshustov mshustov Mar 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a hack to fix karma tests.
We might have to refactor karma tests when move ui settings defaults declaration to the NP.

@@ -33,6 +33,5 @@ export default function({ loadTestFile }) {
loadTestFile(require.resolve('./status'));
loadTestFile(require.resolve('./stats'));
loadTestFile(require.resolve('./ui_metric'));
loadTestFile(require.resolve('./core'));
Copy link
Contributor Author

@mshustov mshustov Mar 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a duplicate #59694 (comment)

@@ -147,17 +143,50 @@ export class UiSettingsClient implements IUiSettingsClient {
return defaultsDeep(userProvided, this.defaults);
}

private validateKey(key: string, value: unknown) {
const definition = this.defaults[key];
if (value === null || definition === undefined) return;
Copy link
Contributor Author

@mshustov mshustov Mar 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should throw when set a key without definition. Should I add this logic now or add to the list of future improvements? That would be a breaking change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that would be the preferred behaviour, but it feels like a risky change to make, it could easily cause exceptions in code. Maybe we should start with logging a message similar to setting an invalid key.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there's at least two cases where introducing an exception would be a problem:

  • Any settings that have been removed from the codebase but are still present in the user's config
  • Any settings that belong to a plugin that was enabled but is now disabled. I don't think we should ever break Kibana in this case.

* Value validation schema
* Used to validate value on write and read.
*/
schema?: Type<T>;
Copy link
Contributor Author

@mshustov mshustov Mar 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make it required. No objections?

uiSettings.register({
custom: {
value: '42',
schema: schema.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.

I was thinking about dynamic vs static declaration and it seems that dynamic is simpler. The only downside I can think of that we cannot get all the ui settings defaults without starting Kibana. That would be useful for such cases as uiSettings.overrides validation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as for SO types registration, so this is consistent.

@mshustov mshustov added the release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. label Mar 10, 2020
@mshustov mshustov requested a review from joshdover March 12, 2020 10:11
* A sub-set of {@link UiSettingsParams} exposed to the client-side.
* @public
* */
export type PublicUiSettingsParams = Omit<UiSettingsParams, 'schema'>;
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 open to other naming suggestions. ther problem that https://github.com/elastic/kibana/pull/59694/files#diff-e3ce52429da99a601c0f3a737a9039daR51 is not an internal type

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine with me

// validate value read from saved objects as it can be changed via SO API
const filteredValues: UserProvided<T> = {};
for (const [key, userValue] of Object.entries(values)) {
if (userValue === null || this.isOverridden(key)) continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be simpler & possibly safer if we validated the fully resolved settings (user provided + defaults) rather than just the user provided values. There's a small performance penalty for that, but I imagine it's negligible?

I just worry about this getting refactored at some point and the validation passing on settings that may not actually match the expected types 100%.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will validate defaults and overrides in the start phase.

As a bonus, uiSettings service validates on start defaults values. doesn't work currently since all declarations are done in LP

I don't wait to waste CPU cycles since users usually adjust a handful of settings, so I'd rather prioritize #59755 to move definitions to the NP.

filteredValues[key] = {
userValue: userValue as T,
};
} catch (error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aside: the more places I see use config-schema, the more I think it shouldn't throw exception messages. Invalid data isn't really an exception, if anything, it's almost entirely expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. In my initial implementation, I had to wrap it in a function returning {error?: string, valid: boolean}. I'd prefer @kbn/config-schema to have such an interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joshdover should I create an issue to investigate refactoring options?

* A sub-set of {@link UiSettingsParams} exposed to the client-side.
* @public
* */
export type PublicUiSettingsParams = Omit<UiSettingsParams, 'schema'>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine with me

@mshustov mshustov requested a review from a team as a code owner March 16, 2020 09:10
@kibanamachine
Copy link
Contributor

💚 Build Succeeded

History

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

Comment on lines 20 to 25
export {
UiSettingsParams,
PublicUiSettingsParams,
UserProvidedValues,
UiSettingsType,
ImageValidation,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not introduced by this PR, but we may want to rename the exposed types to use the UiSettings prefix at some point. Outside of the scope of the PR though.

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 will create an issue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added to #48925

uiSettings.register({
custom: {
value: '42',
schema: schema.string(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as for SO types registration, so this is consistent.

Copy link
Contributor

@mattkime mattkime left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app-arch changes look good to me

@mshustov mshustov merged commit dd7531d into elastic:master Mar 16, 2020
@mshustov mshustov deleted the issue-50658-default-route-redirection branch March 16, 2020 13:30
mshustov added a commit to mshustov/kibana that referenced this pull request Mar 16, 2020
…#59694)

* add schema to ui settings params

* add validation for defaults and overrides

* validate in ui settings client

* ui settings routes validation

* clean up tests

* use schema for defaultRoutes

* move URL redirection to NP

* fix spaces test

* update docs

* update kbn pm

* fix karma test

* fix tests

* address comments

* get rid of getDEfaultRoute

* regen docs

* fix tests

* fix enter-spaces test

* validate on relative url format

* update i18n

* fix enter-spoace test

* move relative url validation to utils

* add CoreApp containing application logic

* extract public uiSettings params in a separate type

* make schema required

* update docs
gmmorris added a commit to gmmorris/kibana that referenced this pull request Mar 16, 2020
* master: (40 commits)
  skips 'config_open.ts' files from linter check (elastic#60248)
  [Searchprofiler] Spacing between rendered shards (elastic#60238)
  Add UiSettings validation & Kibana default route redirection (elastic#59694)
  [SIEM][CASE] Change configuration button (elastic#60229)
  [Step 1][App Arch] Saved object migrations from kibana plugin to new platform  (elastic#59044)
  adds new test (elastic#60064)
  [Uptime] Index Status API to Rest (elastic#59657)
  [SIEM] Adds 'Closes and opens signals' Cypress test (elastic#59950)
  [NP] Graph migration (elastic#59409)
  [ML] Clone analytics job  (elastic#59791)
  Move VALUE_CLICK_TRIGGER and APPLY_FILTER_TRIGGER to ui_action… (elastic#60202)
  Handle improperly defined Watcher Logging Action text parameter. (elastic#60169)
  Move select range trigger to uiActions (elastic#60168)
  [SIEM][CASES] Configure cases: Final (elastic#59358)
  Closes elastic#59784. Sets xpack.apm.serviceMapEnabled default value true. (elastic#60153)
  [siem/cypress] update junit filename to be picked up by runbld (elastic#60156)
  OSS logic added to test environment  (elastic#60134)
  Move canvas setup into app mount (elastic#59926)
  enable triggers_actions_ui plugin by default (elastic#60137)
  Expose Elasticsearch from start and deprecate from setup (elastic#59886)
  ...
mshustov added a commit that referenced this pull request Mar 16, 2020
…#60257)

* add schema to ui settings params

* add validation for defaults and overrides

* validate in ui settings client

* ui settings routes validation

* clean up tests

* use schema for defaultRoutes

* move URL redirection to NP

* fix spaces test

* update docs

* update kbn pm

* fix karma test

* fix tests

* address comments

* get rid of getDEfaultRoute

* regen docs

* fix tests

* fix enter-spaces test

* validate on relative url format

* update i18n

* fix enter-spoace test

* move relative url validation to utils

* add CoreApp containing application logic

* extract public uiSettings params in a separate type

* make schema required

* update docs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backported Feature:New Platform release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v7.7.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Migrate default route redirection
9 participants