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

Migrate from Joi to @kbn/config-schema in "home" and "features" plugins #100201

Merged
merged 9 commits into from
May 18, 2021
1 change: 0 additions & 1 deletion src/plugins/home/server/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export type { TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials
export { TutorialsCategory } from './tutorials';

export type {
ParamTypes,
InstructionSetSchema,
ParamsSchema,
InstructionsSchema,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* Side Public License, v 1.
*/

import { SavedObject } from 'src/core/server';
import type { SampleDatasetSchema } from './sample_dataset_schema';
export type { SampleDatasetSchema, AppLinkSchema, DataIndexSchema } from './sample_dataset_schema';

export enum DatasetStatusTypes {
NOT_INSTALLED = 'not_installed',
Expand All @@ -26,57 +27,4 @@ export enum EmbeddableTypes {
SEARCH_EMBEDDABLE_TYPE = 'search',
VISUALIZE_EMBEDDABLE_TYPE = 'visualization',
}
export interface DataIndexSchema {
id: string;

// path to newline delimented JSON file containing data relative to KIBANA_HOME
dataPath: string;

// Object defining Elasticsearch field mappings (contents of index.mappings.type.properties)
fields: object;

// times fields that will be updated relative to now when data is installed
timeFields: string[];

// Reference to now in your test data set.
// When data is installed, timestamps are converted to the present time.
// The distance between a timestamp and currentTimeMarker is preserved but the date and time will change.
// For example:
// sample data set: timestamp: 2018-01-01T00:00:00Z, currentTimeMarker: 2018-01-01T12:00:00Z
// installed data set: timestamp: 2018-04-18T20:33:14Z, currentTimeMarker: 2018-04-19T08:33:14Z
currentTimeMarker: string;

// Set to true to move timestamp to current week, preserving day of week and time of day
// Relative distance from timestamp to currentTimeMarker will not remain the same
preserveDayOfWeekTimeOfDay: boolean;
}

export interface AppLinkSchema {
path: string;
icon: string;
label: string;
}

export interface SampleDatasetSchema<T = unknown> {
id: string;
name: string;
description: string;
previewImagePath: string;
darkPreviewImagePath: string;

// saved object id of main dashboard for sample data set
overviewDashboard: string;
appLinks: AppLinkSchema[];

// saved object id of default index-pattern for sample data set
defaultIndex: string;

// Kibana saved objects (index patter, visualizations, dashboard, ...)
// Should provide a nice demo of Kibana's functionality with the sample data set
savedObjects: Array<SavedObject<T>>;
dataIndices: DataIndexSchema[];
status?: string | undefined;
statusMsg?: unknown;
}

export type SampleDatasetProvider = () => SampleDatasetSchema;
Original file line number Diff line number Diff line change
Expand Up @@ -5,60 +5,94 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { Writable } from '@kbn/utility-types';
import { schema, TypeOf } from '@kbn/config-schema';

import Joi from 'joi';

const dataIndexSchema = Joi.object({
id: Joi.string()
.regex(/^[a-zA-Z0-9-]+$/)
.required(),
const idRegExp = /^[a-zA-Z0-9-]+$/;
const dataIndexSchema = schema.object({
id: schema.string({
validate(value: string) {
if (!idRegExp.test(value)) {
return `Does not satisfy regexp: ${idRegExp.toString()}`;
}
},
}),

// path to newline delimented JSON file containing data relative to KIBANA_HOME
dataPath: Joi.string().required(),
dataPath: schema.string(),

// Object defining Elasticsearch field mappings (contents of index.mappings.type.properties)
fields: Joi.object().required(),
fields: schema.recordOf(schema.string(), schema.any()),

// times fields that will be updated relative to now when data is installed
timeFields: Joi.array().items(Joi.string()).required(),
timeFields: schema.arrayOf(schema.string()),

// Reference to now in your test data set.
// When data is installed, timestamps are converted to the present time.
// The distance between a timestamp and currentTimeMarker is preserved but the date and time will change.
// For example:
// sample data set: timestamp: 2018-01-01T00:00:00Z, currentTimeMarker: 2018-01-01T12:00:00Z
// installed data set: timestamp: 2018-04-18T20:33:14Z, currentTimeMarker: 2018-04-19T08:33:14Z
currentTimeMarker: Joi.string().isoDate().required(),
currentTimeMarker: schema.string({
validate(value: string) {
if (isNaN(Date.parse(value))) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe it's a valid case to extend @kbn/config-schema with date type

Copy link
Contributor

Choose a reason for hiding this comment

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

It does. I'm surprised we didn't get any feedback about this type missing to be honest.

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.

created #100635

return 'Expected a valid string in iso format';
}
},
}),

// Set to true to move timestamp to current week, preserving day of week and time of day
// Relative distance from timestamp to currentTimeMarker will not remain the same
preserveDayOfWeekTimeOfDay: Joi.boolean().default(false),
preserveDayOfWeekTimeOfDay: schema.boolean({ defaultValue: false }),
});

const appLinkSchema = Joi.object({
path: Joi.string().required(),
label: Joi.string().required(),
icon: Joi.string().required(),
export type DataIndexSchema = TypeOf<typeof dataIndexSchema>;

const appLinkSchema = schema.object({
path: schema.string(),
label: schema.string(),
icon: schema.string(),
});
export type AppLinkSchema = TypeOf<typeof appLinkSchema>;

export const sampleDataSchema = {
id: Joi.string()
.regex(/^[a-zA-Z0-9-]+$/)
.required(),
name: Joi.string().required(),
description: Joi.string().required(),
previewImagePath: Joi.string().required(),
darkPreviewImagePath: Joi.string(),
export const sampleDataSchema = schema.object({
id: schema.string({
validate(value: string) {
if (!idRegExp.test(value)) {
return `Does not satisfy regexp: ${idRegExp.toString()}`;
}
},
}),
name: schema.string(),
description: schema.string(),
previewImagePath: schema.string(),
darkPreviewImagePath: schema.maybe(schema.string()),

// saved object id of main dashboard for sample data set
overviewDashboard: Joi.string().required(),
appLinks: Joi.array().items(appLinkSchema).default([]),
overviewDashboard: schema.string(),
appLinks: schema.arrayOf(appLinkSchema, { defaultValue: [] }),

// saved object id of default index-pattern for sample data set
defaultIndex: Joi.string().required(),
defaultIndex: schema.string(),

// Kibana saved objects (index patter, visualizations, dashboard, ...)
// Should provide a nice demo of Kibana's functionality with the sample data set
savedObjects: Joi.array().items(Joi.object()).required(),
dataIndices: Joi.array().items(dataIndexSchema).required(),
};
savedObjects: schema.arrayOf(
schema.object(
{
id: schema.string(),
type: schema.string(),
attributes: schema.any(),
references: schema.arrayOf(schema.any()),
version: schema.maybe(schema.any()),
},
{ unknowns: 'allow' }
)
),
dataIndices: schema.arrayOf(dataIndexSchema),

status: schema.maybe(schema.string()),
statusMsg: schema.maybe(schema.string()),
});

export type SampleDatasetSchema = Writable<TypeOf<typeof sampleDataSchema>>;
10 changes: 8 additions & 2 deletions src/plugins/home/server/services/sample_data/routes/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
*/

import { schema } from '@kbn/config-schema';
import { IRouter, Logger, IScopedClusterClient } from 'src/core/server';
import type {
IRouter,
Logger,
IScopedClusterClient,
SavedObjectsBulkCreateObject,
} from 'src/core/server';
import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types';
import { createIndexName } from '../lib/create_index_name';
import {
Expand Down Expand Up @@ -148,8 +153,9 @@ export function createInstallRoute(

const client = getClient({ includedHiddenTypes });

const savedObjects = sampleDataset.savedObjects as SavedObjectsBulkCreateObject[];
createResults = await client.bulkCreate(
sampleDataset.savedObjects.map(({ version, ...savedObject }) => savedObject),
savedObjects.map(({ version, ...savedObject }) => savedObject),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@kbn/config-schema creates a result type for { property: schema.any() }, as {property?: any}. That's okay, since any includes undefined or null types, but it's not compatible with savedObject.attributes property,(which is a generic, with unknown default).

{ overwrite: true }
);
} catch (err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
* Side Public License, v 1.
*/

import Joi from 'joi';
import { CoreSetup, PluginInitializerContext } from 'src/core/server';
import { SavedObject } from 'src/core/public';
import {
Expand Down Expand Up @@ -55,11 +54,13 @@ export class SampleDataRegistry {

return {
registerSampleDataset: (specProvider: SampleDatasetProvider) => {
const { error, value } = Joi.validate(specProvider(), sampleDataSchema);

if (error) {
let value: SampleDatasetSchema;
try {
value = sampleDataSchema.validate(specProvider());
} catch (error) {
throw new Error(`Unable to register sample dataset spec because it's invalid. ${error}`);
}

const defaultIndexSavedObjectJson = value.savedObjects.find((savedObjectJson: any) => {
return (
savedObjectJson.type === 'index-pattern' && savedObjectJson.id === value.defaultIndex
Expand Down
1 change: 0 additions & 1 deletion src/plugins/home/server/services/tutorials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export type { TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials
export { TutorialsCategory } from './lib/tutorials_registry_types';

export type {
ParamTypes,
InstructionSetSchema,
ParamsSchema,
InstructionsSchema,
Expand Down
Loading