Skip to content

Commit e073190

Browse files
committed
Merge branch 'master' of github.com:elastic/kibana into qa-slack-send-on-fail
2 parents 0a42bbe + f7739b5 commit e073190

File tree

144 files changed

+7035
-608
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

144 files changed

+7035
-608
lines changed

docs/apm/advanced-queries.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ or, to only show transactions that are slower than a specified time threshold.
1111
==== Example APM app queries
1212

1313
* Exclude response times slower than 2000 ms: `transaction.duration.us > 2000000`
14-
* Filter by response status code: `context.response.status_code >= 400`
14+
* Filter by response status code: `context.response.status_code 400`
1515
* Filter by single user ID: `context.user.id : 12`
1616

1717
When querying in the APM app, you're merely searching and selecting data from fields in Elasticsearch documents.

docs/apm/service-maps.asciidoc

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ Machine learning jobs can be created to calculate anomaly scores on APM transact
6262
When these jobs are active, service maps will display a color-coded anomaly indicator based on the detected anomaly score:
6363

6464
[horizontal]
65-
image:apm/images/green-service.png[APM green service]:: Max anomaly score **<=25**. Service is healthy.
65+
image:apm/images/green-service.png[APM green service]:: Max anomaly score **25**. Service is healthy.
6666
image:apm/images/yellow-service.png[APM yellow service]:: Max anomaly score **26-74**. Anomalous activity detected. Service may be degraded.
67-
image:apm/images/red-service.png[APM red service]:: Max anomaly score **>=75**. Anomalous activity detected. Service is unhealthy.
67+
image:apm/images/red-service.png[APM red service]:: Max anomaly score **75**. Anomalous activity detected. Service is unhealthy.
6868

6969
[role="screenshot"]
7070
image::apm/images/apm-service-map-anomaly.png[Example view of anomaly scores on service maps in the APM app]
@@ -92,10 +92,10 @@ Type and subtype are based on `span.type`, and `span.subtype`.
9292
Service maps are supported for the following Agent versions:
9393

9494
[horizontal]
95-
Go Agent:: >= v1.7.0
96-
Java Agent:: >= v1.13.0
97-
.NET Agent:: >= v1.3.0
98-
Node.js Agent:: >= v3.6.0
99-
Python Agent:: >= v5.5.0
100-
Ruby Agent:: >= v3.6.0
101-
Real User Monitoring (RUM) Agent:: >= v4.7.0
95+
Go Agent:: v1.7.0
96+
Java Agent:: v1.13.0
97+
.NET Agent:: v1.3.0
98+
Node.js Agent:: v3.6.0
99+
Python Agent:: v5.5.0
100+
Ruby Agent:: v3.6.0
101+
Real User Monitoring (RUM) Agent:: v4.7.0

