From 5193d4569838a07b0673456f72a00b6b541cb96b Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 7 Jun 2024 17:57:44 +0200 Subject: [PATCH 01/10] [ES|QL] Allows to comment out the current line with the keyboard (#184637) ## Summary Closes https://github.com/elastic/kibana/issues/184521 It allows the users to comment out the current line by typing `CMD + /`, bringing it closer to what a developer would expect. ![meow](https://github.com/elastic/kibana/assets/17003240/11199afa-b7d6-4751-b6fa-7bf7389c04ef) --- .../src/text_based_languages_editor.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx index c4ecf644a6b60..65516e498ba15 100644 --- a/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx +++ b/packages/kbn-text-based-editor/src/text_based_languages_editor.tsx @@ -237,6 +237,28 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ } }, [language, onTextLangQuerySubmit, abortController, isQueryLoading, allowQueryCancellation]); + const onCommentLine = useCallback(() => { + const currentPosition = editor1.current?.getPosition(); + const lineNumber = currentPosition?.lineNumber; + if (lineNumber) { + const lineContent = editorModel.current?.getLineContent(lineNumber) ?? ''; + const hasComment = lineContent?.startsWith('//'); + const commentedLine = hasComment ? lineContent?.replace('//', '') : `//${lineContent}`; + // executeEdits allows to keep edit in history + editor1.current?.executeEdits('comment', [ + { + range: { + startLineNumber: lineNumber, + startColumn: 0, + endLineNumber: lineNumber, + endColumn: (lineContent?.length ?? 0) + 1, + }, + text: commentedLine, + }, + ]); + } + }, []); + useEffect(() => { if (!isLoading) setIsQueryLoading(false); }, [isLoading]); @@ -903,6 +925,13 @@ export const TextBasedLanguagesEditor = memo(function TextBasedLanguagesEditor({ onQuerySubmit ); + // on CMD/CTRL + / comment out the entire line + editor.addCommand( + // eslint-disable-next-line no-bitwise + monaco.KeyMod.CtrlCmd | monaco.KeyCode.Slash, + onCommentLine + ); + setMeasuredEditorWidth(editor.getLayoutInfo().width); setMeasuredContentWidth(editor.getContentWidth()); From 825efd0ef3e8940a8d40e2f9c99986a5cd1276a3 Mon Sep 17 00:00:00 2001 From: elena-shostak <165678770+elena-shostak@users.noreply.github.com> Date: Fri, 7 Jun 2024 18:00:42 +0200 Subject: [PATCH 02/10] [Roles] Reordered sections in role management page (#184924) ## Summary Reordered sections in role management page. [As agreed](https://github.com/elastic/kibana/issues/182984#issuecomment-2149886052) the order is the following: - Cluster - Remote Cluster - Run As - Index - Remote Index ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) __Fixes: https://github.com/elastic/kibana/issues/182984__ --- .../es/elasticsearch_privileges.tsx | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx index 1ff2ede4c575c..44a4dd68816ad 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx @@ -106,6 +106,39 @@ export class ElasticsearchPrivileges extends Component { + {buildFlavor === 'traditional' && canUseRemoteClusters && ( + <> + +

+ +

+
+ + +

+ + {this.learnMore(docLinks.links.security.clusterPrivileges)} +

+
+ + + + + )} {buildFlavor === 'traditional' && ( <> @@ -222,40 +255,6 @@ export class ElasticsearchPrivileges extends Component { /> )} - {buildFlavor === 'traditional' && canUseRemoteClusters && ( - <> - - - - -

- -

-
- - -

- - {this.learnMore(docLinks.links.security.clusterPrivileges)} -

-
- - - )} ); }; From aefcfd03f092fd34ee3bcfe48cb96319afb63dac Mon Sep 17 00:00:00 2001 From: Cee Chen <549407+cee-chen@users.noreply.github.com> Date: Fri, 7 Jun 2024 09:17:07 -0700 Subject: [PATCH 03/10] [EuiCheckbox] Remove `inList` type checkboxes (#184881) ## Summary The `type="inList"` prop is about to be deprecated shortly by EUI (https://github.com/elastic/eui/pull/7814), so this is being removed ahead of time. The same style already gets applied when no `label` prop is passed, so no visual regressions should occur (hence the removal of an unnecessary prop). That being said, we would appreciate a quick smoke check of the affected tables by CODEOWNERs to ensure your selection checkboxes look the same as before/unbroken. Thank you! --- .../sections/home/index_list/index_table/index_table.js | 2 -- .../components/custom_selection_table/custom_selection_table.js | 2 -- .../public/crud_app/sections/job_list/job_table/job_table.js | 2 -- 3 files changed, 6 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js index 8ad11dbdbeb80..a6c8ca92a70b2 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js +++ b/x-pack/plugins/index_management/public/application/sections/home/index_list/index_table/index_table.js @@ -429,7 +429,6 @@ export class IndexTable extends Component { > { @@ -696,7 +695,6 @@ export class IndexTable extends Component { id="selectAllIndexes" checked={this.areAllItemsSelected()} onChange={this.toggleAll} - type="inList" aria-label={i18n.translate( 'xpack.idxMgmt.indexTable.selectAllIndicesAriaLabel', { diff --git a/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js b/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js index d1597cce0fb08..2846b4de6f6ed 100644 --- a/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js +++ b/x-pack/plugins/ml/public/application/components/custom_selection_table/custom_selection_table.js @@ -197,7 +197,6 @@ export function CustomSelectionTable({ label={mobile ? selectAll : null} checked={areAllItemsSelected()} onChange={toggleAll} - type={mobile ? null : 'inList'} /> ); } @@ -283,7 +282,6 @@ export function CustomSelectionTable({ data-test-subj={`${item[tableItemId]}-checkbox`} checked={isItemSelected(item[tableItemId])} onChange={() => toggleItem(item[tableItemId])} - type="inList" /> )} {singleSelection && ( diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js index 5a30f9e6ec5f3..16ec3dd11c20e 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.js @@ -305,7 +305,6 @@ export class JobTable extends Component { { @@ -393,7 +392,6 @@ export class JobTable extends Component { id="selectAllJobsCheckbox" checked={this.areAllItemsSelected()} onChange={this.toggleAll} - type="inList" aria-label={i18n.translate('xpack.rollupJobs.jobTable.selectAllRows', { defaultMessage: 'Select all rows', })} From 79ab4de52f36b1cbe5c74ee0353516f457995512 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 7 Jun 2024 11:18:44 -0500 Subject: [PATCH 04/10] Add serverless pointer compression builds (#184675) This introduces a Node.js build with pointer compression enabled to our serverless artifacts. Pointer compression is a v8 feature that can reduce memory consumption. Details can be found at https://v8.dev/blog/pointer-compression. Impact depends on the workload. For idle projects we're seeing runtime memory drop by ~33%. ### Implementation Previously, build archives were created based on platform and CPU architecture. Archives can now also be created with a variant. In this case we're introducing a `serverless` variant. In the future we can split this further per project type if needed. The serverless variant installs two versions of Node.js: the existing glibc-217 compatible version and the new pointer compression version. Build scripts for the pointer compression version were introduced at https://github.com/elastic/kibana-custom-nodejs-builds/pull/18. kibana-serverless will default to using the pointer compression version. ### Tradeoffs - Pointer compression builds have a heap limit of 4Gb. To mitigate this we continue installing the glibc-217 compatible version and can swap runtimes if a larger heap is needed. - Pointer compression builds are not official Node.js builds - Unofficial builds have been [functional since Node 14](https://github.com/nodejs/unofficial-builds/commits/main/recipes/x64-pointer-compression) - We're already running an unofficial build to support operating systems running older versions of glibc Additional test runs: https://buildkite.com/elastic/kibana-artifacts-snapshot/builds/4366 https://buildkite.com/elastic/appex-qa-serverless-kibana-ftr-tests/builds/1884 Closes https://github.com/elastic/kibana/issues/96062 --- .../scripts/steps/artifacts/docker_image.sh | 13 +- src/dev/build/args.test.ts | 8 + src/dev/build/args.ts | 5 +- src/dev/build/build_distributables.ts | 4 +- src/dev/build/cli.ts | 1 + src/dev/build/lib/build.test.ts | 1 + src/dev/build/lib/build.ts | 6 +- src/dev/build/lib/config.test.ts | 13 +- src/dev/build/lib/config.ts | 16 +- src/dev/build/lib/platform.test.ts | 32 ++- src/dev/build/lib/platform.ts | 31 ++- src/dev/build/lib/runner.test.ts | 1 + .../build/tasks/bin/copy_bin_scripts_task.ts | 38 ++- src/dev/build/tasks/bin/scripts/kibana | 27 +- .../tasks/bin/scripts/kibana-encryption-keys | 7 +- .../bin/scripts/kibana-encryption-keys.bat | 2 +- .../tasks/bin/scripts/kibana-health-gateway | 7 +- .../bin/scripts/kibana-health-gateway.bat | 2 +- .../build/tasks/bin/scripts/kibana-keystore | 7 +- .../tasks/bin/scripts/kibana-keystore.bat | 2 +- src/dev/build/tasks/bin/scripts/kibana-plugin | 7 +- .../build/tasks/bin/scripts/kibana-plugin.bat | 2 +- src/dev/build/tasks/bin/scripts/kibana-setup | 7 +- .../bin/scripts/kibana-verification-code | 7 +- .../bin/scripts/kibana-verification-code.bat | 2 +- .../tasks/build_canvas_shareable_runtime.ts | 5 + src/dev/build/tasks/clean_tasks.ts | 20 -- .../tasks/create_archives_sources_task.ts | 36 ++- .../tasks/fetch_agent_versions_list.test.ts | 1 + src/dev/build/tasks/install_chromium.ts | 6 +- .../tasks/nodejs/clean_node_builds_task.ts | 12 +- .../nodejs/download_node_builds_task.test.ts | 33 +-- .../tasks/nodejs/download_node_builds_task.ts | 24 +- .../nodejs/extract_node_builds_task.test.ts | 49 +++- .../tasks/nodejs/extract_node_builds_task.ts | 24 +- .../build/tasks/nodejs/node_download_info.ts | 41 ++- .../build/tasks/nodejs/node_shasums.test.ts | 6 +- src/dev/build/tasks/nodejs/node_shasums.ts | 18 +- .../verify_existing_node_builds_task.test.ts | 258 +++++++++++++++--- .../verify_existing_node_builds_task.ts | 21 +- src/dev/build/tasks/notice_file_task.ts | 2 +- .../tasks/os_packages/docker_generator/run.ts | 4 +- .../templates/base/Dockerfile | 9 - 43 files changed, 600 insertions(+), 217 deletions(-) diff --git a/.buildkite/scripts/steps/artifacts/docker_image.sh b/.buildkite/scripts/steps/artifacts/docker_image.sh index 308b391118b77..551910432c413 100755 --- a/.buildkite/scripts/steps/artifacts/docker_image.sh +++ b/.buildkite/scripts/steps/artifacts/docker_image.sh @@ -27,15 +27,10 @@ echo "--- Build Kibana" node scripts/build \ --debug \ --release \ + --serverless \ --docker-cross-compile \ - --docker-images \ --docker-namespace="kibana-ci" \ - --docker-tag="$KIBANA_IMAGE_TAG" \ - --skip-docker-ubuntu \ - --skip-docker-chainguard \ - --skip-docker-ubi \ - --skip-docker-fips \ - --skip-docker-cloud + --docker-tag="$KIBANA_IMAGE_TAG" echo "--- Tag images" docker rmi "$KIBANA_IMAGE" @@ -99,8 +94,8 @@ ts-node "$(git rev-parse --show-toplevel)/.buildkite/scripts/steps/artifacts/val "$CDN_ASSETS_FOLDER" echo "--- Upload archives" -buildkite-agent artifact upload "kibana-$BASE_VERSION-linux-x86_64.tar.gz" -buildkite-agent artifact upload "kibana-$BASE_VERSION-linux-aarch64.tar.gz" +buildkite-agent artifact upload "kibana-serverless-$BASE_VERSION-linux-x86_64.tar.gz" +buildkite-agent artifact upload "kibana-serverless-$BASE_VERSION-linux-aarch64.tar.gz" buildkite-agent artifact upload "kibana-serverless-$BASE_VERSION-docker-image.tar.gz" buildkite-agent artifact upload "kibana-serverless-$BASE_VERSION-docker-image-aarch64.tar.gz" buildkite-agent artifact upload "kibana-serverless-$BASE_VERSION-docker-build-context.tar.gz" diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts index 85f799daa8a0d..5d1aabc85a7f6 100644 --- a/src/dev/build/args.test.ts +++ b/src/dev/build/args.test.ts @@ -53,6 +53,7 @@ it('build default and oss dist for current platform, without packages, by defaul "initialize": true, "isRelease": false, "targetAllPlatforms": false, + "targetServerlessPlatforms": false, "versionQualifier": "", "withExamplePlugins": false, "withTestPlugins": false, @@ -94,6 +95,7 @@ it('builds packages if --all-platforms is passed', () => { "initialize": true, "isRelease": false, "targetAllPlatforms": true, + "targetServerlessPlatforms": false, "versionQualifier": "", "withExamplePlugins": false, "withTestPlugins": false, @@ -135,6 +137,7 @@ it('limits packages if --rpm passed with --all-platforms', () => { "initialize": true, "isRelease": false, "targetAllPlatforms": true, + "targetServerlessPlatforms": false, "versionQualifier": "", "withExamplePlugins": false, "withTestPlugins": false, @@ -176,6 +179,7 @@ it('limits packages if --deb passed with --all-platforms', () => { "initialize": true, "isRelease": false, "targetAllPlatforms": true, + "targetServerlessPlatforms": false, "versionQualifier": "", "withExamplePlugins": false, "withTestPlugins": false, @@ -218,6 +222,7 @@ it('limits packages if --docker passed with --all-platforms', () => { "initialize": true, "isRelease": false, "targetAllPlatforms": true, + "targetServerlessPlatforms": false, "versionQualifier": "", "withExamplePlugins": false, "withTestPlugins": false, @@ -267,6 +272,7 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform "initialize": true, "isRelease": false, "targetAllPlatforms": true, + "targetServerlessPlatforms": false, "versionQualifier": "", "withExamplePlugins": false, "withTestPlugins": false, @@ -309,6 +315,7 @@ it('limits packages if --all-platforms passed with --skip-docker-ubuntu', () => "initialize": true, "isRelease": false, "targetAllPlatforms": true, + "targetServerlessPlatforms": false, "versionQualifier": "", "withExamplePlugins": false, "withTestPlugins": false, @@ -351,6 +358,7 @@ it('limits packages if --all-platforms passed with --skip-docker-fips', () => { "initialize": true, "isRelease": false, "targetAllPlatforms": true, + "targetServerlessPlatforms": false, "versionQualifier": "", "withExamplePlugins": false, "withTestPlugins": false, diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts index 9526d10eb2ae1..2ffab8ccb17b9 100644 --- a/src/dev/build/args.ts +++ b/src/dev/build/args.ts @@ -48,6 +48,7 @@ export function readCliArgs(argv: string[]) { 'help', 'with-test-plugins', 'with-example-plugins', + 'serverless', ], string: ['docker-namespace', 'epr-registry'], alias: { @@ -144,11 +145,13 @@ export function readCliArgs(argv: string[]) { isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-chainguard']), createDockerCloud: isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-cloud']), createDockerServerless: - isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-serverless']), + (isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-serverless'])) || + Boolean(flags.serverless), createDockerUBI: isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-ubi']), createDockerContexts: !Boolean(flags['skip-docker-contexts']), createDockerFIPS: isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-fips']), targetAllPlatforms: Boolean(flags['all-platforms']), + targetServerlessPlatforms: Boolean(flags.serverless), eprRegistry: flags['epr-registry'], buildCanvasShareableRuntime: !Boolean(flags['skip-canvas-shareable-runtime']), withExamplePlugins: Boolean(flags['with-example-plugins']), diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts index cc23b57530a68..fca37dd46769b 100644 --- a/src/dev/build/build_distributables.ts +++ b/src/dev/build/build_distributables.ts @@ -38,6 +38,7 @@ export interface BuildOptions { createDockerFIPS: boolean; versionQualifier: string | undefined; targetAllPlatforms: boolean; + targetServerlessPlatforms: boolean; withExamplePlugins: boolean; withTestPlugins: boolean; eprRegistry: 'production' | 'snapshot'; @@ -75,7 +76,6 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions } await run(Tasks.CopyLegacySource); - await run(Tasks.CopyBinScripts); await run(Tasks.CreateEmptyDirsAndFiles); await run(Tasks.CreateReadme); @@ -109,7 +109,7 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions await run(Tasks.CreateArchivesSources); await run(Tasks.PatchNativeModules); await run(Tasks.InstallChromium); - await run(Tasks.CleanExtraBinScripts); + await run(Tasks.CopyBinScripts); await run(Tasks.CleanNodeBuilds); await run(Tasks.AssertFileTime); diff --git a/src/dev/build/cli.ts b/src/dev/build/cli.ts index 86160988c0f72..105ce366742a5 100644 --- a/src/dev/build/cli.ts +++ b/src/dev/build/cli.ts @@ -38,6 +38,7 @@ if (showHelp) { --all-platforms {dim Produce archives for all platforms, not just this one} --rpm {dim Only build the rpm packages} --deb {dim Only build the deb packages} + --serverless {dim Only build the serverless packages} --docker-images {dim Only build the Docker images} --docker-context-use-local-artifact {dim Use a local artifact when building the Docker context} --docker-cross-compile {dim Produce arm64 and amd64 Docker images} diff --git a/src/dev/build/lib/build.test.ts b/src/dev/build/lib/build.test.ts index 76fa06b04affa..bbace15247286 100644 --- a/src/dev/build/lib/build.test.ts +++ b/src/dev/build/lib/build.test.ts @@ -16,6 +16,7 @@ expect.addSnapshotSerializer(createAbsolutePathSerializer()); const config = new Config( true, + false, { version: '8.0.0', engines: { diff --git a/src/dev/build/lib/build.ts b/src/dev/build/lib/build.ts index c777ad18dc51f..a888da135f2e3 100644 --- a/src/dev/build/lib/build.ts +++ b/src/dev/build/lib/build.ts @@ -22,19 +22,21 @@ export class Build { } resolvePathForPlatform(platform: Platform, ...args: string[]) { + const variant = platform.getVariant() ? `-${platform.getVariant()}` : ''; return this.config.resolveFromRepo( 'build', 'default', - `kibana-${this.config.getBuildVersion()}-${platform.getBuildName()}`, + `kibana${variant}-${this.config.getBuildVersion()}-${platform.getBuildName()}`, ...args ); } getPlatformArchivePath(platform: Platform) { const ext = platform.isWindows() ? 'zip' : 'tar.gz'; + const variant = platform.getVariant() ? `-${platform.getVariant()}` : ''; return this.config.resolveFromRepo( 'target', - `${this.name}-${this.config.getBuildVersion()}-${platform.getBuildName()}.${ext}` + `${this.name}${variant}-${this.config.getBuildVersion()}-${platform.getBuildName()}.${ext}` ); } diff --git a/src/dev/build/lib/config.test.ts b/src/dev/build/lib/config.test.ts index 72b74c6e168cd..76a45ddb2e182 100644 --- a/src/dev/build/lib/config.test.ts +++ b/src/dev/build/lib/config.test.ts @@ -33,6 +33,7 @@ const setup = async ({ return await Config.create({ isRelease, targetAllPlatforms, + targetServerlessPlatforms: false, dockerContextUseLocalArtifact: false, dockerCrossCompile: false, dockerNamespace: null, @@ -123,6 +124,8 @@ describe('#getTargetPlatforms()', () => { "darwin-arm64", "darwin-x64", "linux-arm64", + "linux-arm64", + "linux-x64", "linux-x64", "win32-x64", ] @@ -146,7 +149,15 @@ describe('#getNodePlatforms()', () => { .getTargetPlatforms() .map((p) => p.getNodeArch()) .sort() - ).toEqual(['darwin-arm64', 'darwin-x64', 'linux-arm64', 'linux-x64', 'win32-x64']); + ).toEqual([ + 'darwin-arm64', + 'darwin-x64', + 'linux-arm64', + 'linux-arm64', + 'linux-x64', + 'linux-x64', + 'win32-x64', + ]); }); it('returns this platform and linux, when targetAllPlatforms = false', async () => { diff --git a/src/dev/build/lib/config.ts b/src/dev/build/lib/config.ts index ced8e10afab4b..1cf8faed7dd6e 100644 --- a/src/dev/build/lib/config.ts +++ b/src/dev/build/lib/config.ts @@ -19,11 +19,17 @@ import { } from '@kbn/repo-packages'; import { getVersionInfo, VersionInfo } from './version_info'; -import { PlatformName, PlatformArchitecture, ALL_PLATFORMS } from './platform'; +import { + PlatformName, + PlatformArchitecture, + ALL_PLATFORMS, + SERVERLESS_PLATFORMS, +} from './platform'; interface Options { isRelease: boolean; targetAllPlatforms: boolean; + targetServerlessPlatforms: boolean; versionQualifier?: string; dockerContextUseLocalArtifact: boolean | null; dockerCrossCompile: boolean; @@ -45,6 +51,7 @@ export class Config { return new Config( opts.targetAllPlatforms, + opts.targetServerlessPlatforms, kibanaPackageJson, nodeVersion, REPO_ROOT, @@ -72,6 +79,7 @@ export class Config { constructor( private readonly targetAllPlatforms: boolean, + private readonly targetServerlessPlatforms: boolean, private readonly pkg: KibanaPackageJson, private readonly nodeVersion: string, private readonly repoRoot: string, @@ -164,6 +172,9 @@ export class Config { * specified only the platform for this OS will be returned */ getTargetPlatforms() { + if (this.targetServerlessPlatforms) { + return SERVERLESS_PLATFORMS; + } if (this.targetAllPlatforms) { return ALL_PLATFORMS; } @@ -177,6 +188,9 @@ export class Config { * reliably get the LICENSE file, which isn't included in the windows version */ getNodePlatforms() { + if (this.targetServerlessPlatforms) { + return SERVERLESS_PLATFORMS; + } if (this.targetAllPlatforms) { return ALL_PLATFORMS; } diff --git a/src/dev/build/lib/platform.test.ts b/src/dev/build/lib/platform.test.ts index 193579d1a35c1..facbc0bd1a577 100644 --- a/src/dev/build/lib/platform.test.ts +++ b/src/dev/build/lib/platform.test.ts @@ -10,45 +10,47 @@ import { Platform } from './platform'; describe('getName()', () => { it('returns the name argument', () => { - expect(new Platform('win32', 'x64', 'foo').getName()).toBe('win32'); + expect(new Platform('win32', 'x64', 'foo', null).getName()).toBe('win32'); }); }); describe('getNodeArch()', () => { it('returns the node arch for the passed name', () => { - expect(new Platform('win32', 'x64', 'foo').getNodeArch()).toBe('win32-x64'); + expect(new Platform('win32', 'x64', 'foo', null).getNodeArch()).toBe('win32-x64'); }); }); describe('getBuildName()', () => { it('returns the build name for the passed name', () => { - expect(new Platform('linux', 'arm64', 'linux-aarch64').getBuildName()).toBe('linux-aarch64'); + expect(new Platform('linux', 'arm64', 'linux-aarch64', null).getBuildName()).toBe( + 'linux-aarch64' + ); }); }); describe('isWindows()', () => { it('returns true if name is win32', () => { - expect(new Platform('win32', 'x64', 'foo').isWindows()).toBe(true); - expect(new Platform('linux', 'x64', 'foo').isWindows()).toBe(false); - expect(new Platform('darwin', 'x64', 'foo').isWindows()).toBe(false); - expect(new Platform('darwin', 'arm64', 'foo').isWindows()).toBe(false); + expect(new Platform('win32', 'x64', 'foo', null).isWindows()).toBe(true); + expect(new Platform('linux', 'x64', 'foo', null).isWindows()).toBe(false); + expect(new Platform('darwin', 'x64', 'foo', null).isWindows()).toBe(false); + expect(new Platform('darwin', 'arm64', 'foo', null).isWindows()).toBe(false); }); }); describe('isLinux()', () => { it('returns true if name is linux', () => { - expect(new Platform('win32', 'x64', 'foo').isLinux()).toBe(false); - expect(new Platform('linux', 'x64', 'foo').isLinux()).toBe(true); - expect(new Platform('darwin', 'x64', 'foo').isLinux()).toBe(false); - expect(new Platform('darwin', 'arm64', 'foo').isLinux()).toBe(false); + expect(new Platform('win32', 'x64', 'foo', null).isLinux()).toBe(false); + expect(new Platform('linux', 'x64', 'foo', null).isLinux()).toBe(true); + expect(new Platform('darwin', 'x64', 'foo', null).isLinux()).toBe(false); + expect(new Platform('darwin', 'arm64', 'foo', null).isLinux()).toBe(false); }); }); describe('isMac()', () => { it('returns true if name is darwin', () => { - expect(new Platform('win32', 'x64', 'foo').isMac()).toBe(false); - expect(new Platform('linux', 'x64', 'foo').isMac()).toBe(false); - expect(new Platform('darwin', 'x64', 'foo').isMac()).toBe(true); - expect(new Platform('darwin', 'arm64', 'foo').isMac()).toBe(true); + expect(new Platform('win32', 'x64', 'foo', null).isMac()).toBe(false); + expect(new Platform('linux', 'x64', 'foo', null).isMac()).toBe(false); + expect(new Platform('darwin', 'x64', 'foo', null).isMac()).toBe(true); + expect(new Platform('darwin', 'arm64', 'foo', null).isMac()).toBe(true); }); }); diff --git a/src/dev/build/lib/platform.ts b/src/dev/build/lib/platform.ts index 4c4ec271318d6..dc70fb47dcefc 100644 --- a/src/dev/build/lib/platform.ts +++ b/src/dev/build/lib/platform.ts @@ -8,12 +8,14 @@ export type PlatformName = 'win32' | 'darwin' | 'linux'; export type PlatformArchitecture = 'x64' | 'arm64'; +export type Variant = 'serverless' | null; export class Platform { constructor( private name: PlatformName, private architecture: PlatformArchitecture, - private buildName: string + private buildName: string, + private variant: Variant ) {} getName() { @@ -32,6 +34,10 @@ export class Platform { return `${this.name}-${this.architecture}`; } + getVariant() { + return this.variant; + } + isWindows() { return this.name === 'win32'; } @@ -43,12 +49,23 @@ export class Platform { isLinux() { return this.name === 'linux'; } + + isServerless() { + return this.variant === 'serverless'; + } } -export const ALL_PLATFORMS = [ - new Platform('linux', 'x64', 'linux-x86_64'), - new Platform('linux', 'arm64', 'linux-aarch64'), - new Platform('darwin', 'x64', 'darwin-x86_64'), - new Platform('darwin', 'arm64', 'darwin-aarch64'), - new Platform('win32', 'x64', 'windows-x86_64'), +export const DOWNLOAD_PLATFORMS = [ + new Platform('linux', 'x64', 'linux-x86_64', null), + new Platform('linux', 'arm64', 'linux-aarch64', null), + new Platform('darwin', 'x64', 'darwin-x86_64', null), + new Platform('darwin', 'arm64', 'darwin-aarch64', null), + new Platform('win32', 'x64', 'windows-x86_64', null), ]; + +export const SERVERLESS_PLATFORMS = [ + new Platform('linux', 'x64', 'linux-x86_64', 'serverless'), + new Platform('linux', 'arm64', 'linux-aarch64', 'serverless'), +]; + +export const ALL_PLATFORMS = [...DOWNLOAD_PLATFORMS, ...SERVERLESS_PLATFORMS]; diff --git a/src/dev/build/lib/runner.test.ts b/src/dev/build/lib/runner.test.ts index a8e62219bd414..87220d450f63e 100644 --- a/src/dev/build/lib/runner.test.ts +++ b/src/dev/build/lib/runner.test.ts @@ -45,6 +45,7 @@ const setup = async () => { const config = await Config.create({ isRelease: true, targetAllPlatforms: true, + targetServerlessPlatforms: false, versionQualifier: '-SNAPSHOT', dockerContextUseLocalArtifact: false, dockerCrossCompile: false, diff --git a/src/dev/build/tasks/bin/copy_bin_scripts_task.ts b/src/dev/build/tasks/bin/copy_bin_scripts_task.ts index 5eceb1c346f01..c2faf889a1d29 100644 --- a/src/dev/build/tasks/bin/copy_bin_scripts_task.ts +++ b/src/dev/build/tasks/bin/copy_bin_scripts_task.ts @@ -6,15 +6,43 @@ * Side Public License, v 1. */ +import Mustache from 'mustache'; +import { join } from 'path'; +import { readFileSync, writeFileSync, mkdirSync } from 'fs'; +import globby from 'globby'; import { copyAll, Task } from '../../lib'; export const CopyBinScripts: Task = { - description: 'Copying bin scripts into platform-generic build directory', + description: 'Copying bin scripts into platform-specific build directory', async run(config, log, build) { - await copyAll( - config.resolveFromRepo('src/dev/build/tasks/bin/scripts'), - build.resolvePath('bin') - ); + const scriptsSrc = config.resolveFromRepo('src/dev/build/tasks/bin/scripts'); + for (const platform of config.getTargetPlatforms()) { + const scriptsDest = build.resolvePathForPlatform(platform, 'bin'); + mkdirSync(scriptsDest, { recursive: true }); + + if (platform.isWindows()) { + await copyAll(scriptsSrc, scriptsDest, { + select: ['*.bat'], + }); + } else { + globby + .sync(['*'], { + ignore: ['*.bat'], + cwd: scriptsSrc, + }) + .forEach((script) => { + const template = readFileSync(join(scriptsSrc, script), { encoding: 'utf-8' }); + const output = Mustache.render(template, { + darwin: platform.isMac(), + linux: platform.isLinux(), + serverless: platform.isServerless(), + }); + writeFileSync(join(scriptsDest, script), output, { + mode: '0755', + }); + }); + } + } }, }; diff --git a/src/dev/build/tasks/bin/scripts/kibana b/src/dev/build/tasks/bin/scripts/kibana index 4a27e965dc037..e0957582897f0 100755 --- a/src/dev/build/tasks/bin/scripts/kibana +++ b/src/dev/build/tasks/bin/scripts/kibana @@ -15,17 +15,32 @@ done DIR="$(dirname "${SCRIPT}")/.." CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"} -NODE="${DIR}/node/bin/node" -test -x "$NODE" -if [ ! -x "$NODE" ]; then - echo "unable to find usable node.js executable." - exit 1 -fi + +test_node() { + test -x "$NODE" + if [ ! -x "$NODE" ]; then + echo "unable to find usable node.js executable." + exit 1 + fi +} if [ -f "${CONFIG_DIR}/node.options" ]; then KBN_NODE_OPTS="$(grep -v ^# < ${CONFIG_DIR}/node.options | xargs)" fi +{{#darwin}} +NODE="${DIR}/node/default/bin/node" +{{/darwin}} +{{#linux}} +NODE="${DIR}/node/glibc-217/bin/node" +{{#serverless}} +if [ "$KBN_DISABLE_POINTER_COMPRESSION" != 'true' ]; then + NODE="${DIR}/node/pointer-compression/bin/node" +fi +{{/serverless}} +{{/linux}} +test_node + # Enable capturing heap snapshots. See https://nodejs.org/api/cli.html#--heapsnapshot-signalsignal # For now only on POSIX platforms, term signals work differently on Windows. NODE_OPTIONS_HEAPSNAPSHOT_DEFAULT="${NODE_OPTIONS_HEAPSNAPSHOT_DEFAULT:---heapsnapshot-signal=SIGUSR2 --diagnostic-dir=$DIR/data}" diff --git a/src/dev/build/tasks/bin/scripts/kibana-encryption-keys b/src/dev/build/tasks/bin/scripts/kibana-encryption-keys index 0521ca1c39ce7..5f3c5c08c9632 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-encryption-keys +++ b/src/dev/build/tasks/bin/scripts/kibana-encryption-keys @@ -15,7 +15,12 @@ done DIR="$(dirname "${SCRIPT}")/.." CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"} -NODE="${DIR}/node/bin/node" +{{#darwin}} +NODE="${DIR}/node/default/bin/node" +{{/darwin}} +{{#linux}} +NODE="${DIR}/node/glibc-217/bin/node" +{{/linux}} test -x "$NODE" if [ ! -x "$NODE" ]; then echo "unable to find usable node.js executable." diff --git a/src/dev/build/tasks/bin/scripts/kibana-encryption-keys.bat b/src/dev/build/tasks/bin/scripts/kibana-encryption-keys.bat index 3ad3c9e8b0797..63bca1df1ce5e 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-encryption-keys.bat +++ b/src/dev/build/tasks/bin/scripts/kibana-encryption-keys.bat @@ -5,7 +5,7 @@ SETLOCAL ENABLEDELAYEDEXPANSION set SCRIPT_DIR=%~dp0 for %%I in ("%SCRIPT_DIR%..") do set DIR=%%~dpfI -set NODE=%DIR%\node\node.exe +set NODE=%DIR%\node\default\node.exe set NODE_ENV=production If Not Exist "%NODE%" ( diff --git a/src/dev/build/tasks/bin/scripts/kibana-health-gateway b/src/dev/build/tasks/bin/scripts/kibana-health-gateway index 6b190ee2d82c4..f2a48c2babdc8 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-health-gateway +++ b/src/dev/build/tasks/bin/scripts/kibana-health-gateway @@ -15,7 +15,12 @@ done DIR="$(dirname "${SCRIPT}")/.." CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"} -NODE="${DIR}/node/bin/node" +{{#darwin}} +NODE="${DIR}/node/default/bin/node" +{{/darwin}} +{{#linux}} +NODE="${DIR}/node/glibc-217/bin/node" +{{/linux}} test -x "$NODE" if [ ! -x "$NODE" ]; then echo "unable to find usable node.js executable." diff --git a/src/dev/build/tasks/bin/scripts/kibana-health-gateway.bat b/src/dev/build/tasks/bin/scripts/kibana-health-gateway.bat index 61c3474427d00..bcbc5cfbd5254 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-health-gateway.bat +++ b/src/dev/build/tasks/bin/scripts/kibana-health-gateway.bat @@ -5,7 +5,7 @@ SETLOCAL ENABLEDELAYEDEXPANSION set SCRIPT_DIR=%~dp0 for %%I in ("%SCRIPT_DIR%..") do set DIR=%%~dpfI -set NODE=%DIR%\node\node.exe +set NODE=%DIR%\node\default\node.exe set NODE_ENV=production If Not Exist "%NODE%" ( diff --git a/src/dev/build/tasks/bin/scripts/kibana-keystore b/src/dev/build/tasks/bin/scripts/kibana-keystore index 7f9986b519023..d69f02a1869b6 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-keystore +++ b/src/dev/build/tasks/bin/scripts/kibana-keystore @@ -15,7 +15,12 @@ done DIR="$(dirname "${SCRIPT}")/.." CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"} -NODE="${DIR}/node/bin/node" +{{#darwin}} +NODE="${DIR}/node/default/bin/node" +{{/darwin}} +{{#linux}} +NODE="${DIR}/node/glibc-217/bin/node" +{{/linux}} test -x "$NODE" if [ ! -x "$NODE" ]; then echo "unable to find usable node.js executable." diff --git a/src/dev/build/tasks/bin/scripts/kibana-keystore.bat b/src/dev/build/tasks/bin/scripts/kibana-keystore.bat index 8142d4db15d06..91f183f19db08 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-keystore.bat +++ b/src/dev/build/tasks/bin/scripts/kibana-keystore.bat @@ -5,7 +5,7 @@ SETLOCAL ENABLEDELAYEDEXPANSION set SCRIPT_DIR=%~dp0 for %%I in ("%SCRIPT_DIR%..") do set DIR=%%~dpfI -set NODE=%DIR%\node\node.exe +set NODE=%DIR%\node\default\node.exe set NODE_ENV=production If Not Exist "%NODE%" ( diff --git a/src/dev/build/tasks/bin/scripts/kibana-plugin b/src/dev/build/tasks/bin/scripts/kibana-plugin index 78fdb7702643f..4c64970775aef 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-plugin +++ b/src/dev/build/tasks/bin/scripts/kibana-plugin @@ -15,7 +15,12 @@ done DIR="$(dirname "${SCRIPT}")/.." CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"} -NODE="${DIR}/node/bin/node" +{{#darwin}} +NODE="${DIR}/node/default/bin/node" +{{/darwin}} +{{#linux}} +NODE="${DIR}/node/glibc-217/bin/node" +{{/linux}} test -x "$NODE" if [ ! -x "$NODE" ]; then echo "unable to find usable node.js executable." diff --git a/src/dev/build/tasks/bin/scripts/kibana-plugin.bat b/src/dev/build/tasks/bin/scripts/kibana-plugin.bat index 1841d1998bba6..a9758fbcbab6b 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-plugin.bat +++ b/src/dev/build/tasks/bin/scripts/kibana-plugin.bat @@ -5,7 +5,7 @@ SETLOCAL ENABLEDELAYEDEXPANSION set SCRIPT_DIR=%~dp0 for %%I in ("%SCRIPT_DIR%..") do set DIR=%%~dpfI -set NODE=%DIR%\node\node.exe +set NODE=%DIR%\node\default\node.exe set NODE_ENV="production" If Not Exist "%NODE%" ( diff --git a/src/dev/build/tasks/bin/scripts/kibana-setup b/src/dev/build/tasks/bin/scripts/kibana-setup index db5ecd2db915c..8ba1ae875263d 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-setup +++ b/src/dev/build/tasks/bin/scripts/kibana-setup @@ -15,7 +15,12 @@ done DIR="$(dirname "${SCRIPT}")/.." CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"} -NODE="${DIR}/node/bin/node" +{{#darwin}} +NODE="${DIR}/node/default/bin/node" +{{/darwin}} +{{#linux}} +NODE="${DIR}/node/glibc-217/bin/node" +{{/linux}} test -x "$NODE" if [ ! -x "$NODE" ]; then echo "unable to find usable node.js executable." diff --git a/src/dev/build/tasks/bin/scripts/kibana-verification-code b/src/dev/build/tasks/bin/scripts/kibana-verification-code index d64b05a571246..f7085bd7a0460 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-verification-code +++ b/src/dev/build/tasks/bin/scripts/kibana-verification-code @@ -15,7 +15,12 @@ done DIR="$(dirname "${SCRIPT}")/.." CONFIG_DIR=${KBN_PATH_CONF:-"$DIR/config"} -NODE="${DIR}/node/bin/node" +{{#darwin}} +NODE="${DIR}/node/default/bin/node" +{{/darwin}} +{{#linux}} +NODE="${DIR}/node/glibc-217/bin/node" +{{/linux}} test -x "$NODE" if [ ! -x "$NODE" ]; then echo "unable to find usable node.js executable." diff --git a/src/dev/build/tasks/bin/scripts/kibana-verification-code.bat b/src/dev/build/tasks/bin/scripts/kibana-verification-code.bat index 8eb6cd1773a3d..76091f1207fd5 100755 --- a/src/dev/build/tasks/bin/scripts/kibana-verification-code.bat +++ b/src/dev/build/tasks/bin/scripts/kibana-verification-code.bat @@ -5,7 +5,7 @@ SETLOCAL ENABLEDELAYEDEXPANSION set SCRIPT_DIR=%~dp0 for %%I in ("%SCRIPT_DIR%..") do set DIR=%%~dpfI -set NODE=%DIR%\node\node.exe +set NODE=%DIR%\node\default\node.exe set NODE_ENV=production If Not Exist "%NODE%" ( diff --git a/src/dev/build/tasks/build_canvas_shareable_runtime.ts b/src/dev/build/tasks/build_canvas_shareable_runtime.ts index 6eb43b4dc7070..f84980aa29fcd 100644 --- a/src/dev/build/tasks/build_canvas_shareable_runtime.ts +++ b/src/dev/build/tasks/build_canvas_shareable_runtime.ts @@ -13,6 +13,11 @@ export const BuildCanvasShareableRuntime: Task = { description: 'Build Canvas shareable runtime', async run(config, log, build) { + const onlyServerless = config.getTargetPlatforms().every((platform) => platform.isServerless()); + if (onlyServerless) { + log.info('Skipping Canvas shareable runtime for serverless builds'); + return; + } await del(config.resolveFromRepo('x-pack/plugins/canvas/shareable_runtime/build')); await exec(log, process.execPath, ['plugins/canvas/scripts/shareable_runtime'], { diff --git a/src/dev/build/tasks/clean_tasks.ts b/src/dev/build/tasks/clean_tasks.ts index a9d10dfc0d3c3..6de7cb9dcd058 100644 --- a/src/dev/build/tasks/clean_tasks.ts +++ b/src/dev/build/tasks/clean_tasks.ts @@ -192,26 +192,6 @@ export const CleanExtraFilesFromModules: Task = { }, }; -export const CleanExtraBinScripts: Task = { - description: 'Cleaning extra bin/* scripts from platform-specific builds', - - async run(config, log, build) { - for (const platform of config.getNodePlatforms()) { - if (platform.isWindows()) { - await deleteAll( - [ - build.resolvePathForPlatform(platform, 'bin', '*'), - `!${build.resolvePathForPlatform(platform, 'bin', '*.bat')}`, - ], - log - ); - } else { - await deleteAll([build.resolvePathForPlatform(platform, 'bin', '*.bat')], log); - } - } - }, -}; - export const CleanEmptyFolders: Task = { description: 'Cleaning all empty folders recursively', diff --git a/src/dev/build/tasks/create_archives_sources_task.ts b/src/dev/build/tasks/create_archives_sources_task.ts index 9779896ba8cca..a8c08a2aba4d2 100644 --- a/src/dev/build/tasks/create_archives_sources_task.ts +++ b/src/dev/build/tasks/create_archives_sources_task.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ -import { scanCopy, Task } from '../lib'; +import { REPO_ROOT } from '@kbn/repo-info'; +import { resolve } from 'path'; +import { scanCopy, Task, deleteAll, copyAll } from '../lib'; import { getNodeDownloadInfo } from './nodejs'; export const CreateArchivesSources: Task = { @@ -27,12 +29,36 @@ export const CreateArchivesSources: Task = { ); // copy node.js install - await scanCopy({ - source: getNodeDownloadInfo(config, platform).extractDir, - destination: build.resolvePathForPlatform(platform, 'node'), - }); + await Promise.all( + getNodeDownloadInfo(config, platform).map((nodeInfo) => { + return scanCopy({ + source: nodeInfo.extractDir, + destination: build.resolvePathForPlatform(platform, 'node', nodeInfo.variant), + }); + }) + ); log.debug('Node.js copied into', platform.getNodeArch(), 'specific build directory'); + + if (platform.isServerless()) { + await deleteAll( + [ + 'x-pack/plugins/canvas/shareable_runtime/build', + 'node_modules/@kbn/screenshotting-plugin/server/assets', + ].map((path) => build.resolvePathForPlatform(platform, path)), + log + ); + await copyAll( + resolve(REPO_ROOT, 'config'), + build.resolvePathForPlatform(platform, 'config'), + { + select: ['serverless.yml', 'serverless.{es,oblt,security}.yml'], + } + ); + log.debug( + `Serverless adjustments made in serverless-${platform.getNodeArch()} specific build directory` + ); + } }) ); }, diff --git a/src/dev/build/tasks/fetch_agent_versions_list.test.ts b/src/dev/build/tasks/fetch_agent_versions_list.test.ts index e27442beff0b4..b8ac42d033a6e 100644 --- a/src/dev/build/tasks/fetch_agent_versions_list.test.ts +++ b/src/dev/build/tasks/fetch_agent_versions_list.test.ts @@ -20,6 +20,7 @@ jest.mock('../lib'); const config = new Config( true, + false, { version: '8.0.0', engines: { diff --git a/src/dev/build/tasks/install_chromium.ts b/src/dev/build/tasks/install_chromium.ts index 968f9f5b474a3..3ec31f95370ca 100644 --- a/src/dev/build/tasks/install_chromium.ts +++ b/src/dev/build/tasks/install_chromium.ts @@ -18,9 +18,11 @@ export const InstallChromium: Task = { for (const platform of config.getNodePlatforms()) { const pkg = paths.find(platform.getName(), platform.getArchitecture(), preInstalledPackages); - const target = `${platform.getName()}-${platform.getArchitecture()}`; + const target = `${ + platform.getVariant() || 'default' + }-${platform.getName()}-${platform.getArchitecture()}`; - if (!pkg) { + if (!pkg || platform.isServerless()) { log.info(`Skipping Chromium install for ${target}`); // Unbundled chromium packages (for Darwin): Chromium is downloaded at diff --git a/src/dev/build/tasks/nodejs/clean_node_builds_task.ts b/src/dev/build/tasks/nodejs/clean_node_builds_task.ts index 487cf99cf162c..6498efdcce19f 100644 --- a/src/dev/build/tasks/nodejs/clean_node_builds_task.ts +++ b/src/dev/build/tasks/nodejs/clean_node_builds_task.ts @@ -15,12 +15,12 @@ export const CleanNodeBuilds: Task = { for (const platform of config.getTargetPlatforms()) { await deleteAll( [ - build.resolvePathForPlatform(platform, 'node/lib/node_modules'), - build.resolvePathForPlatform(platform, 'node/bin/npm'), - build.resolvePathForPlatform(platform, 'node/bin/npx'), - build.resolvePathForPlatform(platform, 'node/bin/corepack'), - build.resolvePathForPlatform(platform, 'node/CHANGELOG.md'), - build.resolvePathForPlatform(platform, 'node/README.md'), + build.resolvePathForPlatform(platform, '*/node/lib/node_modules'), + build.resolvePathForPlatform(platform, '*/node/bin/npm'), + build.resolvePathForPlatform(platform, '*/node/bin/npx'), + build.resolvePathForPlatform(platform, '*/node/bin/corepack'), + build.resolvePathForPlatform(platform, '*/node/CHANGELOG.md'), + build.resolvePathForPlatform(platform, '*/node/README.md'), ], log ); diff --git a/src/dev/build/tasks/nodejs/download_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/download_node_builds_task.test.ts index a67ccb254cb8b..f873e1648ffaf 100644 --- a/src/dev/build/tasks/nodejs/download_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/download_node_builds_task.test.ts @@ -36,6 +36,7 @@ async function setup({ failOnUrl }: { failOnUrl?: string } = {}) { const config = await Config.create({ isRelease: true, targetAllPlatforms: true, + targetServerlessPlatforms: false, dockerContextUseLocalArtifact: false, dockerCrossCompile: false, dockerNamespace: null, @@ -48,11 +49,13 @@ async function setup({ failOnUrl }: { failOnUrl?: string } = {}) { }); getNodeDownloadInfo.mockImplementation((_: Config, platform: Platform) => { - return { - url: `${platform.getName()}:url`, - downloadPath: `${platform.getName()}:downloadPath`, - downloadName: `${platform.getName()}:downloadName`, - }; + return [ + { + url: `${platform.getName()}:url`, + downloadPath: `${platform.getName()}:downloadPath`, + downloadName: `${platform.getName()}:downloadName`, + }, + ]; }); getNodeShasums.mockReturnValue({ @@ -87,26 +90,6 @@ it('downloads node builds for each platform', async () => { "url": "linux:url", }, ], - Array [ - Object { - "destination": "linux:downloadPath", - "log": , - "maxAttempts": 3, - "shaAlgorithm": "sha256", - "shaChecksum": "linux:sha256", - "url": "linux:url", - }, - ], - Array [ - Object { - "destination": "darwin:downloadPath", - "log": , - "maxAttempts": 3, - "shaAlgorithm": "sha256", - "shaChecksum": "darwin:sha256", - "url": "darwin:url", - }, - ], Array [ Object { "destination": "darwin:downloadPath", diff --git a/src/dev/build/tasks/nodejs/download_node_builds_task.ts b/src/dev/build/tasks/nodejs/download_node_builds_task.ts index 9bc46def46964..d630ccff7f4c1 100644 --- a/src/dev/build/tasks/nodejs/download_node_builds_task.ts +++ b/src/dev/build/tasks/nodejs/download_node_builds_task.ts @@ -14,16 +14,26 @@ export const DownloadNodeBuilds: GlobalTask = { global: true, description: 'Downloading node.js builds for all platforms', async run(config, log) { - const shasums = await getNodeShasums(log, config.getNodeVersion()); + const downloads: Array<{ url: string; destination: string; shaChecksum: string }> = []; + for (const platform of config.getNodePlatforms()) { + for (const nodeInfo of getNodeDownloadInfo(config, platform)) { + const shasums = await getNodeShasums(log, config.getNodeVersion(), nodeInfo.variant); + + if (!downloads.some((download) => download.url === nodeInfo.url)) { + downloads.push({ + url: nodeInfo.url, + destination: nodeInfo.downloadPath, + shaChecksum: shasums[nodeInfo.downloadName], + }); + } + } + } await Promise.all( - config.getNodePlatforms().map(async (platform) => { - const { url, downloadPath, downloadName } = getNodeDownloadInfo(config, platform); - await downloadToDisk({ + downloads.map(function (download) { + return downloadToDisk({ + ...download, log, - url, - shaChecksum: shasums[downloadName], shaAlgorithm: 'sha256', - destination: downloadPath, maxAttempts: 3, }); }) diff --git a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts index 5ca9fcbe10718..962cde6c4df4f 100644 --- a/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/extract_node_builds_task.test.ts @@ -39,6 +39,7 @@ async function setup() { const config = await Config.create({ isRelease: true, targetAllPlatforms: true, + targetServerlessPlatforms: false, dockerContextUseLocalArtifact: false, dockerCrossCompile: false, dockerNamespace: null, @@ -81,8 +82,8 @@ it('runs expected fs operations', async () => { Object { "copy": Array [ Array [ - /.node_binaries//node.exe, - /.node_binaries//win32-x64/node.exe, + /.node_binaries//default/node.exe, + /.node_binaries//default/win32-x64/node.exe, Object { "clone": true, }, @@ -90,29 +91,57 @@ it('runs expected fs operations', async () => { ], "untar": Array [ Array [ - /.node_binaries//node-v-linux-x64.tar.gz, - /.node_binaries//linux-x64, + /.node_binaries//glibc-217/node-v-linux-x64.tar.gz, + /.node_binaries//glibc-217/linux-x64, Object { "strip": 1, }, ], Array [ - /.node_binaries//node-v-linux-arm64.tar.gz, - /.node_binaries//linux-arm64, + /.node_binaries//glibc-217/node-v-linux-arm64.tar.gz, + /.node_binaries//glibc-217/linux-arm64, Object { "strip": 1, }, ], Array [ - /.node_binaries//node-v-darwin-x64.tar.gz, - /.node_binaries//darwin-x64, + /.node_binaries//default/node-v-darwin-x64.tar.gz, + /.node_binaries//default/darwin-x64, Object { "strip": 1, }, ], Array [ - /.node_binaries//node-v-darwin-arm64.tar.gz, - /.node_binaries//darwin-arm64, + /.node_binaries//default/node-v-darwin-arm64.tar.gz, + /.node_binaries//default/darwin-arm64, + Object { + "strip": 1, + }, + ], + Array [ + /.node_binaries//glibc-217/node-v-linux-x64.tar.gz, + /.node_binaries//glibc-217/linux-x64, + Object { + "strip": 1, + }, + ], + Array [ + /.node_binaries//pointer-compression/node-v-linux-x64.tar.gz, + /.node_binaries//pointer-compression/linux-x64, + Object { + "strip": 1, + }, + ], + Array [ + /.node_binaries//glibc-217/node-v-linux-arm64.tar.gz, + /.node_binaries//glibc-217/linux-arm64, + Object { + "strip": 1, + }, + ], + Array [ + /.node_binaries//pointer-compression/node-v-linux-arm64.tar.gz, + /.node_binaries//pointer-compression/linux-arm64, Object { "strip": 1, }, diff --git a/src/dev/build/tasks/nodejs/extract_node_builds_task.ts b/src/dev/build/tasks/nodejs/extract_node_builds_task.ts index 234158d5b5bfd..d77c7474bc24b 100644 --- a/src/dev/build/tasks/nodejs/extract_node_builds_task.ts +++ b/src/dev/build/tasks/nodejs/extract_node_builds_task.ts @@ -7,7 +7,7 @@ */ import Path from 'path'; - +import Fs from 'fs'; import { untar, GlobalTask, copy } from '../../lib'; import { getNodeDownloadInfo } from './node_download_info'; @@ -17,15 +17,19 @@ export const ExtractNodeBuilds: GlobalTask = { async run(config) { await Promise.all( config.getNodePlatforms().map(async (platform) => { - const { downloadPath, extractDir } = getNodeDownloadInfo(config, platform); - if (platform.isWindows()) { - // windows executable is not extractable, it's just an .exe file - await copy(downloadPath, Path.resolve(extractDir, 'node.exe'), { - clone: true, - }); - } else { - await untar(downloadPath, extractDir, { strip: 1 }); - } + await Promise.all( + getNodeDownloadInfo(config, platform).map((nodeInfo) => { + if (Fs.existsSync(nodeInfo.extractDir)) return; + if (platform.isWindows()) { + // windows executable is not extractable, it's just an .exe file + return copy(nodeInfo.downloadPath, Path.resolve(nodeInfo.extractDir, 'node.exe'), { + clone: true, + }); + } else { + return untar(nodeInfo.downloadPath, nodeInfo.extractDir, { strip: 1 }); + } + }) + ); }) ); }, diff --git a/src/dev/build/tasks/nodejs/node_download_info.ts b/src/dev/build/tasks/nodejs/node_download_info.ts index 821cdb6973ca9..489ff1fc00947 100644 --- a/src/dev/build/tasks/nodejs/node_download_info.ts +++ b/src/dev/build/tasks/nodejs/node_download_info.ts @@ -13,20 +13,35 @@ import { Config, Platform } from '../../lib'; export function getNodeDownloadInfo(config: Config, platform: Platform) { const version = config.getNodeVersion(); const arch = platform.getNodeArch(); + let variants = ['default']; + if (platform.isLinux()) { + variants = ['glibc-217']; + if (platform.isServerless()) variants.push('pointer-compression'); + } - const downloadName = platform.isWindows() - ? 'win-x64/node.exe' - : `node-v${version}-${arch}.tar.gz`; + return variants.map((variant) => { + const downloadName = platform.isWindows() + ? 'win-x64/node.exe' + : `node-v${version}-${arch}.tar.gz`; - const url = `https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v${version}/${downloadName}`; - const downloadPath = config.resolveFromRepo('.node_binaries', version, basename(downloadName)); - const extractDir = config.resolveFromRepo('.node_binaries', version, arch); + let variantPath = ''; + if (variant === 'pointer-compression') variantPath = 'node-pointer-compression/'; + const url = `https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/${variantPath}dist/v${version}/${downloadName}`; + const downloadPath = config.resolveFromRepo( + '.node_binaries', + version, + variant, + basename(downloadName) + ); + const extractDir = config.resolveFromRepo('.node_binaries', version, variant, arch); - return { - url, - downloadName, - downloadPath, - extractDir, - version, - }; + return { + url, + downloadName, + downloadPath, + extractDir, + variant, + version, + }; + }); } diff --git a/src/dev/build/tasks/nodejs/node_shasums.test.ts b/src/dev/build/tasks/nodejs/node_shasums.test.ts index fc888851946e2..a1c6e963d4c66 100644 --- a/src/dev/build/tasks/nodejs/node_shasums.test.ts +++ b/src/dev/build/tasks/nodejs/node_shasums.test.ts @@ -61,7 +61,11 @@ import { getNodeShasums } from './node_shasums'; describe('src/dev/build/tasks/nodejs/node_shasums', () => { it('resolves to an object with shasums for node downloads for version', async () => { - const shasums = await getNodeShasums(new ToolingLog(), '8.9.4'); + const shasums = await getNodeShasums(new ToolingLog(), '8.9.4', { + getTarget() { + return null; + }, + } as any); expect(shasums).toEqual( expect.objectContaining({ 'node-v8.9.4.tar.gz': '729b44b32b2f82ecd5befac4f7518de0c4e3add34e8fe878f745740a66cbbc01', diff --git a/src/dev/build/tasks/nodejs/node_shasums.ts b/src/dev/build/tasks/nodejs/node_shasums.ts index 850e28d5d3ead..7e25908185eb9 100644 --- a/src/dev/build/tasks/nodejs/node_shasums.ts +++ b/src/dev/build/tasks/nodejs/node_shasums.ts @@ -9,14 +9,22 @@ import { ToolingLog } from '@kbn/tooling-log'; import { downloadToString } from '../../lib/download'; -export async function getNodeShasums(log: ToolingLog, nodeVersion: string) { - const url = `https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v${nodeVersion}/SHASUMS256.txt`; +const cache: Record> = {}; + +export async function getNodeShasums(log: ToolingLog, nodeVersion: string, variant: string | null) { + let variantPath = ''; + if (variant === 'pointer-compression') variantPath = 'node-pointer-compression/'; + const url = `https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/${variantPath}dist/v${nodeVersion}/SHASUMS256.txt`; + + if (cache[url]) { + log.debug('Returning cached shasum values for node version', nodeVersion, 'from', url); + return cache[url]; + } log.debug('Downloading shasum values for node version', nodeVersion, 'from', url); const checksum = await downloadToString({ log, url, expectStatus: 200 }); - - return checksum.split('\n').reduce((acc: Record, line: string) => { + const result = checksum.split('\n').reduce((acc: Record, line: string) => { const [sha, platform] = line.split(' '); return { @@ -24,4 +32,6 @@ export async function getNodeShasums(log: ToolingLog, nodeVersion: string) { [platform]: sha, }; }, {}); + cache[url] = result; + return result; } diff --git a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts index 966012a194ab0..995244e9d2b84 100644 --- a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts +++ b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.test.ts @@ -44,6 +44,7 @@ async function setup(actualShaSums?: Record) { const config = await Config.create({ isRelease: true, targetAllPlatforms: true, + targetServerlessPlatforms: false, dockerContextUseLocalArtifact: false, dockerCrossCompile: false, dockerNamespace: null, @@ -58,16 +59,27 @@ async function setup(actualShaSums?: Record) { getNodeShasums.mockReturnValue( Object.fromEntries( config.getTargetPlatforms().map((platform) => { - return [`${platform.getName()}:${platform.getNodeArch()}:downloadName`, 'valid shasum']; + return [ + `${platform.getName()}:${ + platform.getVariant() || 'default' + }:${platform.getNodeArch()}:downloadName`, + 'valid shasum', + ]; }) ) ); getNodeDownloadInfo.mockImplementation((_: Config, platform: Platform) => { - return { - downloadPath: `${platform.getName()}:${platform.getNodeArch()}:downloadPath`, - downloadName: `${platform.getName()}:${platform.getNodeArch()}:downloadName`, - }; + return [ + { + downloadPath: `${platform.getName()}:${ + platform.getVariant() || 'default' + }:${platform.getNodeArch()}:downloadPath`, + downloadName: `${platform.getName()}:${ + platform.getVariant() || 'default' + }:${platform.getNodeArch()}:downloadName`, + }, + ]; }); getFileHash.mockImplementation((downloadPath: string) => { @@ -97,17 +109,122 @@ it('checks shasums for each downloaded node build', async () => { Array [ , "", + undefined, + ], + Array [ + , + "", + undefined, + ], + Array [ + , + "", + undefined, + ], + Array [ + , + "", + undefined, + ], + Array [ + , + "", + undefined, + ], + Array [ + , + "", + undefined, + ], + Array [ + , + "", + undefined, ], ], "results": Array [ Object { "type": "return", "value": Object { - "darwin:darwin-arm64:downloadName": "valid shasum", - "darwin:darwin-x64:downloadName": "valid shasum", - "linux:linux-arm64:downloadName": "valid shasum", - "linux:linux-x64:downloadName": "valid shasum", - "win32:win32-x64:downloadName": "valid shasum", + "darwin:default:darwin-arm64:downloadName": "valid shasum", + "darwin:default:darwin-x64:downloadName": "valid shasum", + "linux:default:linux-arm64:downloadName": "valid shasum", + "linux:default:linux-x64:downloadName": "valid shasum", + "linux:serverless:linux-arm64:downloadName": "valid shasum", + "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-x64:downloadName": "valid shasum", + }, + }, + Object { + "type": "return", + "value": Object { + "darwin:default:darwin-arm64:downloadName": "valid shasum", + "darwin:default:darwin-x64:downloadName": "valid shasum", + "linux:default:linux-arm64:downloadName": "valid shasum", + "linux:default:linux-x64:downloadName": "valid shasum", + "linux:serverless:linux-arm64:downloadName": "valid shasum", + "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-x64:downloadName": "valid shasum", + }, + }, + Object { + "type": "return", + "value": Object { + "darwin:default:darwin-arm64:downloadName": "valid shasum", + "darwin:default:darwin-x64:downloadName": "valid shasum", + "linux:default:linux-arm64:downloadName": "valid shasum", + "linux:default:linux-x64:downloadName": "valid shasum", + "linux:serverless:linux-arm64:downloadName": "valid shasum", + "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-x64:downloadName": "valid shasum", + }, + }, + Object { + "type": "return", + "value": Object { + "darwin:default:darwin-arm64:downloadName": "valid shasum", + "darwin:default:darwin-x64:downloadName": "valid shasum", + "linux:default:linux-arm64:downloadName": "valid shasum", + "linux:default:linux-x64:downloadName": "valid shasum", + "linux:serverless:linux-arm64:downloadName": "valid shasum", + "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-x64:downloadName": "valid shasum", + }, + }, + Object { + "type": "return", + "value": Object { + "darwin:default:darwin-arm64:downloadName": "valid shasum", + "darwin:default:darwin-x64:downloadName": "valid shasum", + "linux:default:linux-arm64:downloadName": "valid shasum", + "linux:default:linux-x64:downloadName": "valid shasum", + "linux:serverless:linux-arm64:downloadName": "valid shasum", + "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-x64:downloadName": "valid shasum", + }, + }, + Object { + "type": "return", + "value": Object { + "darwin:default:darwin-arm64:downloadName": "valid shasum", + "darwin:default:darwin-x64:downloadName": "valid shasum", + "linux:default:linux-arm64:downloadName": "valid shasum", + "linux:default:linux-x64:downloadName": "valid shasum", + "linux:serverless:linux-arm64:downloadName": "valid shasum", + "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-x64:downloadName": "valid shasum", + }, + }, + Object { + "type": "return", + "value": Object { + "darwin:default:darwin-arm64:downloadName": "valid shasum", + "darwin:default:darwin-x64:downloadName": "valid shasum", + "linux:default:linux-arm64:downloadName": "valid shasum", + "linux:default:linux-x64:downloadName": "valid shasum", + "linux:serverless:linux-arm64:downloadName": "valid shasum", + "linux:serverless:linux-x64:downloadName": "valid shasum", + "win32:default:win32-x64:downloadName": "valid shasum", }, }, ], @@ -122,6 +239,7 @@ it('checks shasums for each downloaded node build', async () => { "architecture": "x64", "buildName": "linux-x86_64", "name": "linux", + "variant": null, }, ], Array [ @@ -130,6 +248,7 @@ it('checks shasums for each downloaded node build', async () => { "architecture": "arm64", "buildName": "linux-aarch64", "name": "linux", + "variant": null, }, ], Array [ @@ -138,6 +257,7 @@ it('checks shasums for each downloaded node build', async () => { "architecture": "x64", "buildName": "darwin-x86_64", "name": "darwin", + "variant": null, }, ], Array [ @@ -146,6 +266,7 @@ it('checks shasums for each downloaded node build', async () => { "architecture": "arm64", "buildName": "darwin-aarch64", "name": "darwin", + "variant": null, }, ], Array [ @@ -154,44 +275,91 @@ it('checks shasums for each downloaded node build', async () => { "architecture": "x64", "buildName": "windows-x86_64", "name": "win32", + "variant": null, + }, + ], + Array [ + , + Platform { + "architecture": "x64", + "buildName": "linux-x86_64", + "name": "linux", + "variant": "serverless", + }, + ], + Array [ + , + Platform { + "architecture": "arm64", + "buildName": "linux-aarch64", + "name": "linux", + "variant": "serverless", }, ], ], "results": Array [ Object { "type": "return", - "value": Object { - "downloadName": "linux:linux-x64:downloadName", - "downloadPath": "linux:linux-x64:downloadPath", - }, + "value": Array [ + Object { + "downloadName": "linux:default:linux-x64:downloadName", + "downloadPath": "linux:default:linux-x64:downloadPath", + }, + ], }, Object { "type": "return", - "value": Object { - "downloadName": "linux:linux-arm64:downloadName", - "downloadPath": "linux:linux-arm64:downloadPath", - }, + "value": Array [ + Object { + "downloadName": "linux:default:linux-arm64:downloadName", + "downloadPath": "linux:default:linux-arm64:downloadPath", + }, + ], }, Object { "type": "return", - "value": Object { - "downloadName": "darwin:darwin-x64:downloadName", - "downloadPath": "darwin:darwin-x64:downloadPath", - }, + "value": Array [ + Object { + "downloadName": "darwin:default:darwin-x64:downloadName", + "downloadPath": "darwin:default:darwin-x64:downloadPath", + }, + ], }, Object { "type": "return", - "value": Object { - "downloadName": "darwin:darwin-arm64:downloadName", - "downloadPath": "darwin:darwin-arm64:downloadPath", - }, + "value": Array [ + Object { + "downloadName": "darwin:default:darwin-arm64:downloadName", + "downloadPath": "darwin:default:darwin-arm64:downloadPath", + }, + ], }, Object { "type": "return", - "value": Object { - "downloadName": "win32:win32-x64:downloadName", - "downloadPath": "win32:win32-x64:downloadPath", - }, + "value": Array [ + Object { + "downloadName": "win32:default:win32-x64:downloadName", + "downloadPath": "win32:default:win32-x64:downloadPath", + }, + ], + }, + Object { + "type": "return", + "value": Array [ + Object { + "downloadName": "linux:serverless:linux-x64:downloadName", + "downloadPath": "linux:serverless:linux-x64:downloadPath", + }, + ], + }, + Object { + "type": "return", + "value": Array [ + Object { + "downloadName": "linux:serverless:linux-arm64:downloadName", + "downloadPath": "linux:serverless:linux-arm64:downloadPath", + }, + ], }, ], } @@ -200,23 +368,31 @@ it('checks shasums for each downloaded node build', async () => { [MockFunction] { "calls": Array [ Array [ - "linux:linux-x64:downloadPath", + "linux:default:linux-x64:downloadPath", "sha256", ], Array [ - "linux:linux-arm64:downloadPath", + "linux:default:linux-arm64:downloadPath", "sha256", ], Array [ - "darwin:darwin-x64:downloadPath", + "darwin:default:darwin-x64:downloadPath", "sha256", ], Array [ - "darwin:darwin-arm64:downloadPath", + "darwin:default:darwin-arm64:downloadPath", "sha256", ], Array [ - "win32:win32-x64:downloadPath", + "win32:default:win32-x64:downloadPath", + "sha256", + ], + Array [ + "linux:serverless:linux-x64:downloadPath", + "sha256", + ], + Array [ + "linux:serverless:linux-arm64:downloadPath", "sha256", ], ], @@ -241,6 +417,14 @@ it('checks shasums for each downloaded node build', async () => { "type": "return", "value": "valid shasum", }, + Object { + "type": "return", + "value": "valid shasum", + }, + Object { + "type": "return", + "value": "valid shasum", + }, ], } `); @@ -248,12 +432,12 @@ it('checks shasums for each downloaded node build', async () => { it('rejects if any download has an incorrect sha256', async () => { const { config } = await setup({ - 'linux:linux-arm64:downloadPath': 'invalid shasum', + 'linux:default:linux-arm64:downloadPath': 'invalid shasum', }); await expect( VerifyExistingNodeBuilds.run(config, log, []) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Download at linux:linux-arm64:downloadPath does not match expected checksum invalid shasum"` + `"Download at linux:default:linux-arm64:downloadPath does not match expected checksum invalid shasum"` ); }); diff --git a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.ts b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.ts index 394e883947eb3..29e86d4ca07b4 100644 --- a/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.ts +++ b/src/dev/build/tasks/nodejs/verify_existing_node_builds_task.ts @@ -14,18 +14,27 @@ export const VerifyExistingNodeBuilds: GlobalTask = { global: true, description: 'Verifying previously downloaded node.js build for all platforms', async run(config, log) { - const shasums = await getNodeShasums(log, config.getNodeVersion()); + const builds: Array<{ downloadPath: string; shaChecksum: string }> = []; + for (const platform of config.getNodePlatforms()) { + for (const nodeInfo of getNodeDownloadInfo(config, platform)) { + const shasums = await getNodeShasums(log, config.getNodeVersion(), nodeInfo.variant); + if (!builds.some((build) => build.downloadPath === nodeInfo.downloadName)) { + builds.push({ + downloadPath: nodeInfo.downloadPath, + shaChecksum: shasums[nodeInfo.downloadName], + }); + } + } + } await Promise.all( - config.getNodePlatforms().map(async (platform) => { - const { downloadPath, downloadName } = getNodeDownloadInfo(config, platform); - + builds.map(async ({ shaChecksum, downloadPath }) => { const sha256 = await getFileHash(downloadPath, 'sha256'); - if (sha256 !== shasums[downloadName]) { + if (sha256 !== shaChecksum) { throw new Error(`Download at ${downloadPath} does not match expected checksum ${sha256}`); } - log.success(`Download for ${platform.getNodeArch()} matches checksum`); + log.success(`Download at ${downloadPath} matches checksum`); }) ); }, diff --git a/src/dev/build/tasks/notice_file_task.ts b/src/dev/build/tasks/notice_file_task.ts index 2b38e379bc9da..62847f0076fc7 100644 --- a/src/dev/build/tasks/notice_file_task.ts +++ b/src/dev/build/tasks/notice_file_task.ts @@ -36,7 +36,7 @@ export const CreateNoticeFile: Task = { }); log.info('Generating build notice'); - const { extractDir: nodeDir, version: nodeVersion } = getNodeDownloadInfo( + const [{ extractDir: nodeDir, version: nodeVersion }] = getNodeDownloadInfo( config, config.getPlatform('linux', 'x64') ); diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index a0aed0a71c1f7..88de0e92e868f 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -64,7 +64,9 @@ export async function runDockerGenerator( const imageTag = `docker.elastic.co/${imageNamespace}/kibana`; const version = config.getBuildVersion(); const artifactArchitecture = flags.architecture === 'aarch64' ? 'aarch64' : 'x86_64'; - const artifactPrefix = `kibana-${version}-linux`; + let artifactVariant = ''; + if (flags.serverless) artifactVariant = '-serverless'; + const artifactPrefix = `kibana${artifactVariant}-${version}-linux`; const artifactTarball = `${artifactPrefix}-${artifactArchitecture}.tar.gz`; const beatsArchitecture = flags.architecture === 'aarch64' ? 'arm64' : 'x86_64'; const metricbeatTarball = `metricbeat-${version}-linux-${beatsArchitecture}.tar.gz`; diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile index d9e67b8389326..e7f515ed3bfe2 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile @@ -35,11 +35,6 @@ COPY {{artifactTarball}} /tmp/kibana.tar.gz RUN mkdir /usr/share/kibana WORKDIR /usr/share/kibana RUN tar \ -{{#serverless}} -# Exclude serverless.yml disabled assets - --exclude=screenshotting-plugin/chromium \ - --exclude=screenshotting-plugin/server/assets \ -{{/serverless}} --strip-components=1 \ -zxf /tmp/kibana.tar.gz # Ensure that group permissions are the same as user permissions. @@ -173,10 +168,6 @@ ENV PATH=/usr/share/kibana/bin:$PATH # Set some Kibana configuration defaults. COPY --chown=1000:0 config/kibana.yml /usr/share/kibana/config/kibana.yml {{#serverless}} -COPY --chown=1000:0 config/serverless.yml /usr/share/kibana/config/serverless.yml -COPY --chown=1000:0 config/serverless.es.yml /usr/share/kibana/config/serverless.es.yml -COPY --chown=1000:0 config/serverless.oblt.yml /usr/share/kibana/config/serverless.oblt.yml -COPY --chown=1000:0 config/serverless.security.yml /usr/share/kibana/config/serverless.security.yml ENV PROFILER_SIGNAL=SIGUSR1 {{/serverless}} {{^opensslLegacyProvider}} From f87eccf18fedb4f12d5b9c62381459dce9c15b0d Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Fri, 7 Jun 2024 12:33:22 -0400 Subject: [PATCH 05/10] [Embeddables Rebuild] Fix sharing unchanged panels (#184873) Fixes a regression where if there were any unsaved changes to a react embeddable, any panels without unsaved changes would be missing. --- .../top_nav/share/show_share_modal.test.tsx | 23 +++++++ .../top_nav/share/show_share_modal.tsx | 62 +++++++++++-------- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.test.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.test.tsx index 7d716528148d1..65b435303ca3e 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.test.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.test.tsx @@ -124,6 +124,20 @@ describe('ShowShareModal', () => { query: { query: 'bye', language: 'kuery' }, } as unknown as DashboardContainerInput; const showModalProps = getPropsAndShare(unsavedDashboardState); + showModalProps.getDashboardState = () => { + return { + panels: { + panel_1: { + type: 'panel_type', + gridData: { w: 0, h: 0, x: 0, y: 0, i: '0' }, + panelRefName: 'superPanel', + explicitInput: { + id: 'superPanel', + }, + }, + }, + } as unknown as DashboardContainerInput; + }; ShowShareModal(showModalProps); expect(toggleShareMenuSpy).toHaveBeenCalledTimes(1); const shareLocatorParams = ( @@ -175,6 +189,14 @@ describe('ShowShareModal', () => { changedKey2: 'definitely NOT changed', }, }, + panel_3: { + gridData: { w: 0, h: 0, x: 0, y: 0, i: '0' }, + type: 'superType', + explicitInput: { + id: 'whatever2', + changedKey3: 'should still exist', + }, + }, }, } as unknown as DashboardContainerInput; }; @@ -197,5 +219,6 @@ describe('ShowShareModal', () => { expect(shareLocatorParams.panels).toBeDefined(); expect(shareLocatorParams.panels![0].embeddableConfig.changedKey1).toBe('changed'); expect(shareLocatorParams.panels![1].embeddableConfig.changedKey2).toBe('definitely changed'); + expect(shareLocatorParams.panels![2].embeddableConfig.changedKey3).toBe('should still exist'); }); }); diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx index c9c7891d6b352..9adebbacd1248 100644 --- a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx +++ b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx @@ -127,40 +127,50 @@ export function ShowShareModal({ let unsavedStateForLocator: DashboardLocatorParams = {}; - const { dashboardState: unsavedDashboardState, panels: unsavedPanelStates } = + const { dashboardState: unsavedDashboardState, panels: panelModifications } = dashboardBackup.getState(savedObjectId) ?? {}; - const latestPanels = getDashboardState().panels; - const unsavedPanelsMap = unsavedPanelStates - ? Object.entries(unsavedPanelStates).reduce((acc, [panelId, unsavedPanel]) => { - if (unsavedPanel && latestPanels?.[panelId]) { - acc[panelId] = { - ...latestPanels[panelId], - explicitInput: { - ...latestPanels?.[panelId].explicitInput, - ...unsavedPanel, - id: panelId, - }, - }; - } - return acc; - }, {} as DashboardPanelMap) - : {}; - - const allPanels: DashboardPanelMap = { - ...(unsavedDashboardState?.panels ?? {}), - ...unsavedPanelsMap, - }; + const allUnsavedPanels = (() => { + if ( + Object.keys(unsavedDashboardState?.panels ?? {}).length === 0 && + Object.keys(panelModifications ?? {}).length === 0 + ) { + // if this dashboard has no modifications or unsaved panels return early. No overrides needed. + return; + } + + const latestPanels = getDashboardState().panels; + // apply modifications to panels. + const modifiedPanels = panelModifications + ? Object.entries(panelModifications).reduce((acc, [panelId, unsavedPanel]) => { + if (unsavedPanel && latestPanels?.[panelId]) { + acc[panelId] = { + ...latestPanels[panelId], + explicitInput: { + ...latestPanels?.[panelId].explicitInput, + ...unsavedPanel, + id: panelId, + }, + }; + } + return acc; + }, {} as DashboardPanelMap) + : {}; + + // The latest state of panels to share. This will overwrite panels from the saved object on Dashboard load. + const allUnsavedPanelsMap = { + ...latestPanels, + ...modifiedPanels, + }; + return convertPanelMapToSavedPanels(allUnsavedPanelsMap); + })(); if (unsavedDashboardState) { unsavedStateForLocator = { query: unsavedDashboardState.query, filters: unsavedDashboardState.filters, controlGroupInput: unsavedDashboardState.controlGroupInput as SerializableControlGroupInput, - panels: - allPanels && Object.keys(allPanels).length > 0 - ? (convertPanelMapToSavedPanels(allPanels) as DashboardLocatorParams['panels']) - : undefined, + panels: allUnsavedPanels as DashboardLocatorParams['panels'], // options useMargins: unsavedDashboardState?.useMargins, From c7d682323ad1d7c0d60119b72a29d2a737864687 Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Fri, 7 Jun 2024 19:39:48 +0200 Subject: [PATCH 06/10] [Serverless][Observability] Use role-based testing - functional (#184823) part of: [#184033](https://github.com/elastic/kibana/issues/184033) ## Summary This PR changes the observability serverless tests to not run with operator privileges. ### How to test - Follow the steps from https://github.com/elastic/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../observability/advanced_settings.ts | 2 +- .../cases/attachment_framework.ts | 2 +- .../observability/cases/configure.ts | 2 +- .../observability/cases/create_case_form.ts | 2 +- .../observability/cases/list_view.ts | 2 +- .../observability/cases/view_case.ts | 2 +- .../observability/infra/header_menu.ts | 2 +- .../observability/infra/hosts_page.ts | 2 +- .../test_suites/observability/infra/infra.ts | 2 +- .../observability/infra/navigation.ts | 2 +- .../observability/infra/node_details.ts | 2 +- .../ml/anomaly_detection_jobs_list.ts | 2 +- .../observability/ml/search_bar_features.ts | 142 ++++++++++-------- .../columns_selection.ts | 2 +- .../custom_control_columns.ts | 2 +- .../data_source_selection_state.ts | 2 +- .../data_source_selector.ts | 2 +- .../observability_logs_explorer/field_list.ts | 2 +- .../filter_controls.ts | 2 +- .../observability_logs_explorer/flyout.ts | 2 +- .../header_menu.ts | 2 +- .../observability_logs_explorer/navigation.ts | 2 +- .../observability/rules/rules_list.ts | 2 +- .../response_ops_docs/cases/list_view.ts | 2 +- .../connectors/server_log_connector.ts | 2 +- .../maintenance_windows/create_window.ts | 2 +- 26 files changed, 101 insertions(+), 91 deletions(-) diff --git a/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts b/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts index d6f2ef6502f2d..0e3c4d65349c9 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/advanced_settings.ts @@ -18,7 +18,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Observability advanced settings', function () { before(async () => { - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('admin'); await pageObjects.common.navigateToApp('settings'); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts index 2dda21ca85215..7719ba01ce8ea 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/attachment_framework.ts @@ -26,7 +26,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Cases persistable attachments', function () { describe('lens visualization', () => { before(async () => { - await svlCommonPage.login(); + await svlCommonPage.loginWithRole('admin'); await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); await kibanaServer.importExport.load( diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts index 7b56c069fec5d..8a966c6b7d135 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/configure.ts @@ -23,7 +23,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Configure Case', function () { before(async () => { - await svlCommonPage.login(); + await svlCommonPage.loginWithRole('admin'); await svlObltNavigation.navigateToLandingPage(); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' }); await header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts index c1b2f1e0068a5..88f2663a10749 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/create_case_form.ts @@ -25,7 +25,7 @@ export default ({ getService, getPageObject }: FtrProviderContext) => { const header = getPageObject('header'); before(async () => { - await svlCommonPage.login(); + await svlCommonPage.loginWithRole('admin'); }); beforeEach(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts index 6009c4557bed6..e98cc99bdec5b 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/list_view.ts @@ -21,7 +21,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Cases list', function () { before(async () => { - await svlCommonPage.login(); + await svlCommonPage.loginWithRole('admin'); await svlObltNavigation.navigateToLandingPage(); await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'observability-overview:cases' }); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts index a4bc060062ad7..5962a5f7c4195 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/cases/view_case.ts @@ -36,7 +36,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { describe('Case View', function () { before(async () => { - await svlCommonPage.login(); + await svlCommonPage.loginWithRole('admin'); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/header_menu.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/header_menu.ts index 0f9ed330c3ad4..e59021dbd7e1a 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/header_menu.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/header_menu.ts @@ -16,7 +16,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Header menu', () => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('viewer'); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts index 22757855e02f8..45aa622281312 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/hosts_page.ts @@ -45,7 +45,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await Promise.all([ esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'), ]); - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('viewer'); await browser.setWindowSize(1600, 1200); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/infra.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/infra.ts index 677611b3e4ef7..0f9ddcef05db6 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/infra.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/infra.ts @@ -31,7 +31,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Infra pages', function () { before(async () => { - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('viewer'); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/navigation.ts index 6f03c8831ce85..6bbb746eb65a6 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/navigation.ts @@ -26,7 +26,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Infra Side Navigation', () => { before(async () => { - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('viewer'); await svlObltNavigation.navigateToLandingPage(); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/infra/node_details.ts b/x-pack/test_serverless/functional/test_suites/observability/infra/node_details.ts index 4decf638741f0..bd56244eb12ba 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/infra/node_details.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/infra/node_details.ts @@ -35,7 +35,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('viewer'); await pageObjects.common.navigateToApp( `metrics/${NODE_DETAILS_PATH}/demo-stack-kubernetes-01` ); diff --git a/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts b/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts index f4d0077f6b73c..e8dddef3eb35a 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/ml/anomaly_detection_jobs_list.ts @@ -19,7 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Error: Failed to delete all indices with pattern [.ml-*] this.tags(['failsOnMKI']); before(async () => { - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('admin'); // Load logstash* data and create dataview for logstash*, logstash-2015.09.22 await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); diff --git a/x-pack/test_serverless/functional/test_suites/observability/ml/search_bar_features.ts b/x-pack/test_serverless/functional/test_suites/observability/ml/search_bar_features.ts index fec1fbd9d3646..15b7d3d76c61a 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/ml/search_bar_features.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/ml/search_bar_features.ts @@ -10,80 +10,90 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['header', 'svlCommonPage', 'svlCommonNavigation']); - const allLabels = [ - { label: 'Machine Learning', expected: true }, - { label: 'Machine Learning / Overview', expected: true }, - { label: 'Machine Learning / Anomaly Detection', expected: true }, - { label: 'Machine Learning / Anomaly Detection / Anomaly explorer', expected: true }, - { label: 'Machine Learning / Anomaly Detection / Single metric viewer', expected: true }, - { label: 'Machine Learning / Data Frame Analytics', expected: false }, - { label: 'Machine Learning / Data Frame Analytics / Results explorer', expected: false }, - { label: 'Machine Learning / Data Frame Analytics / Analytics map', expected: false }, - { label: 'Machine Learning / Model Management', expected: true }, - { label: 'Machine Learning / Model Management / Trained Models', expected: true }, - { label: 'Machine Learning / Model Management / Nodes', expected: false }, - { label: 'Machine Learning / Memory Usage', expected: true }, - { label: 'Machine Learning / Settings', expected: true }, - { label: 'Machine Learning / Settings / Calendars', expected: true }, - { label: 'Machine Learning / Settings / Filter Lists', expected: true }, - { label: 'Machine Learning / AIOps', expected: true }, - { label: 'Machine Learning / AIOps / Log Rate Analysis', expected: true }, - { label: 'Machine Learning / AIOps / Log Pattern Analysis', expected: true }, - { label: 'Machine Learning / AIOps / Change Point Detection', expected: true }, - { label: 'Machine Learning / Notifications', expected: true }, - { label: 'Machine Learning / Data Visualizer', expected: true }, - { label: 'Machine Learning / File Upload', expected: true }, - { label: 'Machine Learning / Index Data Visualizer', expected: true }, - { label: 'Machine Learning / ES|QL Data Visualizer', expected: true }, - { label: 'Machine Learning / Data Drift', expected: true }, - { label: 'Alerts and Insights / Machine Learning', expected: true }, - ]; + const allLabels = { + viewer: [ + { label: 'Machine Learning', expected: true }, + { label: 'Machine Learning / Overview', expected: true }, + { label: 'Machine Learning / Anomaly Detection', expected: true }, + { label: 'Machine Learning / Anomaly Detection / Anomaly explorer', expected: true }, + { label: 'Machine Learning / Anomaly Detection / Single metric viewer', expected: true }, + { label: 'Machine Learning / Data Frame Analytics', expected: false }, + { label: 'Machine Learning / Data Frame Analytics / Results explorer', expected: false }, + { label: 'Machine Learning / Data Frame Analytics / Analytics map', expected: false }, + { label: 'Machine Learning / Model Management', expected: true }, + { label: 'Machine Learning / Model Management / Trained Models', expected: true }, + { label: 'Machine Learning / Model Management / Nodes', expected: false }, + { label: 'Machine Learning / Memory Usage', expected: true }, + { label: 'Machine Learning / Settings', expected: true }, + { label: 'Machine Learning / Settings / Calendars', expected: true }, + { label: 'Machine Learning / Settings / Filter Lists', expected: true }, + { label: 'Machine Learning / AIOps', expected: true }, + { label: 'Machine Learning / AIOps / Log Rate Analysis', expected: true }, + { label: 'Machine Learning / AIOps / Log Pattern Analysis', expected: true }, + { label: 'Machine Learning / AIOps / Change Point Detection', expected: true }, + { label: 'Machine Learning / Notifications', expected: true }, + { label: 'Machine Learning / Data Visualizer', expected: true }, + { label: 'Machine Learning / File Upload', expected: true }, + { label: 'Machine Learning / Index Data Visualizer', expected: true }, + { label: 'Machine Learning / ES|QL Data Visualizer', expected: true }, + { label: 'Machine Learning / Data Drift', expected: true }, + ], + admin: [{ label: 'Alerts and Insights / Machine Learning', expected: true }], + }; describe('Search bar features', () => { - before(async () => { - await PageObjects.svlCommonPage.login(); - }); + ([{ role: 'viewer' }, { role: 'admin' }] as Array<{ role: keyof typeof allLabels }>).forEach( + ({ role }) => { + describe(`user role: ${role}`, () => { + before(async () => { + await PageObjects.svlCommonPage.loginWithRole(role); + }); - after(async () => { - await PageObjects.svlCommonPage.forceLogout(); - }); + after(async () => { + await PageObjects.svlCommonPage.forceLogout(); + }); - describe('list features', () => { - it('has the correct features enabled', async () => { - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.svlCommonNavigation.search.showSearch(); + describe('list features', () => { + it('has the correct features enabled', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.svlCommonNavigation.search.showSearch(); - const expectedLabels = allLabels.filter((l) => l.expected).map((l) => l.label); + const expectedLabels = allLabels[role].filter((l) => l.expected).map((l) => l.label); - for (const expectedLabel of expectedLabels) { - await PageObjects.svlCommonNavigation.search.searchFor(expectedLabel); - const [result] = await PageObjects.svlCommonNavigation.search.getDisplayedResults(); - const label = result?.label; - expect(label).to.eql( - expectedLabel, - `First result should be ${expectedLabel} (got matching items '${label}')` - ); - } - await PageObjects.svlCommonNavigation.search.hideSearch(); - }); + for (const expectedLabel of expectedLabels) { + await PageObjects.svlCommonNavigation.search.searchFor(expectedLabel); + const [result] = await PageObjects.svlCommonNavigation.search.getDisplayedResults(); + const label = result?.label; + expect(label).to.eql( + expectedLabel, + `First result should be ${expectedLabel} (got matching items '${label}')` + ); + } + await PageObjects.svlCommonNavigation.search.hideSearch(); + }); - it('has the correct features disabled', async () => { - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.svlCommonNavigation.search.showSearch(); + it('has the correct features disabled', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.svlCommonNavigation.search.showSearch(); - const notExpectedLabels = allLabels.filter((l) => !l.expected).map((l) => l.label); + const notExpectedLabels = allLabels[role] + .filter((l) => !l.expected) + .map((l) => l.label); - for (const notExpectedLabel of notExpectedLabels) { - await PageObjects.svlCommonNavigation.search.searchFor(notExpectedLabel); - const [result] = await PageObjects.svlCommonNavigation.search.getDisplayedResults(); - const label = result?.label; - expect(label).to.not.eql( - notExpectedLabel, - `First result should not be ${notExpectedLabel} (got matching items '${label}')` - ); - } - await PageObjects.svlCommonNavigation.search.hideSearch(); - }); - }); + for (const notExpectedLabel of notExpectedLabels) { + await PageObjects.svlCommonNavigation.search.searchFor(notExpectedLabel); + const [result] = await PageObjects.svlCommonNavigation.search.getDisplayedResults(); + const label = result?.label; + expect(label).to.not.eql( + notExpectedLabel, + `First result should not be ${notExpectedLabel} (got matching items '${label}')` + ); + } + await PageObjects.svlCommonNavigation.search.hideSearch(); + }); + }); + }); + } + ); }); } diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts index ccf04c2f7c554..14c5de7185310 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts @@ -35,7 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('When the logs explorer loads', () => { before(async () => { await synthtrace.index(generateLogsData({ to })); - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('viewer'); await navigateToLogsExplorer(); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts index a351ed4d54582..6e836f00cc3f2 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts @@ -36,7 +36,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('When the logs explorer loads', () => { before(async () => { await synthtrace.index(generateLogsData({ to })); - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('viewer'); await navigateToLogsExplorer(); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts index 5fe928c5d3d5e..871c0151432a2 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts @@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('dataSourceSelection initialization and update', () => { before(async () => { - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('viewer'); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts index df749f2d4f9dd..3bb012c81311f 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts @@ -40,7 +40,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // TimeoutError: Waiting for element to be located By(css selector, [data-test-subj="dataSourceSelectorPopoverButton"]) this.tags(['failsOnMKI']); before(async () => { - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('viewer'); await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts index 828c4d4c61dd1..5c5cd85f624a4 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts @@ -35,7 +35,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('When virtual columns loads', () => { before(async () => { await synthtrace.index(generateLogsData({ from, to })); - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('viewer'); await navigateToLogsExplorer(); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts index 9cf4629e89a90..0dc479b391800 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts @@ -14,7 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Filter controls customization', () => { before('initialize tests', async () => { await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('viewer'); }); after('clean up archives', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts index 8709f6191cb01..2e181f1a7d9b4 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts @@ -45,7 +45,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { NAMESPACE ); await PageObjects.observabilityLogsExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('viewer'); }); beforeEach(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts index 23d2e7729e2f4..a61c1afbfb618 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts @@ -31,7 +31,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' ); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('viewer'); await PageObjects.observabilityLogsExplorer.navigateTo(); await PageObjects.header.waitUntilLoadingHasFinished(); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/navigation.ts index 88852f1f7b5b7..076e4701c91fc 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/navigation.ts @@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); await synthtrace.index(generateLogsData({ to })); - await PageObjects.svlCommonPage.login(); + await PageObjects.svlCommonPage.loginWithRole('viewer'); await navigateToLogsExplorer(); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts index 0e486b4947d80..aa2ba3ff72fda 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts @@ -84,7 +84,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { before(async () => { roleAuthc = await svlUserManager.createApiKeyForRole('admin'); internalReqHeader = svlCommonApi.getInternalRequestHeader(); - await svlCommonPage.login(); + await svlCommonPage.loginWithRole('admin'); await svlObltNavigation.navigateToLandingPage(); await svlCommonNavigation.sidenav.clickLink({ text: 'Alerts' }); await testSubjects.click('manageRulesPageButton'); diff --git a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/cases/list_view.ts b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/cases/list_view.ts index 3cfbd7134b792..b4666e7807342 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/cases/list_view.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/cases/list_view.ts @@ -63,7 +63,7 @@ export default function ({ getPageObject, getPageObjects, getService }: FtrProvi }); beforeEach(async () => { - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('admin'); }); it('cases list screenshot', async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/connectors/server_log_connector.ts b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/connectors/server_log_connector.ts index 7eed7d5cbbd26..a38807f4537ca 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/connectors/server_log_connector.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/connectors/server_log_connector.ts @@ -15,7 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('server log connector', function () { beforeEach(async () => { - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('admin'); }); after(async () => { diff --git a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/maintenance_windows/create_window.ts b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/maintenance_windows/create_window.ts index db43127a0adcf..73f431eeb6ccb 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/maintenance_windows/create_window.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/screenshot_creation/response_ops_docs/maintenance_windows/create_window.ts @@ -16,7 +16,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('create window', function () { beforeEach(async () => { - await pageObjects.svlCommonPage.login(); + await pageObjects.svlCommonPage.loginWithRole('admin'); }); after(async () => { From 1ec9412e82311a1a88c9373ffdbc82a1d2928dc2 Mon Sep 17 00:00:00 2001 From: Joe McElroy Date: Fri, 7 Jun 2024 18:54:08 +0100 Subject: [PATCH 07/10] Disable import of langtrace (#185029) ## Summary Disables the import for langtrace. When imported, it immediately instruments openai lang client. This causes an issue where the completion.create return is no longer a native ReadableStream and we cannot tee off for pompt token counting ([here](https://github.com/elastic/kibana/blob/main/x-pack/plugins/stack_connectors/server/connector_types/openai/openai.ts#L309)) Under @dgieselaar request, i've commented out the import for now. --- .../client/instrumentation/init_langtrace.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/instrumentation/init_langtrace.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/instrumentation/init_langtrace.ts index 77f39c470f43b..9a198cdc902a4 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/instrumentation/init_langtrace.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/service/client/instrumentation/init_langtrace.ts @@ -5,19 +5,19 @@ * 2.0. */ -import { init } from '@langtrase/typescript-sdk'; +// import { init } from '@langtrase/typescript-sdk'; export function initLangtrace() { const apiKey = process.env.LANGTRACE_API_KEY; const apiHost = process.env.LANGTRACE_API_HOST; if (apiKey && apiHost) { - init({ - api_host: apiHost, - api_key: apiKey, - write_to_langtrace_cloud: true, - disable_instrumentations: { - only: [], - }, - }); + // init({ + // api_host: apiHost, + // api_key: apiKey, + // write_to_langtrace_cloud: true, + // disable_instrumentations: { + // only: [], + // }, + // }); } } From 1e197cf7185f902e07d1cd1a2db1024b7546a82c Mon Sep 17 00:00:00 2001 From: Ignacio Rivas Date: Fri, 7 Jun 2024 20:35:56 +0200 Subject: [PATCH 08/10] [Ingest Pipelines] Add unsaved changes prompt (#183699) --- .github/CODEOWNERS | 1 + .i18nrc.json | 1 + package.json | 1 + packages/kbn-unsaved-changes-prompt/README.md | 30 ++++++ packages/kbn-unsaved-changes-prompt/index.ts | 9 ++ .../kbn-unsaved-changes-prompt/jest.config.js | 13 +++ .../kbn-unsaved-changes-prompt/kibana.jsonc | 5 + .../kbn-unsaved-changes-prompt/package.json | 6 ++ .../src/unsaved_changes_prompt/index.ts | 9 ++ .../unsaved_changes_prompt.test.tsx | 65 +++++++++++++ .../unsaved_changes_prompt.tsx | 96 +++++++++++++++++++ .../kbn-unsaved-changes-prompt/tsconfig.json | 14 +++ tsconfig.base.json | 2 + .../helpers/setup_environment.tsx | 4 + .../pipeline_form/pipeline_form.tsx | 64 ++++++++++++- .../components/pipeline_form/utils.test.ts | 39 ++++++++ .../components/pipeline_form/utils.ts | 35 +++++++ .../public/application/index.tsx | 4 +- .../application/mount_management_section.ts | 4 +- .../ingest_pipelines/public/shared_imports.ts | 1 + x-pack/plugins/ingest_pipelines/tsconfig.json | 4 +- .../apps/ingest_pipelines/ingest_pipelines.ts | 16 ++++ yarn.lock | 4 + 23 files changed, 421 insertions(+), 6 deletions(-) create mode 100644 packages/kbn-unsaved-changes-prompt/README.md create mode 100644 packages/kbn-unsaved-changes-prompt/index.ts create mode 100644 packages/kbn-unsaved-changes-prompt/jest.config.js create mode 100644 packages/kbn-unsaved-changes-prompt/kibana.jsonc create mode 100644 packages/kbn-unsaved-changes-prompt/package.json create mode 100644 packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/index.ts create mode 100644 packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx create mode 100644 packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.tsx create mode 100644 packages/kbn-unsaved-changes-prompt/tsconfig.json create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/utils.test.ts create mode 100644 x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/utils.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6b82a71d27bbf..dc4a36124cad2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -893,6 +893,7 @@ examples/unified_field_list_examples @elastic/kibana-data-discovery src/plugins/unified_histogram @elastic/kibana-data-discovery src/plugins/unified_search @elastic/kibana-visualizations packages/kbn-unsaved-changes-badge @elastic/kibana-data-discovery +packages/kbn-unsaved-changes-prompt @elastic/kibana-management x-pack/plugins/upgrade_assistant @elastic/kibana-management x-pack/plugins/observability_solution/uptime @elastic/obs-ux-infra_services-team x-pack/plugins/drilldowns/url_drilldown @elastic/appex-sharedux diff --git a/.i18nrc.json b/.i18nrc.json index 4d71e47159e1b..0053dc5d2ad51 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -148,6 +148,7 @@ "unifiedHistogram": "src/plugins/unified_histogram", "unifiedDataTable": "packages/kbn-unified-data-table", "unsavedChangesBadge": "packages/kbn-unsaved-changes-badge", + "unsavedChangesPrompt": "packages/kbn-unsaved-changes-prompt", "managedContentBadge": "packages/kbn-managed-content-badge" }, "translations": [] diff --git a/package.json b/package.json index 351784fa9a257..4949f98aca52b 100644 --- a/package.json +++ b/package.json @@ -889,6 +889,7 @@ "@kbn/unified-histogram-plugin": "link:src/plugins/unified_histogram", "@kbn/unified-search-plugin": "link:src/plugins/unified_search", "@kbn/unsaved-changes-badge": "link:packages/kbn-unsaved-changes-badge", + "@kbn/unsaved-changes-prompt": "link:packages/kbn-unsaved-changes-prompt", "@kbn/upgrade-assistant-plugin": "link:x-pack/plugins/upgrade_assistant", "@kbn/uptime-plugin": "link:x-pack/plugins/observability_solution/uptime", "@kbn/url-drilldown-plugin": "link:x-pack/plugins/drilldowns/url_drilldown", diff --git a/packages/kbn-unsaved-changes-prompt/README.md b/packages/kbn-unsaved-changes-prompt/README.md new file mode 100644 index 0000000000000..b5bdf1c361e37 --- /dev/null +++ b/packages/kbn-unsaved-changes-prompt/README.md @@ -0,0 +1,30 @@ +# @kbn/unsaved-changes-prompt + +The useUnsavedChangesPrompt function is a custom React hook that prompts users with +a confirmation dialog when they try to leave a page with unsaved changes. It blocks +navigation and shows a dialog using the provided openConfirm function. If the user +confirms, it navigates away; otherwise, it cancels the navigation, ensuring unsaved +changes are not lost. + + +```typescript +import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt'; + +export const SampleForm = ({ servicesForUnsavedChangesPrompt }) => { + const { form } = useForm(); + const isFormDirty = useFormIsModified({ form }); + + useUnsavedChangesPrompt({ + hasUnsavedChanges: isFormDirty, + ...servicesForUnsavedChangesPrompt, + }); + + return ( + <> +
+ .... +
+ + ); +}; +``` \ No newline at end of file diff --git a/packages/kbn-unsaved-changes-prompt/index.ts b/packages/kbn-unsaved-changes-prompt/index.ts new file mode 100644 index 0000000000000..0e572e40f86a4 --- /dev/null +++ b/packages/kbn-unsaved-changes-prompt/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { useUnsavedChangesPrompt } from './src/unsaved_changes_prompt'; diff --git a/packages/kbn-unsaved-changes-prompt/jest.config.js b/packages/kbn-unsaved-changes-prompt/jest.config.js new file mode 100644 index 0000000000000..0db09e6ffcb0b --- /dev/null +++ b/packages/kbn-unsaved-changes-prompt/jest.config.js @@ -0,0 +1,13 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-unsaved-changes-prompt'], +}; diff --git a/packages/kbn-unsaved-changes-prompt/kibana.jsonc b/packages/kbn-unsaved-changes-prompt/kibana.jsonc new file mode 100644 index 0000000000000..3d3f2f6199dd6 --- /dev/null +++ b/packages/kbn-unsaved-changes-prompt/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/unsaved-changes-prompt", + "owner": "@elastic/kibana-management" +} diff --git a/packages/kbn-unsaved-changes-prompt/package.json b/packages/kbn-unsaved-changes-prompt/package.json new file mode 100644 index 0000000000000..250b1068ab373 --- /dev/null +++ b/packages/kbn-unsaved-changes-prompt/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/unsaved-changes-prompt", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/index.ts b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/index.ts new file mode 100644 index 0000000000000..7520607519389 --- /dev/null +++ b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/index.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export { useUnsavedChangesPrompt } from './unsaved_changes_prompt'; diff --git a/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx new file mode 100644 index 0000000000000..bbd14526a0ac3 --- /dev/null +++ b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.test.tsx @@ -0,0 +1,65 @@ +/* + * 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 { createMemoryHistory } from 'history'; +import { renderHook, act } from '@testing-library/react-hooks'; + +import { coreMock } from '@kbn/core/public/mocks'; +import { CoreScopedHistory } from '@kbn/core/public'; + +import { useUnsavedChangesPrompt } from './unsaved_changes_prompt'; + +const basePath = '/mock'; +const memoryHistory = createMemoryHistory({ initialEntries: [basePath] }); +const history = new CoreScopedHistory(memoryHistory, basePath); +const coreStart = coreMock.createStart(); +const navigateToUrl = jest.fn().mockImplementation(async (url) => { + history.push(url); +}); + +describe('useUnsavedChangesPrompt', () => { + it('should not block if not edited', () => { + renderHook(() => + useUnsavedChangesPrompt({ + hasUnsavedChanges: false, + http: coreStart.http, + openConfirm: coreStart.overlays.openConfirm, + history, + navigateToUrl, + }) + ); + + act(() => history.push('/test')); + + expect(history.location.pathname).toBe('/test'); + expect(history.location.search).toBe(''); + expect(coreStart.overlays.openConfirm).not.toBeCalled(); + }); + + it('should block if edited', async () => { + coreStart.overlays.openConfirm.mockResolvedValue(true); + + renderHook(() => + useUnsavedChangesPrompt({ + hasUnsavedChanges: true, + http: coreStart.http, + openConfirm: coreStart.overlays.openConfirm, + history, + navigateToUrl, + }) + ); + + act(() => history.push('/test')); + + // needed because we have an async useEffect + await act(() => new Promise((resolve) => resolve())); + + expect(navigateToUrl).toBeCalledWith('/mock/test', expect.anything()); + expect(coreStart.overlays.openConfirm).toBeCalled(); + }); +}); diff --git a/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.tsx b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.tsx new file mode 100644 index 0000000000000..20d815cffd8b3 --- /dev/null +++ b/packages/kbn-unsaved-changes-prompt/src/unsaved_changes_prompt/unsaved_changes_prompt.tsx @@ -0,0 +1,96 @@ +/* + * 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 { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { ApplicationStart, ScopedHistory, OverlayStart, HttpStart } from '@kbn/core/public'; + +const DEFAULT_BODY_TEXT = i18n.translate('unsavedChangesPrompt.defaultModalText', { + defaultMessage: `The data will be lost if you leave this page without saving the changes.`, +}); + +const DEFAULT_TITLE_TEXT = i18n.translate('unsavedChangesPrompt.defaultModalTitle', { + defaultMessage: 'Discard unsaved changes?', +}); + +const DEFAULT_CANCEL_BUTTON = i18n.translate('unsavedChangesPrompt.defaultModalCancel', { + defaultMessage: 'Keep editing', +}); + +const DEFAULT_CONFIRM_BUTTON = i18n.translate('unsavedChangesPrompt.defaultModalConfirm', { + defaultMessage: 'Leave page', +}); + +interface Props { + hasUnsavedChanges: boolean; + http: HttpStart; + openConfirm: OverlayStart['openConfirm']; + history: ScopedHistory; + navigateToUrl: ApplicationStart['navigateToUrl']; + titleText?: string; + messageText?: string; + cancelButtonText?: string; + confirmButtonText?: string; +} + +export const useUnsavedChangesPrompt = ({ + hasUnsavedChanges, + openConfirm, + history, + http, + navigateToUrl, + // Provide overrides for confirm dialog + messageText = DEFAULT_BODY_TEXT, + titleText = DEFAULT_TITLE_TEXT, + confirmButtonText = DEFAULT_CONFIRM_BUTTON, + cancelButtonText = DEFAULT_CANCEL_BUTTON, +}: Props) => { + useEffect(() => { + if (!hasUnsavedChanges) { + return; + } + + const unblock = history.block((state) => { + async function confirmAsync() { + const confirmResponse = await openConfirm(messageText, { + title: titleText, + cancelButtonText, + confirmButtonText, + 'data-test-subj': 'navigationBlockConfirmModal', + }); + + if (confirmResponse) { + // Compute the URL we want to redirect to + const url = http.basePath.prepend(state.pathname) + state.hash + state.search; + // Unload history block + unblock(); + // Navigate away + navigateToUrl(url, { + state: state.state, + }); + } + } + + confirmAsync(); + return false; + }); + + return unblock; + }, [ + history, + hasUnsavedChanges, + openConfirm, + navigateToUrl, + http.basePath, + titleText, + cancelButtonText, + confirmButtonText, + messageText, + ]); +}; diff --git a/packages/kbn-unsaved-changes-prompt/tsconfig.json b/packages/kbn-unsaved-changes-prompt/tsconfig.json new file mode 100644 index 0000000000000..a8d349af93f84 --- /dev/null +++ b/packages/kbn-unsaved-changes-prompt/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": ["*.ts", "src/**/*"], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/i18n", + "@kbn/core" + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 3f09377aa89a5..31f05377e4d4c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1780,6 +1780,8 @@ "@kbn/unified-search-plugin/*": ["src/plugins/unified_search/*"], "@kbn/unsaved-changes-badge": ["packages/kbn-unsaved-changes-badge"], "@kbn/unsaved-changes-badge/*": ["packages/kbn-unsaved-changes-badge/*"], + "@kbn/unsaved-changes-prompt": ["packages/kbn-unsaved-changes-prompt"], + "@kbn/unsaved-changes-prompt/*": ["packages/kbn-unsaved-changes-prompt/*"], "@kbn/upgrade-assistant-plugin": ["x-pack/plugins/upgrade_assistant"], "@kbn/upgrade-assistant-plugin/*": ["x-pack/plugins/upgrade_assistant/*"], "@kbn/uptime-plugin": ["x-pack/plugins/observability_solution/uptime"], diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx index 01efbc3063870..58701ffb1dd64 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/setup_environment.tsx @@ -19,7 +19,9 @@ import { scopedHistoryMock, uiSettingsServiceMock, applicationServiceMock, + overlayServiceMock, } from '@kbn/core/public/mocks'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/public/mocks'; @@ -66,6 +68,8 @@ const appServices = { share: { url: new MockUrlService(), }, + overlays: overlayServiceMock.createStartContract(), + http: httpServiceMock.createStartContract({ basePath: '/mock' }), }; export const setupEnvironment = () => { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx index eaeeecf71415f..30637d00f495c 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form.tsx @@ -6,14 +6,18 @@ */ import React, { useState, useCallback, useRef } from 'react'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { useForm, Form, FormConfig } from '../../../shared_imports'; +import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt'; import { Pipeline, Processor } from '../../../../common/types'; +import { useForm, Form, FormConfig, useFormIsModified } from '../../../shared_imports'; +import { useKibana } from '../../../shared_imports'; import { OnUpdateHandlerArg, OnUpdateHandler } from '../pipeline_editor'; +import { deepEqualIgnoreUndefined } from './utils'; import { PipelineRequestFlyout } from './pipeline_request_flyout'; import { PipelineFormFields } from './pipeline_form_fields'; import { PipelineFormError } from './pipeline_form_error'; @@ -48,7 +52,16 @@ export const PipelineForm: React.FunctionComponent = ({ onCancel, canEditName, }) => { + const { + overlays, + history, + application: { navigateToUrl }, + http, + } = useKibana().services; + const [isRequestVisible, setIsRequestVisible] = useState(false); + const [areProcessorsDirty, setAreProcessorsDirty] = useState(false); + const [hasSubmittedForm, setHasSubmittedForm] = useState(false); const { processors: initialProcessors, @@ -74,6 +87,11 @@ export const PipelineForm: React.FunctionComponent = ({ if (processorStateRef.current) { const state = processorStateRef.current; if (await state.validate()) { + // We only want to show unsaved changed prompts to the user when the form + // hasnt been submitted. + setHasSubmittedForm(true); + + // Save the form state, this will also trigger a redirect to pipelines list onSave({ ...formData, ...state.getData() }); } } @@ -85,6 +103,8 @@ export const PipelineForm: React.FunctionComponent = ({ onSubmit: handleSave, }); + const isFormDirty = useFormIsModified({ form }); + const onEditorFlyoutOpen = useCallback(() => { setIsRequestVisible(false); }, [setIsRequestVisible]); @@ -107,10 +127,48 @@ export const PipelineForm: React.FunctionComponent = ({ ); const onProcessorsChangeHandler = useCallback( - (arg) => (processorStateRef.current = arg), - [] + (arg) => { + processorStateRef.current = arg; + + const currentProcessorsState = processorStateRef.current?.getData(); + + // Calculate if the current processor state has changed compared to the + // initial processors state. + setAreProcessorsDirty( + !deepEqualIgnoreUndefined( + { + processors: processorsState?.processors || [], + onFailure: processorsState?.onFailure || [], + }, + { + processors: currentProcessorsState?.processors || [], + onFailure: currentProcessorsState?.on_failure || [], + } + ) + ); + }, + [processorsState] ); + /* + We need to check if the form is dirty and also if the form has been submitted. + Because on form submission we also redirect the user to the pipelines list, + and this could otherwise trigger an unwanted unsaved changes prompt. + */ + useUnsavedChangesPrompt({ + titleText: i18n.translate('xpack.ingestPipelines.form.unsavedPrompt.title', { + defaultMessage: `Exit pipeline creation without saving changes?`, + }), + messageText: i18n.translate('xpack.ingestPipelines.form.unsavedPrompt.body', { + defaultMessage: `The data will be lost if you leave this page without saving the pipeline changes`, + }), + hasUnsavedChanges: (isFormDirty || areProcessorsDirty) && !hasSubmittedForm, + openConfirm: overlays.openConfirm, + history, + http, + navigateToUrl, + }); + return ( <>
{ + const testObjectA = Object.freeze({ + a: 1, + b: { + c: 2, + d: undefined, + }, + }); + + const testObjectB = Object.freeze({ + a: 1, + b: { + c: 2, + d: undefined, + }, + }); + + it('knows how to remove undefined values', () => { + expect(removeUndefinedValues(testObjectA)).toStrictEqual({ + a: 1, + b: { + c: 2, + }, + }); + }); + + it('knows how to compare two objects and see if they are equal ignoring undefined values', () => { + expect(deepEqualIgnoreUndefined(testObjectA, testObjectB)).toBe(true); + }); +}); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/utils.ts b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/utils.ts new file mode 100644 index 0000000000000..df6b16b3ef2b7 --- /dev/null +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_form/utils.ts @@ -0,0 +1,35 @@ +/* + * 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 { transform, isObject, isEqual } from 'lodash'; + +export function removeUndefinedValues(obj: object) { + // If the input is an object, recursively clean each key-value pair. + if (isObject(obj)) { + // Use transform to iterate over the object and build a new result object. + return transform( + obj, + (result: Record, value: any, key: string) => { + const cleanedValue = removeUndefinedValues(value); + if (cleanedValue !== undefined) { + // Only add the key-value pair if the value is not undefined. + result[key as keyof typeof obj] = cleanedValue; + } + }, + {} + ); + } + return obj; +} + +export function deepEqualIgnoreUndefined(obj1: object, obj2: object) { + // Clean both objects by removing undefined values. + const cleanedObj1 = removeUndefinedValues(obj1); + const cleanedObj2 = removeUndefinedValues(obj2); + + return isEqual(cleanedObj1, cleanedObj2); +} diff --git a/x-pack/plugins/ingest_pipelines/public/application/index.tsx b/x-pack/plugins/ingest_pipelines/public/application/index.tsx index 982fc487957d1..6ec215db8b043 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/index.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/index.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { ApplicationStart } from '@kbn/core/public'; -import { NotificationsSetup, IUiSettingsClient } from '@kbn/core/public'; +import { NotificationsSetup, IUiSettingsClient, OverlayStart, HttpStart } from '@kbn/core/public'; import { ManagementAppMountParams } from '@kbn/management-plugin/public'; import type { ConsolePluginStart } from '@kbn/console-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; @@ -48,6 +48,8 @@ export interface AppServices { application: ApplicationStart; license: ILicense | null; consolePlugin?: ConsolePluginStart; + overlays: OverlayStart; + http: HttpStart; } type StartServices = Pick; diff --git a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts index ea6a229cfb73c..4b6ca4f35cd3f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts +++ b/x-pack/plugins/ingest_pipelines/public/application/mount_management_section.ts @@ -28,7 +28,7 @@ export async function mountManagementSection( ) { const { element, setBreadcrumbs, history, license } = params; const [coreStart, depsStart] = await getStartServices(); - const { docLinks, application, executionContext } = coreStart; + const { docLinks, application, executionContext, overlays } = coreStart; documentationService.setup(docLinks); breadcrumbService.setup(setBreadcrumbs); @@ -49,6 +49,8 @@ export async function mountManagementSection( executionContext, license, consolePlugin: depsStart.console, + overlays, + http, }; return renderApp(element, services, { ...coreStart, http }); diff --git a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts index 420da38be6c54..0e24e3c984af9 100644 --- a/x-pack/plugins/ingest_pipelines/public/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/public/shared_imports.ts @@ -62,6 +62,7 @@ export { FormDataProvider, getFieldValidityAndErrorMessage, useFormData, + useFormIsModified, } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib'; export { fieldFormatters, fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers'; diff --git a/x-pack/plugins/ingest_pipelines/tsconfig.json b/x-pack/plugins/ingest_pipelines/tsconfig.json index 8d14d5f11412d..1f8fd6eb5f758 100644 --- a/x-pack/plugins/ingest_pipelines/tsconfig.json +++ b/x-pack/plugins/ingest_pipelines/tsconfig.json @@ -33,7 +33,9 @@ "@kbn/code-editor", "@kbn/react-kibana-context-render", "@kbn/console-plugin", - "@kbn/react-kibana-context-theme" + "@kbn/react-kibana-context-theme", + "@kbn/unsaved-changes-prompt", + "@kbn/core-http-browser-mocks" ], "exclude": [ "target/**/*", diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts index c6f1eb4d189a0..b4a6bfe60e010 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts @@ -133,6 +133,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); + it('Shows a prompt when trying to navigate away from the creation form when the form is dirty', async () => { + // Navigate to creation flow + await testSubjects.click('createPipelineDropdown'); + await testSubjects.click('createNewPipeline'); + + // Fill in the form with some data + await testSubjects.setValue('nameField > input', 'test_name'); + await testSubjects.setValue('descriptionField > input', 'test_description'); + + // Try to navigate to another page + await testSubjects.click('logo'); + + // Since the form is now dirty it should trigger a confirmation prompt + expect(await testSubjects.exists('navigationBlockConfirmModal')).to.be(true); + }); + describe('Create pipeline', () => { afterEach(async () => { // Delete the pipeline that was created diff --git a/yarn.lock b/yarn.lock index db126a34a2eef..b2c096ed6f9ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6728,6 +6728,10 @@ version "0.0.0" uid "" +"@kbn/unsaved-changes-prompt@link:packages/kbn-unsaved-changes-prompt": + version "0.0.0" + uid "" + "@kbn/upgrade-assistant-plugin@link:x-pack/plugins/upgrade_assistant": version "0.0.0" uid "" From 8f9630b0b9748d1e56435e6088c8e6b1ae4adcd3 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 7 Jun 2024 14:35:34 -0500 Subject: [PATCH 09/10] [ci] Remove defend workflows from on-merge-unsupported (#185047) This is already in the on-merge pipeline. --- .../pipelines/on_merge_unsupported_ftrs.yml | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml index 24fc59fdbe26a..c7ac27e032bd8 100644 --- a/.buildkite/pipelines/on_merge_unsupported_ftrs.yml +++ b/.buildkite/pipelines/on_merge_unsupported_ftrs.yml @@ -80,39 +80,3 @@ steps: limit: 3 - exit_status: '*' limit: 1 - - - command: .buildkite/scripts/steps/functional/defend_workflows.sh - label: 'Defend Workflows Cypress Tests' - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-prod - provider: gcp - enableNestedVirtualization: true - localSsdInterface: nvme - localSsds: 1 - machineType: n2-standard-4 - depends_on: build - timeout_in_minutes: 60 - parallelism: 20 - retry: - automatic: - - exit_status: '-1' - limit: 1 - - - command: .buildkite/scripts/steps/functional/defend_workflows_serverless.sh - label: 'Defend Workflows Cypress Tests on Serverless' - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-prod - provider: gcp - enableNestedVirtualization: true - localSsdInterface: nvme - localSsds: 1 - machineType: n2-standard-4 - depends_on: build - timeout_in_minutes: 60 - parallelism: 14 - retry: - automatic: - - exit_status: '-1' - limit: 1 From 75d6815f9293c1cfc946f9e7b16d316f0ba0eb2f Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Fri, 7 Jun 2024 22:44:37 +0300 Subject: [PATCH 10/10] [Storybook][i18n] initialize i18n with english locale (#185033) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary We added a check to ensure that `i18n` is initialized on the browser before any javascript code uses `i18n`. We need to do the same in the storybook so it works properly. To summarize in a couple of pictures 😄 **Before PR:** ![image](https://github.com/elastic/kibana/assets/6191849/3a598dd9-963d-4f4a-aa2a-d98a3d7f379a) **After PR:** ![image](https://github.com/elastic/kibana/assets/6191849/e0f0b4a7-69f4-4eb1-b5b6-14bdfa716caf) cc @petrklapka --- packages/kbn-storybook/src/lib/decorators.tsx | 7 +++++-- packages/kbn-storybook/tsconfig.json | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/kbn-storybook/src/lib/decorators.tsx b/packages/kbn-storybook/src/lib/decorators.tsx index 8693d55faac61..ff10c78fe4589 100644 --- a/packages/kbn-storybook/src/lib/decorators.tsx +++ b/packages/kbn-storybook/src/lib/decorators.tsx @@ -18,10 +18,11 @@ import { CoreTheme } from '@kbn/core-theme-browser'; import { I18nStart } from '@kbn/core-i18n-browser'; import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser'; import { KibanaRootContextProvider } from '@kbn/react-kibana-context-root'; +import { i18n } from '@kbn/i18n'; const theme$ = new BehaviorSubject({ darkMode: false }); -const i18n: I18nStart = { +const i18nStart: I18nStart = { Context: ({ children }) => {children}, }; @@ -36,6 +37,8 @@ const analytics: AnalyticsServiceStart = { * `globals` provided by the Storybook theme switcher to set the `colorMode`. */ const KibanaContextDecorator: DecoratorFn = (storyFn, { globals }) => { + // TODO: Add a switcher to see components in other locales or pseudo locale + i18n.init({ locale: 'en', messages: {} }); const colorMode = globals.euiTheme === 'v8.dark' ? 'dark' : 'light'; useEffect(() => { @@ -43,7 +46,7 @@ const KibanaContextDecorator: DecoratorFn = (storyFn, { globals }) => { }, [colorMode]); return ( - + {storyFn()} ); diff --git a/packages/kbn-storybook/tsconfig.json b/packages/kbn-storybook/tsconfig.json index 97d423551115d..ab513c1d02c0c 100644 --- a/packages/kbn-storybook/tsconfig.json +++ b/packages/kbn-storybook/tsconfig.json @@ -17,6 +17,7 @@ "@kbn/repo-info", "@kbn/dev-cli-runner", "@kbn/core-theme-browser", + "@kbn/i18n", "@kbn/i18n-react", "@kbn/core-i18n-browser", "@kbn/react-kibana-context-root",