Skip to content

Commit

Permalink
Merge branch 'master' of github.com:elastic/kibana into qa-slack-send…
Browse files Browse the repository at this point in the history
…-on-fail
  • Loading branch information
wayneseymour committed Jun 4, 2020
2 parents 0a42bbe + f7739b5 commit e073190
Show file tree
Hide file tree
Showing 144 changed files with 7,035 additions and 608 deletions.
2 changes: 1 addition & 1 deletion docs/apm/advanced-queries.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ or, to only show transactions that are slower than a specified time threshold.
==== Example APM app queries

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

When querying in the APM app, you're merely searching and selecting data from fields in Elasticsearch documents.
Expand Down
18 changes: 9 additions & 9 deletions docs/apm/service-maps.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ Machine learning jobs can be created to calculate anomaly scores on APM transact
When these jobs are active, service maps will display a color-coded anomaly indicator based on the detected anomaly score:

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

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

[horizontal]
Go Agent:: >= v1.7.0
Java Agent:: >= v1.13.0
.NET Agent:: >= v1.3.0
Node.js Agent:: >= v3.6.0
Python Agent:: >= v5.5.0
Ruby Agent:: >= v3.6.0
Real User Monitoring (RUM) Agent:: >= v4.7.0
Go Agent:: v1.7.0
Java Agent:: v1.13.0
.NET Agent:: v1.3.0
Node.js Agent:: v3.6.0
Python Agent:: v5.5.0
Ruby Agent:: v3.6.0
Real User Monitoring (RUM) Agent:: v4.7.0
3 changes: 2 additions & 1 deletion packages/kbn-config-schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"kbn:bootstrap": "yarn build"
},
"devDependencies": {
"typescript": "3.7.2"
"typescript": "3.7.2",
"tsd": "^0.7.4"
},
"peerDependencies": {
"joi": "^13.5.2",
Expand Down
3 changes: 2 additions & 1 deletion packages/kbn-config-schema/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
ObjectType,
ObjectTypeOptions,
Props,
NullableProps,
RecordOfOptions,
RecordOfType,
StringOptions,
Expand All @@ -57,7 +58,7 @@ import {
StreamType,
} from './types';

export { ObjectType, TypeOf, Type };
export { ObjectType, TypeOf, Type, Props, NullableProps };
export { ByteSizeValue } from './byte_size_value';
export { SchemaTypeError, ValidationError } from './errors';
export { isConfigSchema } from './typeguards';
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-config-schema/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export { LiteralType } from './literal_type';
export { MaybeType } from './maybe_type';
export { MapOfOptions, MapOfType } from './map_type';
export { NumberOptions, NumberType } from './number_type';
export { ObjectType, ObjectTypeOptions, Props, TypeOf } from './object_type';
export { ObjectType, ObjectTypeOptions, Props, NullableProps, TypeOf } from './object_type';
export { RecordOfOptions, RecordOfType } from './record_type';
export { StreamType } from './stream_type';
export { StringOptions, StringType } from './string_type';
Expand Down
140 changes: 133 additions & 7 deletions packages/kbn-config-schema/src/types/object_type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/

import { expectType } from 'tsd';
import { schema } from '..';
import { TypeOf } from './object_type';

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

type SchemaType = TypeOf<typeof type>;

let foo: SchemaType = {
expectType<SchemaType>({
required: 'foo',
};
foo = {
});
expectType<SchemaType>({
required: 'hello',
optional: undefined,
};
foo = {
});
expectType<SchemaType>({
required: 'hello',
optional: 'bar',
};
});
});

describe('#extends', () => {
it('allows to extend an existing schema by adding new properties', () => {
const origin = schema.object({
initial: schema.string(),
});

const extended = origin.extends({
added: schema.number(),
});

expect(() => {
extended.validate({ initial: 'foo' });
}).toThrowErrorMatchingInlineSnapshot(
`"[added]: expected value of type [number] but got [undefined]"`
);

expect(() => {
extended.validate({ initial: 'foo', added: 42 });
}).not.toThrowError();

expect(foo).toBeDefined();
expectType<TypeOf<typeof extended>>({
added: 12,
initial: 'foo',
});
});

it('allows to extend an existing schema by removing properties', () => {
const origin = schema.object({
string: schema.string(),
number: schema.number(),
});

const extended = origin.extends({ number: undefined });

expect(() => {
extended.validate({ string: 'foo', number: 12 });
}).toThrowErrorMatchingInlineSnapshot(`"[number]: definition for this key is missing"`);

expect(() => {
extended.validate({ string: 'foo' });
}).not.toThrowError();

expectType<TypeOf<typeof extended>>({
string: 'foo',
});
});

it('allows to extend an existing schema by overriding an existing properties', () => {
const origin = schema.object({
string: schema.string(),
mutated: schema.number(),
});

const extended = origin.extends({
mutated: schema.string(),
});

expect(() => {
extended.validate({ string: 'foo', mutated: 12 });
}).toThrowErrorMatchingInlineSnapshot(
`"[mutated]: expected value of type [string] but got [number]"`
);

expect(() => {
extended.validate({ string: 'foo', mutated: 'bar' });
}).not.toThrowError();

expectType<TypeOf<typeof extended>>({
string: 'foo',
mutated: 'bar',
});
});

it('properly infer the type from optional properties', () => {
const origin = schema.object({
original: schema.maybe(schema.string()),
mutated: schema.maybe(schema.number()),
removed: schema.maybe(schema.string()),
});

const extended = origin.extends({
removed: undefined,
mutated: schema.string(),
});

expect(() => {
extended.validate({ original: 'foo' });
}).toThrowErrorMatchingInlineSnapshot(
`"[mutated]: expected value of type [string] but got [undefined]"`
);
expect(() => {
extended.validate({ original: 'foo' });
}).toThrowErrorMatchingInlineSnapshot(
`"[mutated]: expected value of type [string] but got [undefined]"`
);
expect(() => {
extended.validate({ original: 'foo', mutated: 'bar' });
}).not.toThrowError();

expectType<TypeOf<typeof extended>>({
original: 'foo',
mutated: 'bar',
});
expectType<TypeOf<typeof extended>>({
mutated: 'bar',
});
});

it(`allows to override the original schema's options`, () => {
const origin = schema.object(
{
initial: schema.string(),
},
{ defaultValue: { initial: 'foo' } }
);

const extended = origin.extends(
{
added: schema.number(),
},
{ defaultValue: { initial: 'bar', added: 42 } }
);

expect(extended.validate(undefined)).toEqual({ initial: 'bar', added: 42 });
});
});
119 changes: 114 additions & 5 deletions packages/kbn-config-schema/src/types/object_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { ValidationError } from '../errors';

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