packages/kbn-config-schema/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"kbn:bootstrap": "yarn build"
1111
},
1212
"devDependencies": {
13-
"typescript": "3.7.2"
13+
"typescript": "3.7.2",
14+
"tsd": "^0.7.4"
1415
},
1516
"peerDependencies": {
1617
"joi": "^13.5.2",

packages/kbn-config-schema/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
ObjectType,
4545
ObjectTypeOptions,
4646
Props,
47+
NullableProps,
4748
RecordOfOptions,
4849
RecordOfType,
4950
StringOptions,
@@ -57,7 +58,7 @@ import {
5758
StreamType,
5859
} from './types';
5960

60-
export { ObjectType, TypeOf, Type };
61+
export { ObjectType, TypeOf, Type, Props, NullableProps };
6162
export { ByteSizeValue } from './byte_size_value';
6263
export { SchemaTypeError, ValidationError } from './errors';
6364
export { isConfigSchema } from './typeguards';

packages/kbn-config-schema/src/types/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export { LiteralType } from './literal_type';
2929
export { MaybeType } from './maybe_type';
3030
export { MapOfOptions, MapOfType } from './map_type';
3131
export { NumberOptions, NumberType } from './number_type';
32-
export { ObjectType, ObjectTypeOptions, Props, TypeOf } from './object_type';
32+
export { ObjectType, ObjectTypeOptions, Props, NullableProps, TypeOf } from './object_type';
3333
export { RecordOfOptions, RecordOfType } from './record_type';
3434
export { StreamType } from './stream_type';
3535
export { StringOptions, StringType } from './string_type';

packages/kbn-config-schema/src/types/object_type.test.ts

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* under the License.
1818
*/
1919

20+
import { expectType } from 'tsd';
2021
import { schema } from '..';
2122
import { TypeOf } from './object_type';
2223

@@ -360,17 +361,142 @@ test('handles optional properties', () => {
360361

361362
type SchemaType = TypeOf<typeof type>;
362363

363-
let foo: SchemaType = {
364+
expectType<SchemaType>({
364365
required: 'foo',
365-
};
366-
foo = {
366+
});
367+
expectType<SchemaType>({
367368
required: 'hello',
368369
optional: undefined,
369-
};
370-
foo = {
370+
});
371+
expectType<SchemaType>({
371372
required: 'hello',
372373
optional: 'bar',
373-
};
374+
});
375+
});
376+
377+
describe('#extends', () => {
378+
it('allows to extend an existing schema by adding new properties', () => {
379+
const origin = schema.object({
380+
initial: schema.string(),
381+
});
382+
383+
const extended = origin.extends({
384+
added: schema.number(),
385+
});
386+
387+
expect(() => {
388+
extended.validate({ initial: 'foo' });
389+
}).toThrowErrorMatchingInlineSnapshot(
390+
`"[added]: expected value of type [number] but got [undefined]"`
391+
);
392+
393+
expect(() => {
394+
extended.validate({ initial: 'foo', added: 42 });
395+
}).not.toThrowError();
374396

375-
expect(foo).toBeDefined();
397+
expectType<TypeOf<typeof extended>>({
398+
added: 12,
399+
initial: 'foo',
400+
});
401+
});
402+
403+
it('allows to extend an existing schema by removing properties', () => {
404+
const origin = schema.object({
405+
string: schema.string(),
406+
number: schema.number(),
407+
});
408+
409+
const extended = origin.extends({ number: undefined });
410+
411+
expect(() => {
412+
extended.validate({ string: 'foo', number: 12 });
413+
}).toThrowErrorMatchingInlineSnapshot(`"[number]: definition for this key is missing"`);
414+
415+
expect(() => {
416+
extended.validate({ string: 'foo' });
417+
}).not.toThrowError();
418+
419+
expectType<TypeOf<typeof extended>>({
420+
string: 'foo',
421+
});
422+
});
423+
424+
it('allows to extend an existing schema by overriding an existing properties', () => {
425+
const origin = schema.object({
426+
string: schema.string(),
427+
mutated: schema.number(),
428+
});
429+
430+
const extended = origin.extends({
431+
mutated: schema.string(),
432+
});
433+
434+
expect(() => {
435+
extended.validate({ string: 'foo', mutated: 12 });
436+
}).toThrowErrorMatchingInlineSnapshot(
437+
`"[mutated]: expected value of type [string] but got [number]"`
438+
);
439+
440+
expect(() => {
441+
extended.validate({ string: 'foo', mutated: 'bar' });
442+
}).not.toThrowError();
443+
444+
expectType<TypeOf<typeof extended>>({
445+
string: 'foo',
446+
mutated: 'bar',
447+
});
448+
});
449+
450+
it('properly infer the type from optional properties', () => {
451+
const origin = schema.object({
452+
original: schema.maybe(schema.string()),
453+
mutated: schema.maybe(schema.number()),
454+
removed: schema.maybe(schema.string()),
455+
});
456+
457+
const extended = origin.extends({
458+
removed: undefined,
459+
mutated: schema.string(),
460+
});
461+
462+
expect(() => {
463+
extended.validate({ original: 'foo' });
464+
}).toThrowErrorMatchingInlineSnapshot(
465+
`"[mutated]: expected value of type [string] but got [undefined]"`
466+
);
467+
expect(() => {
468+
extended.validate({ original: 'foo' });
469+
}).toThrowErrorMatchingInlineSnapshot(
470+
`"[mutated]: expected value of type [string] but got [undefined]"`
471+
);
472+
expect(() => {
473+
extended.validate({ original: 'foo', mutated: 'bar' });
474+
}).not.toThrowError();
475+
476+
expectType<TypeOf<typeof extended>>({
477+
original: 'foo',
478+
mutated: 'bar',
479+
});
480+
expectType<TypeOf<typeof extended>>({
481+
mutated: 'bar',
482+
});
483+
});
484+
485+
it(`allows to override the original schema's options`, () => {
486+
const origin = schema.object(
487+
{
488+
initial: schema.string(),
489+
},
490+
{ defaultValue: { initial: 'foo' } }
491+
);
492+
493+
const extended = origin.extends(
494+
{
495+
added: schema.number(),
496+
},
497+
{ defaultValue: { initial: 'bar', added: 42 } }
498+
);
499+
500+
expect(extended.validate(undefined)).toEqual({ initial: 'bar', added: 42 });
501+
});
376502
});

packages/kbn-config-schema/src/types/object_type.ts

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { ValidationError } from '../errors';
2424

2525
export type Props = Record<string, Type<any>>;
2626

27+
export type NullableProps = Record<string, Type<any> | undefined | null>;
28+
2729
export type TypeOf<RT extends Type<any>> = RT['type'];
2830

2931
type OptionalProperties<Base extends Props> = Pick<
@@ -47,6 +49,24 @@ export type ObjectResultType<P extends Props> = Readonly<
4749
{ [K in keyof RequiredProperties<P>]: TypeOf<P[K]> }
4850
>;
4951

52+
type DefinedProperties<Base extends NullableProps> = Pick<
53+
Base,
54+
{
55+
[Key in keyof Base]: undefined extends Base[Key] ? never : null extends Base[Key] ? never : Key;
56+
}[keyof Base]
57+
>;
58+
59+
type ExtendedProps<P extends Props, NP extends NullableProps> = Omit<P, keyof NP> &
60+
{ [K in keyof DefinedProperties<NP>]: NP[K] };
61+
62+
type ExtendedObjectType<P extends Props, NP extends NullableProps> = ObjectType<
63+
ExtendedProps<P, NP>
64+
>;
65+
66+
type ExtendedObjectTypeOptions<P extends Props, NP extends NullableProps> = ObjectTypeOptions<
67+
ExtendedProps<P, NP>
68+
>;
69+
5070
interface UnknownOptions {
5171
/**
5272
* Options for dealing with unknown keys:
@@ -61,10 +81,13 @@ export type ObjectTypeOptions<P extends Props = any> = TypeOptions<ObjectResultT
6181
UnknownOptions;
6282

6383
export class ObjectType<P extends Props = any> extends Type<ObjectResultType<P>> {
64-
private props: Record<string, AnySchema>;
84+
private props: P;
85+
private options: ObjectTypeOptions<P>;
86+
private propSchemas: Record<string, AnySchema>;
6587

66-
constructor(props: P, { unknowns = 'forbid', ...typeOptions }: ObjectTypeOptions<P> = {}) {
88+
constructor(props: P, options: ObjectTypeOptions<P> = {}) {
6789
const schemaKeys = {} as Record<string, AnySchema>;
90+
const { unknowns = 'forbid', ...typeOptions } = options;
6891
for (const [key, value] of Object.entries(props)) {
6992
schemaKeys[key] = value.getSchema();
7093
}
@@ -77,7 +100,93 @@ export class ObjectType<P extends Props = any> extends Type<ObjectResultType<P>>
77100
.options({ stripUnknown: { objects: unknowns === 'ignore' } });
78101

79102
super(schema, typeOptions);
80-
this.props = schemaKeys;
103+
this.props = props;
104+
this.propSchemas = schemaKeys;
105+
this.options = options;
106+
}
107+
108+
/**
109+
* Return a new `ObjectType` instance extended with given `newProps` properties.
110+
* Original properties can be deleted from the copy by passing a `null` or `undefined` value for the key.
111+
*
112+
* @example
113+
* How to add a new key to an object schema
114+
* ```ts
115+
* const origin = schema.object({
116+
* initial: schema.string(),
117+
* });
118+
*
119+
* const extended = origin.extends({
120+
* added: schema.number(),
121+
* });
122+
* ```
123+
*
124+
* How to remove an existing key from an object schema
125+
* ```ts
126+
* const origin = schema.object({
127+
* initial: schema.string(),
128+
* toRemove: schema.number(),
129+
* });
130+
*
131+
* const extended = origin.extends({
132+
* toRemove: undefined,
133+
* });
134+
* ```
135+
*
136+
* How to override the schema's options
137+
* ```ts
138+
* const origin = schema.object({
139+
* initial: schema.string(),
140+
* }, { defaultValue: { initial: 'foo' }});
141+
*
142+
* const extended = origin.extends({
143+
* added: schema.number(),
144+
* }, { defaultValue: { initial: 'foo', added: 'bar' }});
145+
*
146+
* @remarks
147+
* `extends` only support extending first-level properties. It's currently not possible to perform deep/nested extensions.
148+
*
149+
* ```ts
150+
* const origin = schema.object({
151+
* foo: schema.string(),
152+
* nested: schema.object({
153+
* a: schema.string(),
154+
* b: schema.string(),
155+
* }),
156+
* });
157+
*
158+
* const extended = origin.extends({
159+
* nested: schema.object({
160+
* c: schema.string(),
161+
* }),
162+
* });
163+
*
164+
* // TypeOf<typeof extended> is `{ foo: string; nested: { c: string } }`
165+
* ```
166+
*/
167+
public extends<NP extends NullableProps>(
168+
newProps: NP,
169+
newOptions?: ExtendedObjectTypeOptions<P, NP>
170+
): ExtendedObjectType<P, NP> {
171+
const extendedProps = Object.entries({
172+
...this.props,
173+
...newProps,
174+
}).reduce((memo, [key, value]) => {
175+
if (value !== null && value !== undefined) {
176+
return {
177+
...memo,
178+
[key]: value,
179+
};
180+
}
181+
return memo;
182+
}, {} as ExtendedProps<P, NP>);
183+
184+
const extendedOptions = {
185+
...this.options,
186+
...newOptions,
187+
} as ExtendedObjectTypeOptions<P, NP>;
188+
189+
return new ObjectType(extendedProps, extendedOptions);
81190
}
82191

83192
protected handleError(type: string, { reason, value }: Record<string, any>) {
@@ -95,10 +204,10 @@ export class ObjectType<P extends Props = any> extends Type<ObjectResultType<P>>
95204
}
96205

97206
validateKey(key: string, value: any) {
98-
if (!this.props[key]) {
207+
if (!this.propSchemas[key]) {
99208
throw new Error(`${key} is not a valid part of this schema`);
100209
}
101-
const { value: validatedValue, error } = this.props[key].validate(value);
210+
const { value: validatedValue, error } = this.propSchemas[key].validate(value);
102211
if (error) {
103212
throw new ValidationError(error as any, key);
104213
}

0 commit comments

Comments
 (0)