Skip to content

Commit

Permalink
Merge branch 'main' into osquery-flaky-6
Browse files Browse the repository at this point in the history
  • Loading branch information
tomsonpl authored Nov 8, 2023
2 parents 1abd652 + 7449cdb commit 53ca76d
Show file tree
Hide file tree
Showing 189 changed files with 2,924 additions and 2,754 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { z } from "zod";
import { requiredOptional, isValidDateMath } from "@kbn/zod-helpers"
import { requiredOptional, isValidDateMath, ArrayFromString, BooleanFromString } from "@kbn/zod-helpers"

{{> disclaimer}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,17 @@
{{~/if~}}

{{~#if (eq type "array")}}
z.preprocess(
(value: unknown) => (typeof value === "string") ? value === '' ? [] : value.split(",") : value,
z.array({{~> zod_schema_item items ~}})
)
ArrayFromString({{~> zod_schema_item items ~}})
{{~#if minItems}}.min({{minItems}}){{/if~}}
{{~#if maxItems}}.max({{maxItems}}){{/if~}}
{{~#if (eq requiredBool false)}}.optional(){{/if~}}
{{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}}
{{~/if~}}

{{~#if (eq type "boolean")}}
z.preprocess(
(value: unknown) => (typeof value === "boolean") ? String(value) : value,
z.enum(["true", "false"])
{{~#if (defined default)}}.default("{{{toJSON default}}}"){{/if~}}
.transform((value) => value === "true")
)
BooleanFromString
{{~#if (eq requiredBool false)}}.optional(){{/if~}}
{{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}}
{{~/if~}}

{{~#if (eq type "string")}}
Expand Down
3 changes: 3 additions & 0 deletions packages/kbn-zod-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
* Side Public License, v 1.
*/

export * from './src/array_from_string';
export * from './src/boolean_from_string';
export * from './src/expect_parse_error';
export * from './src/expect_parse_success';
export * from './src/is_valid_date_math';
export * from './src/required_optional';
export * from './src/safe_parse_result';
export * from './src/stringify_zod_error';
34 changes: 34 additions & 0 deletions packages/kbn-zod-helpers/src/array_from_string.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 { ArrayFromString } from './array_from_string';
import * as z from 'zod';

describe('ArrayFromString', () => {
const itemsSchema = z.string();

it('should return an array when input is a string', () => {
const result = ArrayFromString(itemsSchema).parse('a,b,c');
expect(result).toEqual(['a', 'b', 'c']);
});

it('should return an empty array when input is an empty string', () => {
const result = ArrayFromString(itemsSchema).parse('');
expect(result).toEqual([]);
});

it('should return the input as is when it is not a string', () => {
const input = ['a', 'b', 'c'];
const result = ArrayFromString(itemsSchema).parse(input);
expect(result).toEqual(input);
});

it('should throw an error when input is not a string or an array', () => {
expect(() => ArrayFromString(itemsSchema).parse(123)).toThrow();
});
});
24 changes: 24 additions & 0 deletions packages/kbn-zod-helpers/src/array_from_string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 * as z from 'zod';

/**
* This is a helper schema to convert comma separated strings to arrays. Useful
* for processing query params.
*
* @param schema Array items schema
* @returns Array schema that accepts a comma-separated string as input
*/
export function ArrayFromString<T extends z.ZodTypeAny>(schema: T) {
return z.preprocess(
(value: unknown) =>
typeof value === 'string' ? (value === '' ? [] : value.split(',')) : value,
z.array(schema)
);
}
32 changes: 32 additions & 0 deletions packages/kbn-zod-helpers/src/boolean_from_string.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 { BooleanFromString } from './boolean_from_string';

describe('BooleanFromString', () => {
it('should return true when input is "true"', () => {
expect(BooleanFromString.parse('true')).toBe(true);
});

it('should return false when input is "false"', () => {
expect(BooleanFromString.parse('false')).toBe(false);
});

it('should return true when input is true', () => {
expect(BooleanFromString.parse(true)).toBe(true);
});

it('should return false when input is false', () => {
expect(BooleanFromString.parse(false)).toBe(false);
});

it('should throw an error when input is not a boolean or "true" or "false"', () => {
expect(() => BooleanFromString.parse('not a boolean')).toThrow();
expect(() => BooleanFromString.parse(42)).toThrow();
});
});
24 changes: 24 additions & 0 deletions packages/kbn-zod-helpers/src/boolean_from_string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 * as z from 'zod';

/**
* This is a helper schema to convert a boolean string ("true" or "false") to a
* boolean. Useful for processing query params.
*
* Accepts "true" or "false" as strings, or a boolean.
*/
export const BooleanFromString = z
.enum(['true', 'false'])
.or(z.boolean())
.transform((value) => {
if (typeof value === 'boolean') {
return value;
}
return value === 'true';
});
7 changes: 6 additions & 1 deletion packages/kbn-zod-helpers/src/expect_parse_success.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
*/

import type { SafeParseReturnType, SafeParseSuccess } from 'zod';
import { stringifyZodError } from './stringify_zod_error';

export function expectParseSuccess<Input, Output>(
result: SafeParseReturnType<Input, Output>
): asserts result is SafeParseSuccess<Output> {
expect(result.success).toEqual(true);
if (!result.success) {
// We are throwing here instead of using assertions because we want to show
// the stringified error to assist with debugging.
throw new Error(`Expected parse success, got error: ${stringifyZodError(result.error)}`);
}
}
28 changes: 28 additions & 0 deletions packages/kbn-zod-helpers/src/safe_parse_result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 * as z from 'zod';

/**
* Safely parse a payload against a schema, returning the output or undefined.
* This method does not throw validation errors and is useful for validating
* optional objects when we don't care about errors.
*
* @param payload Schema payload
* @param schema Validation schema
* @returns Schema output or undefined
*/
export function safeParseResult<T extends z.ZodTypeAny>(
payload: unknown,
schema: T
): T['_output'] | undefined {
const result = schema.safeParse(payload);
if (result.success) {
return result.data;
}
}
45 changes: 35 additions & 10 deletions packages/kbn-zod-helpers/src/stringify_zod_error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,41 @@
* Side Public License, v 1.
*/

import { ZodError } from 'zod';
import { ZodError, ZodIssue } from 'zod';

const MAX_ERRORS = 5;

export function stringifyZodError(err: ZodError<any>) {
return err.issues
.map((issue) => {
// If the path is empty, the error is for the root object
if (issue.path.length === 0) {
return issue.message;
}
return `${issue.path.join('.')}: ${issue.message}`;
})
.join(', ');
const errorMessages: string[] = [];

const issues = err.issues;

// Recursively traverse all issues
while (issues.length > 0) {
const issue = issues.shift()!;

// If the issue is an invalid union, we need to traverse all issues in the
// "unionErrors" array
if (issue.code === 'invalid_union') {
issues.push(...issue.unionErrors.flatMap((e) => e.issues));
continue;
}

errorMessages.push(stringifyIssue(issue));
}

const extraErrorCount = errorMessages.length - MAX_ERRORS;
if (extraErrorCount > 0) {
errorMessages.splice(MAX_ERRORS);
errorMessages.push(`and ${extraErrorCount} more`);
}

return errorMessages.join(', ');
}

function stringifyIssue(issue: ZodIssue) {
if (issue.path.length === 0) {
return issue.message;
}
return `${issue.path.join('.')}: ${issue.message}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { createLogExplorerProfileCustomizations } from '../../customizations/log
import { createPropertyGetProxy } from '../../utils/proxies';
import { LogExplorerProfileContext } from '../../state_machines/log_explorer_profile';
import { LogExplorerStartDeps } from '../../types';
import { LogExplorerCustomizations } from './types';

export interface CreateLogExplorerArgs {
core: CoreStart;
Expand All @@ -29,6 +30,7 @@ export interface LogExplorerStateContainer {
}

export interface LogExplorerProps {
customizations?: LogExplorerCustomizations;
scopedHistory: ScopedHistory;
state$?: BehaviorSubject<LogExplorerStateContainer>;
}
Expand All @@ -44,10 +46,10 @@ export const createLogExplorer = ({ core, plugins }: CreateLogExplorerArgs) => {
uiSettings: createUiSettingsServiceProxy(core.uiSettings),
};

return ({ scopedHistory, state$ }: LogExplorerProps) => {
return ({ customizations = {}, scopedHistory, state$ }: LogExplorerProps) => {
const logExplorerCustomizations = useMemo(
() => [createLogExplorerProfileCustomizations({ core, plugins, state$ })],
[state$]
() => [createLogExplorerProfileCustomizations({ core, customizations, plugins, state$ })],
[customizations, state$]
);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { DataTableRecord } from '@kbn/discover-utils/types';

export type RenderPreviousContent = () => React.ReactNode;

export interface LogExplorerFlyoutContentProps {
doc: DataTableRecord;
}

export type FlyoutRenderContent = (
renderPreviousContent: RenderPreviousContent,
props: LogExplorerFlyoutContentProps
) => React.ReactNode;

export interface LogExplorerCustomizations {
flyout?: {
renderContent?: FlyoutRenderContent;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,40 @@
* 2.0.
*/

import React from 'react';
import React, { useCallback } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FlyoutDetail } from '../components/flyout_detail/flyout_detail';
import { FlyoutProps } from '../components/flyout_detail';
import { useLogExplorerCustomizationsContext } from '../hooks/use_log_explorer_customizations';

export const CustomFlyoutContent = ({
actions,
dataView,
doc,
renderDefaultContent,
}: FlyoutProps) => {
const { flyout } = useLogExplorerCustomizationsContext();

const renderPreviousContent = useCallback(
() => (
<>
{/* Apply custom Log Explorer detail */}
<EuiFlexItem>
<FlyoutDetail actions={actions} dataView={dataView} doc={doc} />
</EuiFlexItem>
</>
),
[actions, dataView, doc]
);

const content = flyout?.renderContent
? flyout?.renderContent(renderPreviousContent, { doc })
: renderPreviousContent();

return (
<EuiFlexGroup direction="column">
{/* Apply custom Log Explorer detail */}
<EuiFlexItem>
<FlyoutDetail actions={actions} dataView={dataView} doc={doc} />
</EuiFlexItem>
{content}
{/* Restore default content */}
<EuiFlexItem>{renderDefaultContent()}</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Loading

0 comments on commit 53ca76d

Please sign in to comment.