export type NullableProps = Record<string, Type<any> | undefined | null>;

export type TypeOf<RT extends Type<any>> = RT['type'];

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

type DefinedProperties<Base extends NullableProps> = Pick<
Base,
{
[Key in keyof Base]: undefined extends Base[Key] ? never : null extends Base[Key] ? never : Key;
}[keyof Base]
>;

type ExtendedProps<P extends Props, NP extends NullableProps> = Omit<P, keyof NP> &
{ [K in keyof DefinedProperties<NP>]: NP[K] };

type ExtendedObjectType<P extends Props, NP extends NullableProps> = ObjectType<
ExtendedProps<P, NP>
>;

type ExtendedObjectTypeOptions<P extends Props, NP extends NullableProps> = ObjectTypeOptions<
ExtendedProps<P, NP>
>;

interface UnknownOptions {
/**
* Options for dealing with unknown keys:
Expand All @@ -61,10 +81,13 @@ export type ObjectTypeOptions<P extends Props = any> = TypeOptions<ObjectResultT
UnknownOptions;

export class ObjectType<P extends Props = any> extends Type<ObjectResultType<P>> {
private props: Record<string, AnySchema>;
private props: P;
private options: ObjectTypeOptions<P>;
private propSchemas: Record<string, AnySchema>;

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

super(schema, typeOptions);
this.props = schemaKeys;
this.props = props;
this.propSchemas = schemaKeys;
this.options = options;
}

/**
* Return a new `ObjectType` instance extended with given `newProps` properties.
* Original properties can be deleted from the copy by passing a `null` or `undefined` value for the key.
*
* @example
* How to add a new key to an object schema
* ```ts
* const origin = schema.object({
* initial: schema.string(),
* });
*
* const extended = origin.extends({
* added: schema.number(),
* });
* ```
*
* How to remove an existing key from an object schema
* ```ts
* const origin = schema.object({
* initial: schema.string(),
* toRemove: schema.number(),
* });
*
* const extended = origin.extends({
* toRemove: undefined,
* });
* ```
*
* How to override the schema's options
* ```ts
* const origin = schema.object({
* initial: schema.string(),
* }, { defaultValue: { initial: 'foo' }});
*
* const extended = origin.extends({
* added: schema.number(),
* }, { defaultValue: { initial: 'foo', added: 'bar' }});
*
* @remarks
* `extends` only support extending first-level properties. It's currently not possible to perform deep/nested extensions.
*
* ```ts
* const origin = schema.object({
* foo: schema.string(),
* nested: schema.object({
* a: schema.string(),
* b: schema.string(),
* }),
* });
*
* const extended = origin.extends({
* nested: schema.object({
* c: schema.string(),
* }),
* });
*
* // TypeOf<typeof extended> is `{ foo: string; nested: { c: string } }`
* ```
*/
public extends<NP extends NullableProps>(
newProps: NP,
newOptions?: ExtendedObjectTypeOptions<P, NP>
): ExtendedObjectType<P, NP> {
const extendedProps = Object.entries({
...this.props,
...newProps,
}).reduce((memo, [key, value]) => {
if (value !== null && value !== undefined) {
return {
...memo,
[key]: value,
};
}
return memo;
}, {} as ExtendedProps<P, NP>);

const extendedOptions = {
...this.options,
...newOptions,
} as ExtendedObjectTypeOptions<P, NP>;

return new ObjectType(extendedProps, extendedOptions);
}

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

validateKey(key: string, value: any) {
if (!this.props[key]) {
if (!this.propSchemas[key]) {
throw new Error(`${key} is not a valid part of this schema`);
}
const { value: validatedValue, error } = this.props[key].validate(value);
const { value: validatedValue, error } = this.propSchemas[key].validate(value);
if (error) {
throw new ValidationError(error as any, key);
}
Expand Down
Loading

0 comments on commit e073190

Please sign in to comment.