Skip to content

Commit

Permalink
Review#2: implement UiSettingsDefaultsClient.
Browse files Browse the repository at this point in the history
  • Loading branch information
azasypkin committed Jul 16, 2021
1 parent ded628f commit c3b8ede
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A limited set of Elasticsearch configuration entries.
<b>Signature:</b>

```typescript
readonly config: ElasticsearchConfigPreboot;
readonly config: Readonly<ElasticsearchConfigPreboot>;
```

## Example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export interface ElasticsearchServicePreboot

| Property | Type | Description |
| --- | --- | --- |
| [config](./kibana-plugin-core-server.elasticsearchservicepreboot.config.md) | <code>ElasticsearchConfigPreboot</code> | A limited set of Elasticsearch configuration entries. |
| [config](./kibana-plugin-core-server.elasticsearchservicepreboot.config.md) | <code>Readonly&lt;ElasticsearchConfigPreboot&gt;</code> | A limited set of Elasticsearch configuration entries. |
| [createClient](./kibana-plugin-core-server.elasticsearchservicepreboot.createclient.md) | <code>(type: string, clientConfig?: Partial&lt;ElasticsearchClientConfig&gt;) =&gt; ICustomClusterClient</code> | Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md)<!-- -->. |

2 changes: 1 addition & 1 deletion src/core/server/elasticsearch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface ElasticsearchServicePreboot {
* const { hosts, credentialsSpecified } = core.elasticsearch.config;
* ```
*/
readonly config: ElasticsearchConfigPreboot;
readonly config: Readonly<ElasticsearchConfigPreboot>;

/**
* Create application specific Elasticsearch cluster API client with customized config. See {@link IClusterClient}.
Expand Down
2 changes: 1 addition & 1 deletion src/core/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,7 @@ export interface ElasticsearchConfigPreboot {

// @public (undocumented)
export interface ElasticsearchServicePreboot {
readonly config: ElasticsearchConfigPreboot;
readonly config: Readonly<ElasticsearchConfigPreboot>;
readonly createClient: (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient;
}

Expand Down
93 changes: 93 additions & 0 deletions src/core/server/ui_settings/base_ui_settings_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { omit } from 'lodash';

import {
IUiSettingsClient,
UiSettingsParams,
PublicUiSettingsParams,
UserProvidedValues,
} from './types';
import { Logger } from '../logging';

export interface BaseUiSettingsDefaultsClientOptions {
overrides?: Record<string, any>;
defaults?: Record<string, UiSettingsParams>;
log: Logger;
}

/**
* Base implementation of the {@link IUiSettingsClient}.
*/
export abstract class BaseUiSettingsClient implements IUiSettingsClient {
private readonly defaults: NonNullable<BaseUiSettingsDefaultsClientOptions['defaults']>;
private readonly defaultValues: Record<string, unknown>;
protected readonly overrides: NonNullable<BaseUiSettingsDefaultsClientOptions['overrides']>;
protected readonly log: Logger;

protected constructor(options: BaseUiSettingsDefaultsClientOptions) {
const { defaults = {}, overrides = {}, log } = options;
this.log = log;
this.overrides = overrides;

this.defaults = defaults;
this.defaultValues = Object.fromEntries(
Object.entries(this.defaults).map(([key, { value }]) => [key, value])
);
}

getRegistered() {
const copiedDefaults: Record<string, PublicUiSettingsParams> = {};
for (const [key, value] of Object.entries(this.defaults)) {
copiedDefaults[key] = omit(value, 'schema');
}
return copiedDefaults;
}

async get<T = any>(key: string): Promise<T> {
const all = await this.getAll();
return all[key] as T;
}

async getAll<T = any>() {
const result = { ...this.defaultValues };

const userProvided = await this.getUserProvided();
Object.keys(userProvided).forEach((key) => {
if (userProvided[key].userValue !== undefined) {
result[key] = userProvided[key].userValue;
}
});

return Object.freeze(result) as Record<string, T>;
}

isOverridden(key: string) {
return this.overrides.hasOwnProperty(key);
}

isSensitive(key: string): boolean {
const definition = this.defaults[key];
return !!definition?.sensitive;
}

protected validateKey(key: string, value: unknown) {
const definition = this.defaults[key];
if (value === null || definition === undefined) return;
if (definition.schema) {
definition.schema.validate(value, {}, `validation [${key}]`);
}
}

abstract getUserProvided<T = any>(): Promise<Record<string, UserProvidedValues<T>>>;
abstract setMany(changes: Record<string, any>): Promise<void>;
abstract set(key: string, value: any): Promise<void>;
abstract remove(key: string): Promise<void>;
abstract removeMany(keys: string[]): Promise<void>;
}
78 changes: 6 additions & 72 deletions src/core/server/ui_settings/ui_settings_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,20 @@
* Side Public License, v 1.
*/

import { omit } from 'lodash';

import { SavedObjectsErrorHelpers } from '../saved_objects';
import { SavedObjectsClientContract } from '../saved_objects/types';
import { Logger } from '../logging';
import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
import { IUiSettingsClient, UiSettingsParams, PublicUiSettingsParams } from './types';
import { UiSettingsParams } from './types';
import { CannotOverrideError } from './ui_settings_errors';
import { Cache } from './cache';
import { BaseUiSettingsClient } from './base_ui_settings_client';

export interface UiSettingsServiceOptions {
type: string;
id: string;
buildNum: number;
savedObjectsClient?: SavedObjectsClientContract;
savedObjectsClient: SavedObjectsClientContract;
overrides?: Record<string, any>;
defaults?: Record<string, UiSettingsParams>;
log: Logger;
Expand All @@ -37,67 +36,22 @@ interface UserProvidedValue<T = unknown> {

type UserProvided<T = unknown> = Record<string, UserProvidedValue<T>>;

function assertSavedObjectsClient(
savedObjectsClient?: SavedObjectsClientContract
): asserts savedObjectsClient is SavedObjectsClientContract {
if (!savedObjectsClient) {
throw new Error('Saved Objects Client is not available.');
}
}

export class UiSettingsClient implements IUiSettingsClient {
export class UiSettingsClient extends BaseUiSettingsClient {
private readonly type: UiSettingsServiceOptions['type'];
private readonly id: UiSettingsServiceOptions['id'];
private readonly buildNum: UiSettingsServiceOptions['buildNum'];
private readonly savedObjectsClient: UiSettingsServiceOptions['savedObjectsClient'];
private readonly overrides: NonNullable<UiSettingsServiceOptions['overrides']>;
private readonly defaults: NonNullable<UiSettingsServiceOptions['defaults']>;
private readonly defaultValues: Record<string, unknown>;
private readonly log: Logger;
private readonly cache: Cache;

constructor(options: UiSettingsServiceOptions) {
const { type, id, buildNum, savedObjectsClient, log, defaults = {}, overrides = {} } = options;
super({ overrides, defaults, log });

this.type = type;
this.id = id;
this.buildNum = buildNum;
this.savedObjectsClient = savedObjectsClient;
this.overrides = overrides;
this.log = log;
this.cache = new Cache();
this.defaults = defaults;
const defaultValues: Record<string, unknown> = {};
Object.keys(this.defaults).forEach((key) => {
defaultValues[key] = this.defaults[key].value;
});
this.defaultValues = defaultValues;
}

getRegistered() {
const copiedDefaults: Record<string, PublicUiSettingsParams> = {};
for (const [key, value] of Object.entries(this.defaults)) {
copiedDefaults[key] = omit(value, 'schema');
}
return copiedDefaults;
}

async get<T = any>(key: string): Promise<T> {
const all = await this.getAll();
return all[key] as T;
}

async getAll<T = any>() {
const result = { ...this.defaultValues };

const userProvided = await this.getUserProvided();
Object.keys(userProvided).forEach((key) => {
if (userProvided[key].userValue !== undefined) {
result[key] = userProvided[key].userValue;
}
});

Object.freeze(result);
return result as Record<string, T>;
}

async getUserProvided<T = unknown>(): Promise<UserProvided<T>> {
Expand Down Expand Up @@ -142,29 +96,12 @@ export class UiSettingsClient implements IUiSettingsClient {
await this.setMany(changes);
}

isOverridden(key: string) {
return this.overrides.hasOwnProperty(key);
}

isSensitive(key: string): boolean {
const definition = this.defaults[key];
return !!definition?.sensitive;
}

private assertUpdateAllowed(key: string) {
if (this.isOverridden(key)) {
throw new CannotOverrideError(`Unable to update "${key}" because it is overridden`);
}
}

private validateKey(key: string, value: unknown) {
const definition = this.defaults[key];
if (value === null || definition === undefined) return;
if (definition.schema) {
definition.schema.validate(value, {}, `validation [${key}]`);
}
}

private onWriteHook(changes: Record<string, unknown>) {
for (const key of Object.keys(changes)) {
this.assertUpdateAllowed(key);
Expand Down Expand Up @@ -201,7 +138,6 @@ export class UiSettingsClient implements IUiSettingsClient {
changes: Record<string, any>;
autoCreateOrUpgradeIfMissing?: boolean;
}) {
assertSavedObjectsClient(this.savedObjectsClient);
try {
await this.savedObjectsClient.update(this.type, this.id, changes);
} catch (error) {
Expand All @@ -227,7 +163,6 @@ export class UiSettingsClient implements IUiSettingsClient {
private async read({ autoCreateOrUpgradeIfMissing = true }: ReadOptions = {}): Promise<
Record<string, any>
> {
assertSavedObjectsClient(this.savedObjectsClient);
try {
const resp = await this.savedObjectsClient.get<Record<string, any>>(this.type, this.id);
return resp.attributes;
Expand Down Expand Up @@ -257,7 +192,6 @@ export class UiSettingsClient implements IUiSettingsClient {
}

private isIgnorableError(error: Error) {
assertSavedObjectsClient(this.savedObjectsClient);
const { isForbiddenError, isEsUnavailableError } = this.savedObjectsClient.errors;

return isForbiddenError(error) || isEsUnavailableError(error);
Expand Down
Loading

0 comments on commit c3b8ede

Please sign in to comment.