From 9fa409fc0e96215a8380450042faf6a671dd125f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= Date: Sat, 20 Nov 2021 15:18:59 +0000 Subject: [PATCH 01/11] =?UTF-8?q?[main]=C2=A0[UA]=20ES=20deprecation=20log?= =?UTF-8?q?s=20in=20its=20own=20page=20(#118659)=20(#119134)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/doc_links/doc_links_service.ts | 1 + .../components/with_privileges.tsx | 2 +- .../translations/translations/ja-JP.json | 8 +- .../translations/translations/zh-CN.json | 4 +- .../app/cluster_upgrade.test.tsx | 9 +- .../es_deprecation_logs.helpers.ts | 80 ++++++++ .../es_deprecation_logs.test.tsx} | 116 ++++------- .../overview/overview.helpers.ts | 33 --- .../public/application/app.tsx | 9 +- .../es_deprecation_logs.tsx | 74 +++++++ .../_deprecation_logging_toggle.scss | 0 .../deprecation_logging_toggle.tsx | 0 .../deprecation_logging_toggle/index.ts | 0 .../deprecations_count_checkpoint.tsx | 14 +- .../deprecations_count_checkpoint/index.ts | 0 .../external_links.test.ts | 0 .../fix_deprecation_logs}/external_links.tsx | 0 .../fix_deprecation_logs.tsx} | 57 ++---- .../fix_deprecation_logs/index.ts | 8 + .../use_deprecation_logging.ts | 0 .../index.ts | 2 +- .../public/application/components/index.ts | 1 + .../fix_issues_step/fix_issues_step.tsx | 53 ++++- .../components/overview/overview.tsx | 22 +- .../public/application/lib/breadcrumbs.ts | 16 +- .../public/application/lib/ui_metric.ts | 1 + .../accessibility/apps/upgrade_assistant.ts | 188 ++++++++---------- .../es_deprecation_logs_page.ts | 45 +++++ .../apps/upgrade_assistant/index.ts | 1 + .../apps/upgrade_assistant/overview_page.ts | 31 --- .../page_objects/upgrade_assistant_page.ts | 15 ++ 31 files changed, 459 insertions(+), 331 deletions(-) create mode 100644 x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.helpers.ts rename x-pack/plugins/upgrade_assistant/__jest__/client_integration/{overview/fix_logs_step/fix_logs_step.test.tsx => es_deprecation_logs/es_deprecation_logs.test.tsx} (80%) create mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/es_deprecation_logs.tsx rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step => es_deprecation_logs/fix_deprecation_logs}/deprecation_logging_toggle/_deprecation_logging_toggle.scss (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step => es_deprecation_logs/fix_deprecation_logs}/deprecation_logging_toggle/deprecation_logging_toggle.tsx (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step => es_deprecation_logs/fix_deprecation_logs}/deprecation_logging_toggle/index.ts (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step => es_deprecation_logs/fix_deprecation_logs}/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx (89%) rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step => es_deprecation_logs/fix_deprecation_logs}/deprecations_count_checkpoint/index.ts (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step => es_deprecation_logs/fix_deprecation_logs}/external_links.test.ts (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step => es_deprecation_logs/fix_deprecation_logs}/external_links.tsx (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step/fix_logs_step.tsx => es_deprecation_logs/fix_deprecation_logs/fix_deprecation_logs.tsx} (80%) create mode 100644 x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/index.ts rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step => es_deprecation_logs/fix_deprecation_logs}/use_deprecation_logging.ts (100%) rename x-pack/plugins/upgrade_assistant/public/application/components/{overview/fix_logs_step => es_deprecation_logs}/index.ts (81%) create mode 100644 x-pack/test/functional/apps/upgrade_assistant/es_deprecation_logs_page.ts diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 6b6ceeddee68e..f97ec152aa5d7 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -225,6 +225,7 @@ export class DocLinksService { mappingTermVector: `${ELASTICSEARCH_DOCS}term-vector.html`, mappingTypesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`, migrateIndexAllocationFilters: `${ELASTICSEARCH_DOCS}migrate-index-allocation-filters.html`, + migrationApiDeprecation: `${ELASTICSEARCH_DOCS}migration-api-deprecation.html`, nodeRoles: `${ELASTICSEARCH_DOCS}modules-node.html#node-roles`, releaseHighlights: `${ELASTICSEARCH_DOCS}release-highlights.html`, remoteClusters: `${ELASTICSEARCH_DOCS}remote-clusters.html`, diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx index 6485bd7f45e55..16dffdb5d267d 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/authorization/components/with_privileges.tsx @@ -22,7 +22,7 @@ interface Props { isLoading: boolean; hasPrivileges: boolean; privilegesMissing: MissingPrivileges; - }) => JSX.Element; + }) => JSX.Element | null; } type Privilege = [string, string]; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5645ebfebf34e..bf8bb82faf706 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17555,7 +17555,7 @@ "xpack.ml.ruleEditor.scopeSection.noPermissionToViewFilterListsTitle": "フィルターリストを表示するパーミッションがありません", "xpack.ml.ruleEditor.scopeSection.scopeTitle": "範囲", "xpack.ml.ruleEditor.selectRuleAction.createRuleLinkText": "ルールを作成", - "xpack.ml.ruleEditor.selectRuleAction.orText": "OR ", + "xpack.ml.ruleEditor.selectRuleAction.orText": "OR ", "xpack.ml.ruleEditor.typicalAppliesTypeText": "通常", "xpack.ml.sampleDataLinkLabel": "ML ジョブ", "xpack.ml.settings.anomalyDetection.anomalyDetectionTitle": "異常検知", @@ -23489,9 +23489,9 @@ "xpack.securitySolution.open.timeline.showingLabel": "表示中:", "xpack.securitySolution.open.timeline.singleTemplateLabel": "テンプレート", "xpack.securitySolution.open.timeline.singleTimelineLabel": "タイムライン", - "xpack.securitySolution.open.timeline.successfullyDeletedTimelinesTitle": "{totalTimelines, plural, =0 {すべてのタイムライン} other {{totalTimelines} 個のタイムライン}}の削除が正常に完了しました", + "xpack.securitySolution.open.timeline.successfullyDeletedTimelinesTitle": "{totalTimelines, plural, =0 {すべてのタイムライン} other {{totalTimelines} 個のタイムライン}}の削除が正常に完了しました", "xpack.securitySolution.open.timeline.successfullyDeletedTimelineTemplatesTitle": "{totalTimelineTemplates, plural, =0 {すべてのタイムライン} other {{totalTimelineTemplates}個のタイムラインテンプレート}}が正常に削除されました", - "xpack.securitySolution.open.timeline.successfullyExportedTimelinesTitle": "{totalTimelines, plural, =0 {すべてのタイムライン} other {{totalTimelines} 個のタイムライン}}のエクスポートが正常に完了しました", + "xpack.securitySolution.open.timeline.successfullyExportedTimelinesTitle": "{totalTimelines, plural, =0 {すべてのタイムライン} other {{totalTimelines} 個のタイムライン}}のエクスポートが正常に完了しました", "xpack.securitySolution.open.timeline.successfullyExportedTimelineTemplatesTitle": "{totalTimelineTemplates, plural, =0 {すべてのタイムライン} other {{totalTimelineTemplates} タイムラインテンプレート}}が正常にエクスポートされました", "xpack.securitySolution.open.timeline.timelineNameTableHeader": "タイムライン名", "xpack.securitySolution.open.timeline.timelineTemplateNameTableHeader": "テンプレート名", @@ -26568,9 +26568,7 @@ "xpack.upgradeAssistant.overview.deprecationLogs.updateErrorMessage": "ログ状態を更新できませんでした。", "xpack.upgradeAssistant.overview.deprecationsCountCheckpointTitle": "廃止予定の問題を解決して変更を検証", "xpack.upgradeAssistant.overview.documentationLinkText": "ドキュメント", - "xpack.upgradeAssistant.overview.fixIssuesStepDescription": "Elastic 8.xとの互換になるように、ElasticsearchおよびKibanaデプロイを更新します。アップグレード前に、重大な問題を解決する必要があります。警告の問題は独自の裁量で無視できます。", "xpack.upgradeAssistant.overview.fixIssuesStepTitle": "廃止予定設定を確認し、問題を解決", - "xpack.upgradeAssistant.overview.identifyStepTitle": "廃止予定APIの使用を特定し、アプリケーションを更新", "xpack.upgradeAssistant.overview.loadingLogsLabel": "廃止予定ログ収集状態を読み込んでいます...", "xpack.upgradeAssistant.overview.observe.discoveryDescription": "廃止予定ログを検索およびフィルターし、必要な変更のタイプを把握します。", "xpack.upgradeAssistant.overview.observe.observabilityDescription": "使用中のAPIのうち廃止予定のAPIと、更新が必要なアプリケーションを特定できます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 97d68b36baaa2..024a10fbc3a8c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17820,7 +17820,7 @@ "xpack.ml.ruleEditor.scopeSection.noPermissionToViewFilterListsTitle": "您无权查看筛选列表", "xpack.ml.ruleEditor.scopeSection.scopeTitle": "范围", "xpack.ml.ruleEditor.selectRuleAction.createRuleLinkText": "创建规则", - "xpack.ml.ruleEditor.selectRuleAction.orText": "或 ", + "xpack.ml.ruleEditor.selectRuleAction.orText": "或 ", "xpack.ml.ruleEditor.typicalAppliesTypeText": "典型", "xpack.ml.sampleDataLinkLabel": "ML 作业", "xpack.ml.settings.anomalyDetection.anomalyDetectionTitle": "异常检测", @@ -27026,9 +27026,7 @@ "xpack.upgradeAssistant.overview.deprecationLogs.updateErrorMessage": "无法更新日志记录状态。", "xpack.upgradeAssistant.overview.deprecationsCountCheckpointTitle": "解决弃用问题并验证您的更改", "xpack.upgradeAssistant.overview.documentationLinkText": "文档", - "xpack.upgradeAssistant.overview.fixIssuesStepDescription": "更新您的 Elasticsearch 和 Kibana 部署以兼容 Elastic 8.x。在升级之前必须解决紧急问题。您可以酌情忽略警告问题。", "xpack.upgradeAssistant.overview.fixIssuesStepTitle": "复查已弃用设置并解决问题", - "xpack.upgradeAssistant.overview.identifyStepTitle": "识别已弃用 API 的使用并更新您的应用程序", "xpack.upgradeAssistant.overview.loadingLogsLabel": "正在加载弃用日志收集状态……", "xpack.upgradeAssistant.overview.observe.discoveryDescription": "搜索并筛选弃用日志以了解需要进行的更改类型。", "xpack.upgradeAssistant.overview.observe.observabilityDescription": "深入了解正在使用哪些已弃用 API 以及需要更新哪些应用程序。", diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/app/cluster_upgrade.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/app/cluster_upgrade.test.tsx index 043c649b39bc2..7276d005844c2 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/app/cluster_upgrade.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/app/cluster_upgrade.test.tsx @@ -36,9 +36,14 @@ describe('Cluster upgrade', () => { }); }); + // The way we detect if we are currently upgrading or if the upgrade has been completed is if + // we ever get back a 426 error in *any* API response that UA makes. For that reason we can + // just mock one of the APIs that are being called from the overview page to return an error + // in order to trigger these interstitial states. In this case we're going to mock the + // `es deprecations` response. describe('when cluster is in the process of a rolling upgrade', () => { beforeEach(async () => { - httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(undefined, { + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, { statusCode: 426, message: '', attributes: { @@ -62,7 +67,7 @@ describe('Cluster upgrade', () => { describe('when cluster has been upgraded', () => { beforeEach(async () => { - httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(undefined, { + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, { statusCode: 426, message: '', attributes: { diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.helpers.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.helpers.ts new file mode 100644 index 0000000000000..11784d0269505 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.helpers.ts @@ -0,0 +1,80 @@ +/* + * 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 { act } from 'react-dom/test-utils'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { EsDeprecationLogs } from '../../../public/application/components/es_deprecation_logs'; +import { WithAppDependencies } from '../helpers'; + +const testBedConfig: AsyncTestBedConfig = { + memoryRouter: { + initialEntries: ['/es_deprecation_logs'], + componentRoutePath: '/es_deprecation_logs', + }, + doMountAsync: true, +}; + +export type EsDeprecationLogsTestBed = TestBed & { + actions: ReturnType; +}; + +const createActions = (testBed: TestBed) => { + /** + * User Actions + */ + + const clickDeprecationToggle = async () => { + const { find, component } = testBed; + + await act(async () => { + find('deprecationLoggingToggle').simulate('click'); + }); + + component.update(); + }; + + const clickRetryButton = async () => { + const { find, component } = testBed; + + await act(async () => { + find('retryButton').simulate('click'); + }); + + component.update(); + }; + + const clickResetButton = async () => { + const { find, component } = testBed; + + await act(async () => { + find('resetLastStoredDate').simulate('click'); + }); + + component.update(); + }; + + return { + clickDeprecationToggle, + clickRetryButton, + clickResetButton, + }; +}; + +export const setupESDeprecationLogsPage = async ( + overrides?: Record +): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(EsDeprecationLogs, overrides), + testBedConfig + ); + const testBed = await initTestBed(); + + return { + ...testBed, + actions: createActions(testBed), + }; +}; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_logs_step/fix_logs_step.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx similarity index 80% rename from x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_logs_step/fix_logs_step.test.tsx rename to x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx index 8b68f5ee449a8..8d97fc3897367 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_logs_step/fix_logs_step.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.test.tsx @@ -6,13 +6,24 @@ */ import { act } from 'react-dom/test-utils'; +import { + EsDeprecationLogsTestBed, + setupESDeprecationLogsPage, +} from './es_deprecation_logs.helpers'; +import { setupEnvironment, advanceTime } from '../helpers'; +import { DeprecationLoggingStatus } from '../../../common/types'; +import { + DEPRECATION_LOGS_INDEX, + DEPRECATION_LOGS_SOURCE_ID, + DEPRECATION_LOGS_COUNT_POLL_INTERVAL_MS, +} from '../../../common/constants'; // Once the logs team register the kibana locators in their app, we should be able // to remove this mock and follow a similar approach to how discover link is tested. // See: https://github.com/elastic/kibana/issues/104855 const MOCKED_TIME = '2021-09-05T10:49:01.805Z'; -jest.mock('../../../../public/application/lib/logs_checkpoint', () => { - const originalModule = jest.requireActual('../../../../public/application/lib/logs_checkpoint'); +jest.mock('../../../public/application/lib/logs_checkpoint', () => { + const originalModule = jest.requireActual('../../../public/application/lib/logs_checkpoint'); return { __esModule: true, @@ -21,83 +32,30 @@ jest.mock('../../../../public/application/lib/logs_checkpoint', () => { }; }); -import { DeprecationLoggingStatus } from '../../../../common/types'; -import { OverviewTestBed, setupOverviewPage } from '../overview.helpers'; -import { setupEnvironment, advanceTime } from '../../helpers'; -import { - DEPRECATION_LOGS_INDEX, - DEPRECATION_LOGS_SOURCE_ID, - DEPRECATION_LOGS_COUNT_POLL_INTERVAL_MS, -} from '../../../../common/constants'; - const getLoggingResponse = (toggle: boolean): DeprecationLoggingStatus => ({ isDeprecationLogIndexingEnabled: toggle, isDeprecationLoggingEnabled: toggle, }); -describe('Overview - Fix deprecation logs step', () => { - let testBed: OverviewTestBed; +describe('ES deprecation logs', () => { + let testBed: EsDeprecationLogsTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); beforeEach(async () => { httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(getLoggingResponse(true)); - testBed = await setupOverviewPage(); - - const { component } = testBed; - component.update(); + testBed = await setupESDeprecationLogsPage(); + testBed.component.update(); }); afterAll(() => { server.restore(); }); - describe('Step status', () => { - test(`It's complete when there are no deprecation logs since last checkpoint`, async () => { - httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ count: 0 }); - - await act(async () => { - testBed = await setupOverviewPage(); - }); - - const { exists, component } = testBed; - - component.update(); - - expect(exists(`fixLogsStep-complete`)).toBe(true); - }); - - test(`It's incomplete when there are deprecation logs since last checkpoint`, async () => { - httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ count: 5 }); - - await act(async () => { - testBed = await setupOverviewPage(); - }); - - const { exists, component } = testBed; - - component.update(); - - expect(exists(`fixLogsStep-incomplete`)).toBe(true); - }); - - test(`It's incomplete when log collection is disabled `, async () => { - httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ count: 0 }); - - await act(async () => { - testBed = await setupOverviewPage(); - }); - - const { actions, exists, component } = testBed; - - component.update(); - - expect(exists(`fixLogsStep-complete`)).toBe(true); - - httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse(getLoggingResponse(false)); - - await actions.clickDeprecationToggle(); + describe('Documentation link', () => { + test('Has a link for migration info api docs in page header', () => { + const { exists } = testBed; - expect(exists(`fixLogsStep-incomplete`)).toBe(true); + expect(exists('documentationLink')).toBe(true); }); }); @@ -123,7 +81,7 @@ describe('Overview - Fix deprecation logs step', () => { }); await act(async () => { - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); const { exists, component } = testBed; @@ -159,7 +117,7 @@ describe('Overview - Fix deprecation logs step', () => { httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(undefined, error); await act(async () => { - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); const { component, exists } = testBed; @@ -176,7 +134,7 @@ describe('Overview - Fix deprecation logs step', () => { }); await act(async () => { - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); const { exists, component } = testBed; @@ -196,7 +154,7 @@ describe('Overview - Fix deprecation logs step', () => { test('Has a link to see logs in observability app', async () => { await act(async () => { - testBed = await setupOverviewPage({ + testBed = await setupESDeprecationLogsPage({ http: { basePath: { prepend: (url: string) => url, @@ -228,7 +186,7 @@ describe('Overview - Fix deprecation logs step', () => { test('Has a link to see logs in discover app', async () => { await act(async () => { - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); const { exists, component, find } = testBed; @@ -257,7 +215,7 @@ describe('Overview - Fix deprecation logs step', () => { }); await act(async () => { - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); const { find, exists, component } = testBed; @@ -274,7 +232,7 @@ describe('Overview - Fix deprecation logs step', () => { }); await act(async () => { - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); const { find, exists, component } = testBed; @@ -295,7 +253,7 @@ describe('Overview - Fix deprecation logs step', () => { httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse(undefined, error); await act(async () => { - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); const { exists, actions, component } = testBed; @@ -319,7 +277,7 @@ describe('Overview - Fix deprecation logs step', () => { }); await act(async () => { - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); const { exists, actions, component } = testBed; @@ -351,7 +309,7 @@ describe('Overview - Fix deprecation logs step', () => { const addDanger = jest.fn(); await act(async () => { - testBed = await setupOverviewPage({ + testBed = await setupESDeprecationLogsPage({ services: { core: { notifications: { @@ -389,17 +347,17 @@ describe('Overview - Fix deprecation logs step', () => { count: 0, }); - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); afterEach(() => { jest.useRealTimers(); }); - test('renders step as incomplete when a success state is followed by an error state', async () => { + test('success state is followed by an error state', async () => { const { exists } = testBed; - expect(exists('fixLogsStep-complete')).toBe(true); + expect(exists('resetLastStoredDate')).toBe(true); // second request will error const error = { @@ -413,7 +371,7 @@ describe('Overview - Fix deprecation logs step', () => { await advanceTime(DEPRECATION_LOGS_COUNT_POLL_INTERVAL_MS); testBed.component.update(); - expect(exists('fixLogsStep-incomplete')).toBe(true); + expect(exists('errorCallout')).toBe(true); }); }); }); @@ -425,7 +383,7 @@ describe('Overview - Fix deprecation logs step', () => { test('It shows copy with compatibility api header advice', async () => { await act(async () => { - testBed = await setupOverviewPage(); + testBed = await setupESDeprecationLogsPage(); }); const { exists, component } = testBed; @@ -449,7 +407,7 @@ describe('Overview - Fix deprecation logs step', () => { test(`doesn't show analyze and resolve logs if it doesn't have the right privileges`, async () => { await act(async () => { - testBed = await setupOverviewPage({ + testBed = await setupESDeprecationLogsPage({ privileges: { hasAllPrivileges: false, missingPrivileges: { diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts index d8bd6a9ff5d83..34abaed727bd3 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts @@ -27,36 +27,6 @@ const createActions = (testBed: TestBed) => { * User Actions */ - const clickDeprecationToggle = async () => { - const { find, component } = testBed; - - await act(async () => { - find('deprecationLoggingToggle').simulate('click'); - }); - - component.update(); - }; - - const clickRetryButton = async () => { - const { find, component } = testBed; - - await act(async () => { - find('retryButton').simulate('click'); - }); - - component.update(); - }; - - const clickResetButton = async () => { - const { find, component } = testBed; - - await act(async () => { - find('resetLastStoredDate').simulate('click'); - }); - - component.update(); - }; - const clickViewSystemIndicesState = async () => { const { find, component } = testBed; @@ -78,9 +48,6 @@ const createActions = (testBed: TestBed) => { }; return { - clickDeprecationToggle, - clickRetryButton, - clickResetButton, clickViewSystemIndicesState, clickRetrySystemIndicesButton, }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/app.tsx b/x-pack/plugins/upgrade_assistant/public/application/app.tsx index 9ac90e5d81f48..70350b6d56eca 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app.tsx @@ -17,7 +17,13 @@ import { ClusterUpgradeState } from '../../common/types'; import { APP_WRAPPER_CLASS, GlobalFlyout, AuthorizationProvider } from '../shared_imports'; import { AppDependencies } from '../types'; import { AppContextProvider, useAppContext } from './app_context'; -import { EsDeprecations, ComingSoonPrompt, KibanaDeprecations, Overview } from './components'; +import { + EsDeprecations, + EsDeprecationLogs, + ComingSoonPrompt, + KibanaDeprecations, + Overview, +} from './components'; const { GlobalFlyoutProvider } = GlobalFlyout; @@ -112,6 +118,7 @@ const AppHandlingClusterUpgradeState: React.FunctionComponent = () => { + diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/es_deprecation_logs.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/es_deprecation_logs.tsx new file mode 100644 index 0000000000000..f778566758294 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/es_deprecation_logs.tsx @@ -0,0 +1,74 @@ +/* + * 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 React, { FunctionComponent, useEffect } from 'react'; + +import { + EuiPageHeader, + EuiButtonEmpty, + EuiSpacer, + EuiPageBody, + EuiPageContentBody, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { METRIC_TYPE } from '@kbn/analytics'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { useAppContext } from '../../app_context'; +import { uiMetricService, UIM_ES_DEPRECATION_LOGS_PAGE_LOAD } from '../../lib/ui_metric'; +import { FixDeprecationLogs } from './fix_deprecation_logs'; + +export const EsDeprecationLogs: FunctionComponent = () => { + const { + services: { + breadcrumbs, + core: { docLinks }, + }, + } = useAppContext(); + + useEffect(() => { + uiMetricService.trackUiMetric(METRIC_TYPE.LOADED, UIM_ES_DEPRECATION_LOGS_PAGE_LOAD); + }, []); + + useEffect(() => { + breadcrumbs.setBreadcrumbs('esDeprecationLogs'); + }, [breadcrumbs]); + + return ( + + + + + , + ]} + /> + + + + + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/_deprecation_logging_toggle.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecation_logging_toggle/_deprecation_logging_toggle.scss similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/_deprecation_logging_toggle.scss rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecation_logging_toggle/_deprecation_logging_toggle.scss diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/deprecation_logging_toggle.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecation_logging_toggle/deprecation_logging_toggle.tsx similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/deprecation_logging_toggle.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecation_logging_toggle/deprecation_logging_toggle.tsx diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecation_logging_toggle/index.ts similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecation_logging_toggle/index.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecation_logging_toggle/index.ts diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx similarity index 89% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx index 6ce1fec32d66c..c958eb68c86ec 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FunctionComponent, useEffect, useState } from 'react'; +import React, { FunctionComponent, useState } from 'react'; import moment from 'moment-timezone'; import { FormattedDate, FormattedTime, FormattedMessage } from '@kbn/i18n/react'; import { METRIC_TYPE } from '@kbn/analytics'; @@ -54,13 +54,11 @@ const i18nTexts = { interface Props { checkpoint: string; setCheckpoint: (value: string) => void; - setHasNoDeprecationLogs: (hasNoLogs: boolean) => void; } export const DeprecationsCountCheckpoint: FunctionComponent = ({ checkpoint, setCheckpoint, - setHasNoDeprecationLogs, }) => { const [isDeletingCache, setIsDeletingCache] = useState(false); const { @@ -96,16 +94,6 @@ export const DeprecationsCountCheckpoint: FunctionComponent = ({ setCheckpoint(now); }; - useEffect(() => { - // Loading shouldn't invalidate the previous state. - if (!isLoading) { - // An error should invalidate the previous state. - setHasNoDeprecationLogs(!error && !hasLogs); - } - // Depending upon setHasNoDeprecationLogs would create an infinite loop. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [error, isLoading, hasLogs]); - if (isInitialRequest && isLoading) { return ; } diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecations_count_checkpoint/index.ts similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/index.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/deprecations_count_checkpoint/index.ts diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/external_links.test.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/external_links.test.ts similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/external_links.test.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/external_links.test.ts diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/external_links.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/external_links.tsx similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/external_links.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/external_links.tsx diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/fix_logs_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/fix_deprecation_logs.tsx similarity index 80% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/fix_logs_step.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/fix_deprecation_logs.tsx index a3e81f6edcd3a..ff1cfc172905f 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/fix_logs_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/fix_deprecation_logs.tsx @@ -9,7 +9,6 @@ import React, { FunctionComponent, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText, EuiSpacer, EuiLink, EuiCallOut, EuiCode } from '@elastic/eui'; -import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; import { useAppContext } from '../../../app_context'; import { ExternalLinks } from './external_links'; @@ -17,14 +16,10 @@ import { DeprecationsCountCheckpoint } from './deprecations_count_checkpoint'; import { useDeprecationLogging } from './use_deprecation_logging'; import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; import { loadLogsCheckpoint, saveLogsCheckpoint } from '../../../lib/logs_checkpoint'; -import type { OverviewStepProps } from '../../types'; import { DEPRECATION_LOGS_INDEX } from '../../../../../common/constants'; import { WithPrivileges, MissingPrivileges } from '../../../../shared_imports'; const i18nTexts = { - identifyStepTitle: i18n.translate('xpack.upgradeAssistant.overview.identifyStepTitle', { - defaultMessage: 'Identify deprecated API use and update your applications', - }), analyzeTitle: i18n.translate('xpack.upgradeAssistant.overview.analyzeTitle', { defaultMessage: 'Analyze deprecation logs', }), @@ -93,16 +88,11 @@ const i18nTexts = { }; interface Props { - setIsComplete: OverviewStepProps['setIsComplete']; hasPrivileges: boolean; privilegesMissing: MissingPrivileges; } -const FixLogsStep: FunctionComponent = ({ - setIsComplete, - hasPrivileges, - privilegesMissing, -}) => { +const FixDeprecationLogsUI: FunctionComponent = ({ hasPrivileges, privilegesMissing }) => { const { services: { core: { docLinks }, @@ -126,15 +116,6 @@ const FixLogsStep: FunctionComponent = ({ saveLogsCheckpoint(checkpoint); }, [checkpoint]); - useEffect(() => { - if (!isDeprecationLogIndexingEnabled) { - setIsComplete(false); - } - - // Depending upon setIsComplete would create an infinite loop. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isDeprecationLogIndexingEnabled]); - return ( <> = ({

{i18nTexts.deprecationsCountCheckpointTitle}

- + @@ -213,23 +190,15 @@ const FixLogsStep: FunctionComponent = ({ ); }; -export const getFixLogsStep = ({ isComplete, setIsComplete }: OverviewStepProps): EuiStepProps => { - const status = isComplete ? 'complete' : 'incomplete'; - - return { - status, - title: i18nTexts.identifyStepTitle, - 'data-test-subj': `fixLogsStep-${status}`, - children: ( - - {({ hasPrivileges, privilegesMissing, isLoading }) => ( - - )} - - ), - }; +export const FixDeprecationLogs = () => { + return ( + + {({ hasPrivileges, privilegesMissing, isLoading }) => ( + + )} + + ); }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/index.ts new file mode 100644 index 0000000000000..c0af5524e3a14 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { FixDeprecationLogs } from './fix_deprecation_logs'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/use_deprecation_logging.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/use_deprecation_logging.ts similarity index 100% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/use_deprecation_logging.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/use_deprecation_logging.ts diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/index.ts similarity index 81% rename from x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/index.ts rename to x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/index.ts index 8a9a9faa6d098..336aa14642f7d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/index.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { getFixLogsStep } from './fix_logs_step'; +export { EsDeprecationLogs } from './es_deprecation_logs'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/index.ts index 8924fa2d355a1..b84af8695ddb1 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/index.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/index.ts @@ -7,5 +7,6 @@ export { KibanaDeprecations } from './kibana_deprecations'; export { EsDeprecations } from './es_deprecations'; +export { EsDeprecationLogs } from './es_deprecation_logs'; export { ComingSoonPrompt } from './coming_soon_prompt'; export { Overview } from './overview'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/fix_issues_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/fix_issues_step.tsx index 61d25404b2aee..410eb695240f9 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/fix_issues_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_issues_step/fix_issues_step.tsx @@ -7,11 +7,13 @@ import React, { FunctionComponent, useState, useEffect } from 'react'; -import { EuiText, EuiFlexItem, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import { EuiText, EuiFlexItem, EuiFlexGroup, EuiSpacer, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; +import { DEPRECATION_LOGS_INDEX } from '../../../../../common/constants'; +import { WithPrivileges } from '../../../../shared_imports'; import type { OverviewStepProps } from '../../types'; import { EsDeprecationIssuesPanel, KibanaDeprecationIssuesPanel } from './components'; @@ -49,10 +51,48 @@ const FixIssuesStep: FunctionComponent = ({ setIsComplete }) => { ); }; +interface CustomProps { + navigateToEsDeprecationLogs: () => void; +} + +const AccessDeprecationLogsMessage = ({ navigateToEsDeprecationLogs }: CustomProps) => { + return ( + + {({ hasPrivileges, isLoading }) => { + if (isLoading || !hasPrivileges) { + // Don't show the message with the link to access deprecation logs + // to users who can't access the UI anyways. + return null; + } + + return ( + + {i18n.translate('xpack.upgradeAssistant.overview.esDeprecationLogsLink', { + defaultMessage: 'Elasticsearch deprecation logs', + })} + + ), + }} + /> + ); + }} + + ); +}; + export const getFixIssuesStep = ({ isComplete, setIsComplete, -}: OverviewStepProps): EuiStepProps => { + navigateToEsDeprecationLogs, +}: OverviewStepProps & CustomProps): EuiStepProps => { const status = isComplete ? 'complete' : 'incomplete'; return { @@ -65,7 +105,14 @@ export const getFixIssuesStep = ({

+ ), + }} />

diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx index 1e7961f8ea782..900c89671315d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FunctionComponent, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { EuiSteps, @@ -15,23 +15,23 @@ import { EuiSpacer, EuiLink, EuiPageBody, - EuiPageContent, + EuiPageContentBody, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import { FormattedMessage } from '@kbn/i18n/react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import { useAppContext } from '../../app_context'; import { uiMetricService, UIM_OVERVIEW_PAGE_LOAD } from '../../lib/ui_metric'; import { getBackupStep } from './backup_step'; import { getFixIssuesStep } from './fix_issues_step'; -import { getFixLogsStep } from './fix_logs_step'; import { getUpgradeStep } from './upgrade_step'; import { getMigrateSystemIndicesStep } from './migrate_system_indices'; -type OverviewStep = 'backup' | 'migrate_system_indices' | 'fix_issues' | 'fix_logs'; +type OverviewStep = 'backup' | 'migrate_system_indices' | 'fix_issues'; -export const Overview: FunctionComponent = () => { +export const Overview = withRouter(({ history }: RouteComponentProps) => { const { services: { breadcrumbs, @@ -52,7 +52,6 @@ export const Overview: FunctionComponent = () => { backup: false, migrate_system_indices: false, fix_issues: false, - fix_logs: false, }); const isStepComplete = (step: OverviewStep) => completedStepsMap[step]; @@ -65,7 +64,7 @@ export const Overview: FunctionComponent = () => { return ( - + { getFixIssuesStep({ isComplete: isStepComplete('fix_issues'), setIsComplete: setCompletedStep.bind(null, 'fix_issues'), - }), - getFixLogsStep({ - isComplete: isStepComplete('fix_logs'), - setIsComplete: setCompletedStep.bind(null, 'fix_logs'), + navigateToEsDeprecationLogs: () => history.push('/es_deprecation_logs'), }), getUpgradeStep(), ]} /> - + ); -}; +}); diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts index 3e30ffd06db15..dd1cc01355696 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts @@ -18,6 +18,9 @@ const i18nTexts = { esDeprecations: i18n.translate('xpack.upgradeAssistant.breadcrumb.esDeprecationsLabel', { defaultMessage: 'Elasticsearch deprecation issues', }), + esDeprecationLogs: i18n.translate('xpack.upgradeAssistant.breadcrumb.esDeprecationLogsLabel', { + defaultMessage: 'Elasticsearch deprecation logs', + }), kibanaDeprecations: i18n.translate( 'xpack.upgradeAssistant.breadcrumb.kibanaDeprecationsLabel', { @@ -48,6 +51,15 @@ export class BreadcrumbService { text: i18nTexts.breadcrumbs.esDeprecations, }, ], + esDeprecationLogs: [ + { + text: i18nTexts.breadcrumbs.overview, + href: '/', + }, + { + text: i18nTexts.breadcrumbs.esDeprecationLogs, + }, + ], kibanaDeprecations: [ { text: i18nTexts.breadcrumbs.overview, @@ -65,7 +77,9 @@ export class BreadcrumbService { this.setBreadcrumbsHandler = setBreadcrumbsHandler; } - public setBreadcrumbs(type: 'overview' | 'esDeprecations' | 'kibanaDeprecations'): void { + public setBreadcrumbs( + type: 'overview' | 'esDeprecations' | 'esDeprecationLogs' | 'kibanaDeprecations' + ): void { if (!this.setBreadcrumbsHandler) { throw new Error('Breadcrumb service has not been initialized'); } diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/ui_metric.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/ui_metric.ts index 394f046a8bafe..1ac34ae53194d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/lib/ui_metric.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/ui_metric.ts @@ -12,6 +12,7 @@ export const UIM_APP_NAME = 'upgrade_assistant'; export const UIM_ES_DEPRECATIONS_PAGE_LOAD = 'es_deprecations_page_load'; export const UIM_KIBANA_DEPRECATIONS_PAGE_LOAD = 'kibana_deprecations_page_load'; export const UIM_OVERVIEW_PAGE_LOAD = 'overview_page_load'; +export const UIM_ES_DEPRECATION_LOGS_PAGE_LOAD = 'es_deprecation_logs_page_load'; export const UIM_REINDEX_OPEN_FLYOUT_CLICK = 'reindex_open_flyout_click'; export const UIM_REINDEX_CLOSE_FLYOUT_CLICK = 'reindex_close_flyout_click'; export const UIM_REINDEX_START_CLICK = 'reindex_start_click'; diff --git a/x-pack/test/accessibility/apps/upgrade_assistant.ts b/x-pack/test/accessibility/apps/upgrade_assistant.ts index 2c0e81a6fb831..829d0a2c42373 100644 --- a/x-pack/test/accessibility/apps/upgrade_assistant.ts +++ b/x-pack/test/accessibility/apps/upgrade_assistant.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { IndicesCreateRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { FtrProviderContext } from '../ftr_provider_context'; -const translogSettingsIndexDeprecation: estypes.IndicesCreateRequest = { +const translogSettingsIndexDeprecation: IndicesCreateRequest = { index: 'deprecated_settings', body: { settings: { @@ -19,7 +19,7 @@ const translogSettingsIndexDeprecation: estypes.IndicesCreateRequest = { }, }; -const multiFieldsIndexDeprecation: estypes.IndicesCreateRequest = { +const multiFieldsIndexDeprecation: IndicesCreateRequest = { index: 'nested_multi_fields', body: { mappings: { @@ -55,6 +55,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe.skip('Upgrade Assistant', () => { before(async () => { await PageObjects.upgradeAssistant.navigateToPage(); + try { // Create two indices that will trigger deprecation warnings to test the ES deprecations page await es.indices.create(multiFieldsIndexDeprecation); @@ -76,128 +77,113 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { } }); - describe('Upgrade Assistant - Overview', () => { - before(async () => { + describe('Overview page', () => { + beforeEach(async () => { await PageObjects.upgradeAssistant.navigateToPage(); - - try { - // Create two indices that will trigger deprecation warnings to test the ES deprecations page - await es.indices.create(multiFieldsIndexDeprecation); - await es.indices.create(translogSettingsIndexDeprecation); - } catch (e) { - log.debug('[Setup error] Error creating indices'); - throw e; - } + await retry.waitFor('Upgrade Assistant overview page to be visible', async () => { + return testSubjects.exists('overview'); + }); }); - after(async () => { - try { - await es.indices.delete({ - index: [multiFieldsIndexDeprecation.index, translogSettingsIndexDeprecation.index], - }); - } catch (e) { - log.debug('[Cleanup error] Error deleting indices'); - throw e; - } + it('has no accessibility issues', async () => { + await a11y.testAppSnapshot(); }); + }); - describe('Overview page', () => { - beforeEach(async () => { - await PageObjects.upgradeAssistant.navigateToPage(); - await retry.waitFor('Upgrade Assistant overview page to be visible', async () => { - return testSubjects.exists('overview'); - }); - }); - - it('with logs collection disabled', async () => { - await a11y.testAppSnapshot(); - }); + describe('ES deprecations logs page', () => { + beforeEach(async () => { + await PageObjects.upgradeAssistant.navigateToEsDeprecationLogs(); + }); - it('with logs collection enabled', async () => { - await PageObjects.upgradeAssistant.clickDeprecationLoggingToggle(); + it('with logs collection disabled', async () => { + await a11y.testAppSnapshot(); + }); - await retry.waitFor('UA external links title to be present', async () => { - return testSubjects.isDisplayed('externalLinksTitle'); - }); + it('with logs collection enabled', async () => { + await PageObjects.upgradeAssistant.clickDeprecationLoggingToggle(); - await a11y.testAppSnapshot(); + await retry.waitFor('UA external links title to be present', async () => { + return testSubjects.isDisplayed('externalLinksTitle'); }); - }); - describe('Elasticsearch deprecations page', () => { - beforeEach(async () => { - await PageObjects.common.navigateToUrl( - 'management', - 'stack/upgrade_assistant/es_deprecations', - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - shouldUseHashForSubUrl: false, - } - ); - - await retry.waitFor('Elasticsearch deprecations table to be visible', async () => { - return testSubjects.exists('esDeprecationsTable'); - }); - }); + await a11y.testAppSnapshot(); + }); + }); - it('Deprecations table', async () => { - await a11y.testAppSnapshot(); + describe('Elasticsearch deprecations page', () => { + beforeEach(async () => { + await PageObjects.common.navigateToUrl( + 'management', + 'stack/upgrade_assistant/es_deprecations', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + } + ); + + await retry.waitFor('Elasticsearch deprecations table to be visible', async () => { + return testSubjects.exists('esDeprecationsTable'); }); + }); - it('Index settings deprecation flyout', async () => { - await PageObjects.upgradeAssistant.clickEsDeprecation( - 'indexSettings' // An index setting deprecation was added in the before() hook so should be guaranteed - ); - await retry.waitFor('ES index settings deprecation flyout to be visible', async () => { - return testSubjects.exists('indexSettingsDetails'); - }); - await a11y.testAppSnapshot(); - }); + it('Deprecations table', async () => { + await a11y.testAppSnapshot(); + }); - it('Default deprecation flyout', async () => { - await PageObjects.upgradeAssistant.clickEsDeprecation( - 'default' // A default deprecation was added in the before() hook so should be guaranteed - ); - await retry.waitFor('ES default deprecation flyout to be visible', async () => { - return testSubjects.exists('defaultDeprecationDetails'); - }); - await a11y.testAppSnapshot(); + // Failing: See https://github.com/elastic/kibana/issues/115859 + it.skip('Index settings deprecation flyout', async () => { + await PageObjects.upgradeAssistant.clickEsDeprecation( + 'indexSettings' // An index setting deprecation was added in the before() hook so should be guaranteed + ); + await retry.waitFor('ES index settings deprecation flyout to be visible', async () => { + return testSubjects.exists('indexSettingsDetails'); }); + await a11y.testAppSnapshot(); }); - describe('Kibana deprecations page', () => { - beforeEach(async () => { - await PageObjects.common.navigateToUrl( - 'management', - 'stack/upgrade_assistant/kibana_deprecations', - { - ensureCurrentUrl: false, - shouldLoginIfPrompted: false, - shouldUseHashForSubUrl: false, - } - ); - - await retry.waitFor('Kibana deprecations to be visible', async () => { - return testSubjects.exists('kibanaDeprecations'); - }); + it('Default deprecation flyout', async () => { + await PageObjects.upgradeAssistant.clickEsDeprecation( + 'default' // A default deprecation was added in the before() hook so should be guaranteed + ); + await retry.waitFor('ES default deprecation flyout to be visible', async () => { + return testSubjects.exists('defaultDeprecationDetails'); }); + await a11y.testAppSnapshot(); + }); + }); - it('Deprecations table', async () => { - await a11y.testAppSnapshot(); + describe('Kibana deprecations page', () => { + beforeEach(async () => { + await PageObjects.common.navigateToUrl( + 'management', + 'stack/upgrade_assistant/kibana_deprecations', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + } + ); + + await retry.waitFor('Kibana deprecations to be visible', async () => { + return testSubjects.exists('kibanaDeprecations'); }); + }); - it('Deprecation details flyout', async () => { - await PageObjects.upgradeAssistant.clickKibanaDeprecation( - 'xpack.securitySolution has a deprecated setting' // This deprecation was added to the test runner config so should be guaranteed - ); + it('Deprecations table', async () => { + await a11y.testAppSnapshot(); + }); - await retry.waitFor('Kibana deprecation details flyout to be visible', async () => { - return testSubjects.exists('kibanaDeprecationDetails'); - }); + it('Deprecation details flyout', async () => { + await PageObjects.upgradeAssistant.clickKibanaDeprecation( + 'xpack.securitySolution has a deprecated setting' // This deprecation was added to the test runner config so should be guaranteed + ); - await a11y.testAppSnapshot(); + await retry.waitFor('Kibana deprecation details flyout to be visible', async () => { + return testSubjects.exists('kibanaDeprecationDetails'); }); + + await a11y.testAppSnapshot(); }); }); }); diff --git a/x-pack/test/functional/apps/upgrade_assistant/es_deprecation_logs_page.ts b/x-pack/test/functional/apps/upgrade_assistant/es_deprecation_logs_page.ts new file mode 100644 index 0000000000000..c6b9b2921cfad --- /dev/null +++ b/x-pack/test/functional/apps/upgrade_assistant/es_deprecation_logs_page.ts @@ -0,0 +1,45 @@ +/* + * 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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function upgradeAssistantESDeprecationLogsPageFunctionalTests({ + getService, + getPageObjects, +}: FtrProviderContext) { + const PageObjects = getPageObjects(['upgradeAssistant', 'common']); + const security = getService('security'); + const testSubjects = getService('testSubjects'); + const es = getService('es'); + + describe.skip('ES deprecation logs page', function () { + this.tags('skipFirefox'); + + before(async () => { + await security.testUser.setRoles(['superuser']); + // Access to system indices will be deprecated and should generate a deprecation log + await es.indices.get({ index: '.kibana' }); + }); + + after(async () => { + await security.testUser.restoreDefaults(); + }); + + beforeEach(async () => { + await PageObjects.upgradeAssistant.navigateToEsDeprecationLogs(); + }); + + it('Shows warnings callout if there are deprecations', async () => { + testSubjects.exists('hasWarningsCallout'); + }); + + it('Shows no warnings callout if there are no deprecations', async () => { + await PageObjects.upgradeAssistant.clickResetLastCheckpointButton(); + testSubjects.exists('noWarningsCallout'); + }); + }); +} diff --git a/x-pack/test/functional/apps/upgrade_assistant/index.ts b/x-pack/test/functional/apps/upgrade_assistant/index.ts index d99d1cd033327..d1ab46463e930 100644 --- a/x-pack/test/functional/apps/upgrade_assistant/index.ts +++ b/x-pack/test/functional/apps/upgrade_assistant/index.ts @@ -14,5 +14,6 @@ export default function upgradeCheckup({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./deprecation_pages')); loadTestFile(require.resolve('./overview_page')); + loadTestFile(require.resolve('./es_deprecation_logs_page')); }); } diff --git a/x-pack/test/functional/apps/upgrade_assistant/overview_page.ts b/x-pack/test/functional/apps/upgrade_assistant/overview_page.ts index 0b8d15695689a..a2ec7493cc3f0 100644 --- a/x-pack/test/functional/apps/upgrade_assistant/overview_page.ts +++ b/x-pack/test/functional/apps/upgrade_assistant/overview_page.ts @@ -15,7 +15,6 @@ export default function upgradeAssistantOverviewPageFunctionalTests({ const retry = getService('retry'); const security = getService('security'); const testSubjects = getService('testSubjects'); - const es = getService('es'); describe.skip('Overview Page', function () { this.tags('skipFirefox'); @@ -41,37 +40,7 @@ export default function upgradeAssistantOverviewPageFunctionalTests({ it('Should render all steps', async () => { testSubjects.exists('backupStep-incomplete'); testSubjects.exists('fixIssuesStep-incomplete'); - testSubjects.exists('fixLogsStep-incomplete'); testSubjects.exists('upgradeStep'); }); - - describe('fixLogsStep', () => { - before(async () => { - await PageObjects.upgradeAssistant.navigateToPage(); - // Access to system indices will be deprecated and should generate a deprecation log - await es.indices.get({ index: '.kibana' }); - // Only click deprecation logging toggle if its not already enabled - if (!(await testSubjects.isDisplayed('externalLinksTitle'))) { - await PageObjects.upgradeAssistant.clickDeprecationLoggingToggle(); - } - - await retry.waitFor('UA external links title to be present', async () => { - return testSubjects.isDisplayed('externalLinksTitle'); - }); - }); - - beforeEach(async () => { - await PageObjects.upgradeAssistant.navigateToPage(); - }); - - it('Shows warnings callout if there are deprecations', async () => { - testSubjects.exists('hasWarningsCallout'); - }); - - it('Shows no warnings callout if there are no deprecations', async () => { - await PageObjects.upgradeAssistant.clickResetLastCheckpointButton(); - testSubjects.exists('noWarningsCallout'); - }); - }); }); } diff --git a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts index 54d7f3d452123..f795a5fd441cd 100644 --- a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts +++ b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts @@ -29,6 +29,21 @@ export class UpgradeAssistantPageObject extends FtrService { }); } + async navigateToEsDeprecationLogs() { + return await this.retry.try(async () => { + await this.common.navigateToApp('settings'); + await this.testSubjects.click('upgrade_assistant'); + await this.testSubjects.click('viewElasticsearchDeprecationLogs'); + await this.retry.waitFor( + 'url to contain /upgrade_assistant/es_deprecation_logs', + async () => { + const url = await this.browser.getCurrentUrl(); + return url.includes('/es_deprecation_logs'); + } + ); + }); + } + async clickEsDeprecationsPanel() { return await this.retry.try(async () => { await this.testSubjects.click('esStatsPanel'); From d0b58bb2b0807e08276776d0baf5af21a06683a4 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Sun, 21 Nov 2021 12:27:53 +0100 Subject: [PATCH 02/11] update APM agents with renovate bot (#119166) * declare deps on APM agents via ranges * update APM agents to the latest versions --- package.json | 2 +- renovate.json | 8 ++++++++ yarn.lock | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 84bd703dba4ef..d56fe68b2e074 100644 --- a/package.json +++ b/package.json @@ -220,7 +220,7 @@ "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", "del": "^5.1.0", - "elastic-apm-node": "3.24.0", + "elastic-apm-node": "^3.24.0", "execa": "^4.0.2", "exit-hook": "^2.2.0", "expiry-js": "0.1.7", diff --git a/renovate.json b/renovate.json index 82005e82299b1..362fd9381b617 100644 --- a/renovate.json +++ b/renovate.json @@ -54,6 +54,14 @@ "labels": ["release_note:skip", "Team:Operations", "Team:Core", "backport:skip"], "enabled": true }, + { + "groupName": "APM", + "matchPackageNames": ["elastic-apm-node", "@elastic/apm-rum", "@elastic/apm-rum-react"], + "reviewers": ["team:kibana-core"], + "matchBaseBranches": ["main"], + "labels": ["release_note:skip", "Team:Core", "backport:skip"], + "enabled": true + }, { "groupName": "babel", "matchPackageNames": ["@types/babel__core"], diff --git a/yarn.lock b/yarn.lock index 9ae9d163a0463..d1e1de94393a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12596,7 +12596,7 @@ elastic-apm-http-client@^10.3.0: readable-stream "^3.4.0" stream-chopper "^3.0.1" -elastic-apm-node@3.24.0: +elastic-apm-node@^3.24.0: version "3.24.0" resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.24.0.tgz#d7acb3352f928a23c28ebabab2bd30098562814e" integrity sha512-Fmj/W2chWQa2zb1FfMYK2ypLB4TcnKNX+1klaJFbytRYDLgeSfo0EC7egvI3a+bLPZSRL5053PXOp7slVTPO6Q== From c1a5ceeb64b3b3f3ffe45f76c867e85f0fdc7fc9 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Sun, 21 Nov 2021 13:18:07 -0500 Subject: [PATCH 03/11] [Fleet] Update logic for "Keep policies up to date" defaults in 8.0 (#119126) * Auto-upgrade policies for all managed packages by default * Fixing unused import in test * Add migration to installation list * Update naming + disable input for APM + Synthetics * Remove unused import + add message for disabled policy update settingg * Fix failing tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/common/constants/epm.ts | 9 ++++ .../common/constants/preconfiguration.ts | 15 +++++++ .../plugins/fleet/common/types/models/epm.ts | 2 +- .../keep_policies_up_to_date_switch.tsx | 18 ++++++-- .../epm/screens/detail/settings/settings.tsx | 14 +++--- .../plugins/fleet/public/constants/index.ts | 2 + .../fleet/server/saved_objects/index.ts | 3 +- .../saved_objects/migrations/to_v8_0_0.ts | 21 ++++++++- .../server/services/epm/packages/install.ts | 13 +++--- .../services/managed_package_policies.test.ts | 45 ------------------- .../epm/__snapshots__/install_by_upload.snap | 1 - .../apis/epm/install_remove_assets.ts | 1 - .../apis/epm/update_assets.ts | 1 - 13 files changed, 77 insertions(+), 68 deletions(-) diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index 734d578687bcd..5137e422e0975 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -32,6 +32,13 @@ export const STANDALONE_RUN_INSTRUCTIONS = './elastic-agent install'; removable, but it doesn't install by default. Following the table, it needs to be in `unremovablePackages` and in `autoUpdatePackages`, but not in `defaultPackages`. + + +We also define "auto upgrade policies" packages below. These are packages that are considered "stack-aligned" +and require policies to be auto-upgraded in order to properly function. Commonly, packages that ship custom policy +editor UI's in the Kibana codebase will be included in this set of packages to avoid backwards-compatibility concerns +in their custom policy editor implementations. + */ export const unremovablePackages = [ @@ -49,6 +56,8 @@ export const autoUpdatePackages = [ FLEET_SYNTHETICS_PACKAGE, ]; +export const autoUpgradePoliciesPackages = [FLEET_APM_PACKAGE, FLEET_SYNTHETICS_PACKAGE]; + export const agentAssetTypes = { Input: 'input', } as const; diff --git a/x-pack/plugins/fleet/common/constants/preconfiguration.ts b/x-pack/plugins/fleet/common/constants/preconfiguration.ts index 2ec67393df76b..3e7377477c93e 100644 --- a/x-pack/plugins/fleet/common/constants/preconfiguration.ts +++ b/x-pack/plugins/fleet/common/constants/preconfiguration.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { uniqBy } from 'lodash'; + import type { PreconfiguredAgentPolicy } from '../types'; import { @@ -13,6 +15,7 @@ import { FLEET_SERVER_PACKAGE, autoUpdatePackages, monitoringTypes, + autoUpgradePoliciesPackages, } from './epm'; export const PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE = @@ -72,6 +75,18 @@ export const AUTO_UPDATE_PACKAGES = autoUpdatePackages.map((name) => ({ version: PRECONFIGURATION_LATEST_KEYWORD, })); +// These packages default to `keep_policies_up_to_date: true` and don't allow users to opt out +export const AUTO_UPGRADE_POLICIES_PACKAGES = autoUpgradePoliciesPackages.map((name) => ({ + name, + version: PRECONFIGURATION_LATEST_KEYWORD, +})); + +// Controls whether the `Keep Policies up to date` setting is exposed to the user +export const KEEP_POLICIES_UP_TO_DATE_PACKAGES = uniqBy( + [...AUTO_UPGRADE_POLICIES_PACKAGES, ...DEFAULT_PACKAGES, ...AUTO_UPDATE_PACKAGES], + ({ name }) => name +); + export interface PreconfigurationError { package?: { name: string; version: string }; agentPolicy?: { name: string }; diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 078281fec9806..f7b446cc53c7d 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -402,7 +402,7 @@ export interface Installation extends SavedObjectAttributes { install_version: string; install_started_at: string; install_source: InstallSource; - keep_policies_up_to_date: boolean; + keep_policies_up_to_date?: boolean; } export interface PackageUsageStats { diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/keep_policies_up_to_date_switch.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/keep_policies_up_to_date_switch.tsx index 751282cc42288..364b24ab48912 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/keep_policies_up_to_date_switch.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/keep_policies_up_to_date_switch.tsx @@ -12,11 +12,13 @@ import { EuiSwitch, EuiSpacer, EuiText, EuiFlexGroup, EuiFlexItem, EuiIcon } fro interface Props { checked: boolean; + disabled?: boolean; onChange: () => void; } export const KeepPoliciesUpToDateSwitch: React.FunctionComponent = ({ checked, + disabled = false, onChange, }) => ( <> @@ -27,6 +29,7 @@ export const KeepPoliciesUpToDateSwitch: React.FunctionComponent = ({ )} checked={checked} onChange={onChange} + disabled={disabled} /> @@ -35,10 +38,17 @@ export const KeepPoliciesUpToDateSwitch: React.FunctionComponent = ({ - + {disabled ? ( + + ) : ( + + )} diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx index 5fa274c0feb98..31a01ffd7bdc8 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx @@ -9,7 +9,6 @@ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; import semverLt from 'semver/functions/lt'; -import { uniq } from 'lodash'; import { EuiCallOut, @@ -35,8 +34,8 @@ import { } from '../../../../../hooks'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, - AUTO_UPDATE_PACKAGES, - DEFAULT_PACKAGES, + KEEP_POLICIES_UP_TO_DATE_PACKAGES, + AUTO_UPGRADE_POLICIES_PACKAGES, } from '../../../../../constants'; import { KeepPoliciesUpToDateSwitch } from '../components'; @@ -107,11 +106,11 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => { const { notifications } = useStartServices(); const shouldShowKeepPoliciesUpToDateSwitch = useMemo(() => { - const packages = [...DEFAULT_PACKAGES, ...AUTO_UPDATE_PACKAGES]; - - const packageNames = uniq(packages.map((pkg) => pkg.name)); + return KEEP_POLICIES_UP_TO_DATE_PACKAGES.some((pkg) => pkg.name === name); + }, [name]); - return packageNames.includes(name); + const isShowKeepPoliciesUpToDateSwitchDisabled = useMemo(() => { + return AUTO_UPGRADE_POLICIES_PACKAGES.some((pkg) => pkg.name === name); }, [name]); const [keepPoliciesUpToDateSwitchValue, setKeepPoliciesUpToDateSwitchValue] = useState( @@ -274,6 +273,7 @@ export const SettingsPage: React.FC = memo(({ packageInfo }: Props) => { diff --git a/x-pack/plugins/fleet/public/constants/index.ts b/x-pack/plugins/fleet/public/constants/index.ts index 38b7875c93b3b..139f9d3d1f1c4 100644 --- a/x-pack/plugins/fleet/public/constants/index.ts +++ b/x-pack/plugins/fleet/public/constants/index.ts @@ -21,6 +21,8 @@ export { // Preconfiguration AUTO_UPDATE_PACKAGES, DEFAULT_PACKAGES, + KEEP_POLICIES_UP_TO_DATE_PACKAGES, + AUTO_UPGRADE_POLICIES_PACKAGES, } from '../../common/constants'; export * from './page_paths'; diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 3b459c938b5f0..26adf7b9fcbc7 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -35,7 +35,7 @@ import { import { migratePackagePolicyToV7140, migrateInstallationToV7140 } from './migrations/to_v7_14_0'; import { migratePackagePolicyToV7150 } from './migrations/to_v7_15_0'; import { migrateInstallationToV7160, migratePackagePolicyToV7160 } from './migrations/to_v7_16_0'; -import { migrateOutputToV800 } from './migrations/to_v8_0_0'; +import { migrateInstallationToV800, migrateOutputToV800 } from './migrations/to_v8_0_0'; /* * Saved object types and mappings @@ -255,6 +255,7 @@ const getSavedObjectTypes = ( '7.14.0': migrateInstallationToV7140, '7.14.1': migrateInstallationToV7140, '7.16.0': migrateInstallationToV7160, + '8.0.0': migrateInstallationToV800, }, }, [ASSETS_SAVED_OBJECT_TYPE]: { diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_0_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_0_0.ts index 77797b3d27ba5..61db35fd9faf5 100644 --- a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_0_0.ts +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_0_0.ts @@ -7,8 +7,8 @@ import type { SavedObjectMigrationFn } from 'kibana/server'; -import type { Output } from '../../../common'; -import {} from '../../../common'; +import type { Installation, Output } from '../../../common'; +import { AUTO_UPGRADE_POLICIES_PACKAGES } from '../../../common'; export const migrateOutputToV800: SavedObjectMigrationFn = ( outputDoc, @@ -20,3 +20,20 @@ export const migrateOutputToV800: SavedObjectMigrationFn = ( return outputDoc; }; + +export const migrateInstallationToV800: SavedObjectMigrationFn = ( + installationDoc, + migrationContext +) => { + const updatedInstallationDoc = installationDoc; + + const shouldKeepPoliciesUpToDate = AUTO_UPGRADE_POLICIES_PACKAGES.some( + (pkg) => pkg.name === updatedInstallationDoc.attributes.name + ); + + if (shouldKeepPoliciesUpToDate) { + updatedInstallationDoc.attributes.keep_policies_up_to_date = true; + } + + return updatedInstallationDoc; +}; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 5b86c944feb39..db26dc3a20a80 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -18,7 +18,7 @@ import type { InstallablePackage, InstallSource, } from '../../../../common'; -import { DEFAULT_PACKAGES } from '../../../../common'; +import { AUTO_UPGRADE_POLICIES_PACKAGES } from '../../../../common'; import { IngestManagerError, PackageOperationNotSupportedError, @@ -534,11 +534,14 @@ export async function createInstallation(options: { const removable = !isUnremovablePackage(pkgName); const toSaveESIndexPatterns = generateESIndexPatterns(packageInfo.data_streams); - // For default packages, default the `keep_policies_up_to_date` setting to true. For all other - // package, default it to false. - const defaultKeepPoliciesUpToDate = DEFAULT_PACKAGES.some( + // For "stack-aligned" packages, default the `keep_policies_up_to_date` setting to true. For all other + // packages, default it to undefined. Use undefined rather than false to allow us to differentiate + // between "unset" and "user explicitly disabled". + const defaultKeepPoliciesUpToDate = AUTO_UPGRADE_POLICIES_PACKAGES.some( ({ name }) => name === packageInfo.name - ); + ) + ? true + : undefined; const created = await savedObjectsClient.create( PACKAGES_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts b/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts index b27248a3cb933..7ccfeb4fe7641 100644 --- a/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts +++ b/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts @@ -8,7 +8,6 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; import type { Installation, PackageInfo } from '../../common'; -import { AUTO_UPDATE_PACKAGES } from '../../common'; import { shouldUpgradePolicies, upgradeManagedPackagePolicies } from './managed_package_policies'; import { packagePolicyService } from './package_policy'; @@ -227,50 +226,6 @@ describe('upgradeManagedPackagePolicies', () => { }); describe('shouldUpgradePolicies', () => { - describe('package is marked as AUTO_UPDATE', () => { - describe('keep_policies_up_to_date is true', () => { - it('returns false', () => { - const packageInfo = { - version: '1.0.0', - keepPoliciesUpToDate: true, - name: AUTO_UPDATE_PACKAGES[0].name, - }; - - const installedPackage = { - version: '1.0.0', - }; - - const result = shouldUpgradePolicies( - packageInfo as PackageInfo, - installedPackage as Installation - ); - - expect(result).toBe(false); - }); - }); - - describe('keep_policies_up_to_date is false', () => { - it('returns false', () => { - const packageInfo = { - version: '1.0.0', - keepPoliciesUpToDate: false, - name: AUTO_UPDATE_PACKAGES[0].name, - }; - - const installedPackage = { - version: '1.0.0', - }; - - const result = shouldUpgradePolicies( - packageInfo as PackageInfo, - installedPackage as Installation - ); - - expect(result).toBe(false); - }); - }); - }); - describe('package policy is up-to-date', () => { describe('keep_policies_up_to_date is true', () => { it('returns false', () => { diff --git a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap index 04d189b5d59b2..5eb4ae1808d7b 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap +++ b/x-pack/test/fleet_api_integration/apis/epm/__snapshots__/install_by_upload.snap @@ -446,7 +446,6 @@ Object { "type": "search", }, ], - "keep_policies_up_to_date": false, "name": "apache", "package_assets": Array [ Object { diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts index f3f036107fc22..0915af7e25f0c 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts @@ -673,7 +673,6 @@ const expectAssetsInstalled = ({ install_status: 'installed', install_started_at: res.attributes.install_started_at, install_source: 'registry', - keep_policies_up_to_date: false, }); }); }; diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index d55f6aad53aa2..357345777e52e 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -462,7 +462,6 @@ export default function (providerContext: FtrProviderContext) { install_status: 'installed', install_started_at: res.attributes.install_started_at, install_source: 'registry', - keep_policies_up_to_date: false, }); }); }); From 25fa46d17bd204632c52f9454cfcfd1cd0928d27 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Mon, 22 Nov 2021 11:06:59 +0100 Subject: [PATCH 04/11] hiding log callout for managed (#119186) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../agent_details_page/components/agent_logs/agent_logs.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx index 783a960aff120..045c9c28c378c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.tsx @@ -277,9 +277,9 @@ export const AgentLogsUI: React.FunctionComponent = memo( return ( - {agentPolicy && !agentPolicy.monitoring_enabled?.includes('logs') && ( - - )} + {agentPolicy && + !agentPolicy.monitoring_enabled?.includes('logs') && + !agentPolicy.is_managed && } From 17fcc359a4c3aa1a03e04c93e10add21f768fbe6 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 22 Nov 2021 11:19:43 +0100 Subject: [PATCH 05/11] [ML] Model management fixes and usability enhancements (#118240) * [ML] update expanded rows on refresh * [ML] truncateText for start and state columns * remove deployment stats endpoints, replace with the trained models stats * update layout for pipelines * add edit icon * rename deployment actions * add panels for pipelines tab * change deployment stats layout * remove redundant fields from node details * render roles with badges * format nodes attrs * hide empty stats tab * enable trained models tests * add canViewMlNodes capability, hide Nodes tab for the viewer * remove unused translation * support force stop * import default ml capabilities in the security_solutions plugin * update translation string id * Revert "support force stop" This reverts commit 1b1a3b22 * fix translation string ids * remove mock id * rename test subject * remove getTrainedModelDeploymentStats leftovers * update tags for nodes_overview endpoint * update api integration tests * fix paddings * add info icons for ingest stats * update api integration tests for capabilities with spaces * expand first 3 pipelines by default * fix typo in the filename * add help_icon component * fix test assertion --- x-pack/plugins/ml/common/index.ts | 1 + .../plugins/ml/common/types/capabilities.ts | 2 + .../plugins/ml/common/types/trained_models.ts | 14 +- .../components/help_icon/help_icon.tsx | 23 + .../components/help_icon/index.tsx | 8 + .../services/ml_api_service/trained_models.ts | 16 - .../models_management/expanded_row.tsx | 307 ++------ .../models_management/models_list.tsx | 56 +- .../pipelines/expanded_row.tsx | 177 +++++ .../models_management/pipelines/index.ts | 8 + .../models_management/pipelines/pipelines.tsx | 119 ++++ .../trained_models/navigation_bar.tsx | 25 +- .../nodes_overview/allocated_models.tsx | 43 +- .../nodes_overview/expanded_row.tsx | 13 +- .../nodes_overview/nodes_list.tsx | 16 +- .../application/trained_models/page.tsx | 5 - .../capabilities/check_capabilities.test.ts | 4 +- .../ml/server/lib/ml_client/ml_client.ts | 5 - .../plugins/ml/server/lib/ml_client/types.ts | 1 - .../__mocks__/mock_deployment_response.json | 674 +++++++++--------- .../model_provider.test.ts | 21 +- .../data_frame_analytics/models_provider.ts | 25 +- x-pack/plugins/ml/server/routes/apidoc.json | 1 - .../ml/server/routes/trained_models.ts | 38 +- .../machine_learning/empty_ml_capabilities.ts | 35 +- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../apis/ml/system/capabilities.ts | 4 +- .../apis/ml/system/space_capabilities.ts | 8 +- .../apps/ml/model_management/model_list.ts | 3 +- 30 files changed, 882 insertions(+), 774 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/components/help_icon/help_icon.tsx create mode 100644 x-pack/plugins/ml/public/application/components/help_icon/index.tsx create mode 100644 x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/expanded_row.tsx create mode 100644 x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/index.ts create mode 100644 x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/pipelines.tsx diff --git a/x-pack/plugins/ml/common/index.ts b/x-pack/plugins/ml/common/index.ts index ad9927a533a60..ea8ad43d6bb3b 100644 --- a/x-pack/plugins/ml/common/index.ts +++ b/x-pack/plugins/ml/common/index.ts @@ -14,3 +14,4 @@ export { composeValidators, patternValidator } from './util/validators'; export { isRuntimeMappings, isRuntimeField } from './util/runtime_field_utils'; export { extractErrorMessage } from './util/errors'; export type { RuntimeMappings } from './types/fields'; +export { getDefaultCapabilities as getDefaultMlCapabilities } from './types/capabilities'; diff --git a/x-pack/plugins/ml/common/types/capabilities.ts b/x-pack/plugins/ml/common/types/capabilities.ts index ed0f3595cb94c..36377aaa1ed3f 100644 --- a/x-pack/plugins/ml/common/types/capabilities.ts +++ b/x-pack/plugins/ml/common/types/capabilities.ts @@ -63,6 +63,8 @@ export const adminMlCapabilities = { // Alerts canCreateMlAlerts: false, canUseMlAlerts: false, + // Model management + canViewMlNodes: false, }; export type UserMlCapabilities = typeof userMlCapabilities; diff --git a/x-pack/plugins/ml/common/types/trained_models.ts b/x-pack/plugins/ml/common/types/trained_models.ts index 89b8a50846cb3..0670849f07f26 100644 --- a/x-pack/plugins/ml/common/types/trained_models.ts +++ b/x-pack/plugins/ml/common/types/trained_models.ts @@ -151,6 +151,8 @@ export interface TrainedModelDeploymentStatsResponse { routing_state: { routing_state: string }; average_inference_time_ms: number; last_access: number; + number_of_pending_requests: number; + start_time: number; }>; } @@ -161,11 +163,18 @@ export interface AllocatedModel { state: string; allocation_count: number; }; - model_id: string; + /** + * Not required for rendering in the Model stats + */ + model_id?: string; state: string; model_threads: number; model_size_bytes: number; node: { + /** + * Not required for rendering in the Nodes overview + */ + name?: string; average_inference_time_ms: number; inference_count: number; routing_state: { @@ -173,13 +182,14 @@ export interface AllocatedModel { reason?: string; }; last_access?: number; + number_of_pending_requests: number; + start_time: number; }; } export interface NodeDeploymentStatsResponse { id: string; name: string; - transport_address: string; attributes: Record; roles: string[]; allocated_models: AllocatedModel[]; diff --git a/x-pack/plugins/ml/public/application/components/help_icon/help_icon.tsx b/x-pack/plugins/ml/public/application/components/help_icon/help_icon.tsx new file mode 100644 index 0000000000000..5ab4fd4de5dd3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/help_icon/help_icon.tsx @@ -0,0 +1,23 @@ +/* + * 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 React, { FC, ReactNode } from 'react'; +import { EuiIcon, EuiToolTip } from '@elastic/eui'; + +export const HelpIcon: FC<{ content: ReactNode | string }> = ({ content }) => { + return ( + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/components/help_icon/index.tsx b/x-pack/plugins/ml/public/application/components/help_icon/index.tsx new file mode 100644 index 0000000000000..712f457da47c8 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/help_icon/index.tsx @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { HelpIcon } from './help_icon'; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts index c483b0a23c2d0..822c5059982e7 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts @@ -15,7 +15,6 @@ import { ModelPipelines, TrainedModelStat, NodesOverviewResponse, - TrainedModelDeploymentStatsResponse, } from '../../../../common/types/trained_models'; export interface InferenceQueryParams { @@ -122,21 +121,6 @@ export function trainedModelsApiProvider(httpService: HttpService) { }); }, - getTrainedModelDeploymentStats(modelId?: string | string[]) { - let model = modelId ?? '*'; - if (Array.isArray(modelId)) { - model = modelId.join(','); - } - - return httpService.http<{ - count: number; - deployment_stats: TrainedModelDeploymentStatsResponse[]; - }>({ - path: `${apiBasePath}/trained_models/${model}/deployment/_stats`, - method: 'GET', - }); - }, - getTrainedModelsNodesOverview() { return httpService.http({ path: `${apiBasePath}/trained_models/nodes_overview`, diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/expanded_row.tsx index 6dd7db1dbb7b6..469973a378c83 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/expanded_row.tsx @@ -5,53 +5,50 @@ * 2.0. */ -import React, { FC, Fragment, useEffect, useState } from 'react'; -import { omit } from 'lodash'; +import React, { FC, useEffect, useState } from 'react'; +import { omit, pick } from 'lodash'; import { EuiBadge, - EuiButtonEmpty, EuiCodeBlock, EuiDescriptionList, EuiFlexGrid, - EuiFlexGroup, EuiFlexItem, - EuiHorizontalRule, - EuiListGroup, EuiNotificationBadge, EuiPanel, EuiSpacer, EuiTabbedContent, - EuiText, - EuiTextColor, EuiTitle, } from '@elastic/eui'; -import { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list'; +import type { EuiDescriptionListProps } from '@elastic/eui/src/components/description_list/description_list'; import { FormattedMessage } from '@kbn/i18n/react'; -import type { EuiListGroupItemProps } from '@elastic/eui/src/components/list_group/list_group_item'; -import { ModelItemFull } from './models_list'; -import { useMlKibana, useMlLocator } from '../../contexts/kibana'; +import type { ModelItemFull } from './models_list'; import { timeFormatter } from '../../../../common/util/date_utils'; import { isDefined } from '../../../../common/types/guards'; import { isPopulatedObject } from '../../../../common'; -import { ML_PAGES } from '../../../../common/constants/locator'; +import { ModelPipelines } from './pipelines'; +import { AllocatedModels } from '../nodes_overview/allocated_models'; +import type { AllocatedModel } from '../../../../common/types/trained_models'; interface ExpandedRowProps { item: ModelItemFull; } +const badgeFormatter = (items: string[]) => { + if (items.length === 0) return; + return ( +
+ {items.map((item) => ( + + {item} + + ))} +
+ ); +}; + const formatterDictionary: Record JSX.Element | string | undefined> = { - tags: (tags: string[]) => { - if (tags.length === 0) return; - return ( -
- {tags.map((tag) => ( - - {tag} - - ))} -
- ); - }, + tags: badgeFormatter, + roles: badgeFormatter, create_time: timeFormatter, timestamp: timeFormatter, }; @@ -89,11 +86,7 @@ export function formatToListItems( } export const ExpandedRow: FC = ({ item }) => { - const mlLocator = useMlLocator(); - - const [deploymentStatsItems, setDeploymentStats] = useState( - [] - ); + const [modelItems, setModelItems] = useState([]); const { inference_config: inferenceConfig, @@ -125,41 +118,32 @@ export const ExpandedRow: FC = ({ item }) => { license_level, }; - const { - services: { share }, - } = useMlKibana(); - useEffect( - function updateDeploymentState() { + function updateModelItems() { (async function () { - const { nodes, ...deploymentStats } = stats.deployment_stats ?? {}; - - if (!isPopulatedObject(deploymentStats)) return; + const deploymentStats = stats.deployment_stats; - const result = formatToListItems(deploymentStats)!; + if (!deploymentStats) return; - const items: EuiListGroupItemProps[] = await Promise.all( - nodes!.map(async (v) => { - const nodeObject = Object.values(v.node)[0]; - const href = await mlLocator!.getUrl({ - page: ML_PAGES.TRAINED_MODELS_NODES, - pageState: { - nodeId: nodeObject.name, - }, - }); - return { - label: nodeObject.name, - href, - }; - }) - ); - - result.push({ - title: 'nodes', - description: , + const items: AllocatedModel[] = deploymentStats.nodes.map((n) => { + const nodeName = Object.values(n.node)[0].name; + return { + ...deploymentStats, + node: { + ...pick(n, [ + 'average_inference_time_ms', + 'inference_count', + 'routing_state', + 'last_access', + 'number_of_pending_requests', + 'start_time', + ]), + name: nodeName, + } as AllocatedModel['node'], + }; }); - setDeploymentStats(result); + setModelItems(items); })(); }, [stats.deployment_stats] @@ -176,7 +160,7 @@ export const ExpandedRow: FC = ({ item }) => { ), content: ( <> - + @@ -232,7 +216,7 @@ export const ExpandedRow: FC = ({ item }) => { ), content: ( <> - + @@ -280,7 +264,7 @@ export const ExpandedRow: FC = ({ item }) => { }, ] : []), - ...(isPopulatedObject(omit(stats, 'pipeline_count')) + ...(isPopulatedObject(omit(stats, ['pipeline_count', 'ingest'])) ? [ { id: 'stats', @@ -292,57 +276,33 @@ export const ExpandedRow: FC = ({ item }) => { ), content: ( <> - - {!!deploymentStatsItems?.length ? ( - <> - - -
- -
-
- - -
- - - ) : null} - - {stats.inference_stats && ( - + + + + {!!modelItems?.length ? ( +
- +
- )} - {stats.ingest?.total && ( + ) : null} + {stats.inference_stats ? ( - +
@@ -350,99 +310,18 @@ export const ExpandedRow: FC = ({ item }) => { - - {stats.ingest?.pipelines && ( - <> - - -
- -
-
- - {Object.entries(stats.ingest.pipelines).map( - ([pipelineName, { processors, ...pipelineStats }], i) => { - return ( - - - - - -
- {i + 1}. {pipelineName} -
-
-
-
- - - -
- - - - -
- -
-
- - <> - {processors.map((processor) => { - const name = Object.keys(processor)[0]; - const { stats: processorStats } = processor[name]; - return ( - - - - - -
{name}
-
-
-
- - - -
- - -
- ); - })} - -
- ); - } - )} - - )}
- )} + ) : null}
), }, ] : []), - ...(pipelines && Object.keys(pipelines).length > 0 + ...((pipelines && Object.keys(pipelines).length > 0) || stats.ingest ? [ { id: 'pipelines', @@ -457,66 +336,8 @@ export const ExpandedRow: FC = ({ item }) => { ), content: ( <> - - - {Object.entries(pipelines).map( - ([pipelineName, { processors, description: pipelineDescription }]) => { - return ( - - - - - -
{pipelineName}
-
-
- - { - const locator = share.url.locators.get( - 'INGEST_PIPELINES_APP_LOCATOR' - ); - if (!locator) return; - locator.navigate({ - page: 'pipeline_edit', - pipelineId: pipelineName, - absolute: true, - }); - }} - > - - - -
- - {pipelineDescription && {pipelineDescription}} - - -
- -
-
- - {JSON.stringify(processors, null, 2)} - -
-
- ); - } - )} -
+ + ), }, diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx index 9c3cc1f93a9cd..ce0e47df292de 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx @@ -6,7 +6,6 @@ */ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { omit } from 'lodash'; import { EuiBadge, EuiButton, @@ -153,9 +152,7 @@ export const ModelsList: FC = () => { } // Need to fetch state for 3rd party models to enable/disable actions - await fetchAndPopulateDeploymentStats( - newItems.filter((v) => v.model_type.includes('pytorch')) - ); + await fetchModelsStats(newItems.filter((v) => v.model_type.includes('pytorch'))); setItems(newItems); @@ -237,39 +234,6 @@ export const ModelsList: FC = () => { } }, []); - /** - * Updates model items with deployment stats; - * - * We have to fetch all deployment stats on each update, - * because for stopped models the API returns 404 response. - */ - const fetchAndPopulateDeploymentStats = useCallback(async (modelItems: ModelItem[]) => { - try { - const { deployment_stats: deploymentStats } = - await trainedModelsApiService.getTrainedModelDeploymentStats('*'); - - for (const deploymentStat of deploymentStats) { - const deployedModel = modelItems.find( - (model) => model.model_id === deploymentStat.model_id - ); - - if (deployedModel) { - deployedModel.stats = { - ...(deployedModel.stats ?? {}), - deployment_stats: omit(deploymentStat, 'model_id'), - }; - } - } - } catch (error) { - displayErrorToast( - error, - i18n.translate('xpack.ml.trainedModels.modelsList.fetchDeploymentStatsErrorMessage', { - defaultMessage: 'Fetch deployment stats failed', - }) - ); - } - }, []); - /** * Unique inference types from models */ @@ -398,11 +362,11 @@ export const ModelsList: FC = () => { }, }, { - name: i18n.translate('xpack.ml.inference.modelsList.startModelAllocationActionLabel', { - defaultMessage: 'Start allocation', + name: i18n.translate('xpack.ml.inference.modelsList.startModelDeploymentActionLabel', { + defaultMessage: 'Start deployment', }), - description: i18n.translate('xpack.ml.inference.modelsList.startModelAllocationActionLabel', { - defaultMessage: 'Start allocation', + description: i18n.translate('xpack.ml.inference.modelsList.startModelDeploymentActionLabel', { + defaultMessage: 'Start deployment', }), icon: 'play', type: 'icon', @@ -442,11 +406,11 @@ export const ModelsList: FC = () => { }, }, { - name: i18n.translate('xpack.ml.inference.modelsList.stopModelAllocationActionLabel', { - defaultMessage: 'Stop allocation', + name: i18n.translate('xpack.ml.inference.modelsList.stopModelDeploymentActionLabel', { + defaultMessage: 'Stop deployment', }), - description: i18n.translate('xpack.ml.inference.modelsList.stopModelAllocationActionLabel', { - defaultMessage: 'Stop allocation', + description: i18n.translate('xpack.ml.inference.modelsList.stopModelDeploymentActionLabel', { + defaultMessage: 'Stop deployment', }), icon: 'stop', type: 'icon', @@ -567,6 +531,7 @@ export const ModelsList: FC = () => { defaultMessage: 'Type', }), sortable: true, + truncateText: true, align: 'left', render: (types: string[]) => ( @@ -587,6 +552,7 @@ export const ModelsList: FC = () => { }), sortable: (item) => item.stats?.deployment_stats?.state, align: 'left', + truncateText: true, render: (model: ModelItem) => { const state = model.stats?.deployment_stats?.state; return state ? {state} : null; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/expanded_row.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/expanded_row.tsx new file mode 100644 index 0000000000000..7430d50219d3e --- /dev/null +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/expanded_row.tsx @@ -0,0 +1,177 @@ +/* + * 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 React, { FC } from 'react'; +import { EuiBadge, EuiInMemoryTable, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; +import { i18n } from '@kbn/i18n'; +import { useFieldFormatter } from '../../../contexts/kibana/use_field_formatter'; +import { FIELD_FORMAT_IDS } from '../../../../../../../../src/plugins/field_formats/common'; +import { IngestStatsResponse } from './pipelines'; +import { HelpIcon } from '../../../components/help_icon'; + +interface ProcessorsStatsProps { + stats: Exclude['pipelines'][string]['processors']; +} + +type ProcessorStatsItem = ProcessorsStatsProps['stats'][number][string] & { id: string }; + +export const ProcessorsStats: FC = ({ stats }) => { + const durationFormatter = useFieldFormatter(FIELD_FORMAT_IDS.DURATION); + + const items: ProcessorStatsItem[] = stats.map((v, i) => { + const key = Object.keys(v)[0]; + return { + ...v[key], + id: `${key}_${i}`, + }; + }); + + const columns: Array> = [ + { + field: 'type', + name: i18n.translate( + 'xpack.ml.trainedModels.modelsList.pipelines.processorStats.typeHeader', + { + defaultMessage: 'Processor type', + } + ), + width: '100px', + sortable: true, + truncateText: false, + render: (type: string) => { + return {type}; + }, + 'data-test-subj': 'mlProcessorStatsType', + }, + { + field: 'stats.count', + name: ( + + + + + + + } + /> + + + ), + width: '100px', + truncateText: true, + 'data-test-subj': 'mlProcessorStatsCount', + }, + { + field: 'stats.time_in_millis', + name: ( + + + + + + + } + /> + + + ), + width: '100px', + truncateText: false, + 'data-test-subj': 'mlProcessorStatsTimePerDoc', + render: (v: number) => { + return durationFormatter(v); + }, + }, + { + field: 'stats.current', + name: ( + + + + + + + } + /> + + + ), + width: '100px', + truncateText: false, + 'data-test-subj': 'mlProcessorStatsCurrent', + }, + { + field: 'stats.failed', + name: ( + + + + + + + } + /> + + + ), + width: '100px', + 'data-test-subj': 'mlProcessorStatsFailed', + }, + ]; + + return ( + + allowNeutralSort={false} + columns={columns} + hasActions={false} + isExpandable={false} + isSelectable={false} + items={items} + itemId={'id'} + rowProps={(item) => ({ + 'data-test-subj': `mlProcessorStatsTableRow row-${item.id}`, + })} + onTableChange={() => {}} + data-test-subj={'mlProcessorStatsTable'} + /> + ); +}; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/index.ts b/x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/index.ts new file mode 100644 index 0000000000000..791561b958164 --- /dev/null +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { ModelPipelines } from './pipelines'; diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/pipelines.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/pipelines.tsx new file mode 100644 index 0000000000000..9b2af52eb03c8 --- /dev/null +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/pipelines/pipelines.tsx @@ -0,0 +1,119 @@ +/* + * 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 React, { FC } from 'react'; +import { + EuiButtonEmpty, + EuiCodeBlock, + EuiFlexGrid, + EuiFlexItem, + EuiTitle, + EuiPanel, + EuiAccordion, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useMlKibana } from '../../../contexts/kibana'; +import { ModelItem } from '../models_list'; +import { ProcessorsStats } from './expanded_row'; + +export type IngestStatsResponse = Exclude['ingest']; + +interface ModelPipelinesProps { + pipelines: Exclude; + ingestStats: IngestStatsResponse; +} + +export const ModelPipelines: FC = ({ pipelines, ingestStats }) => { + const { + services: { share }, + } = useMlKibana(); + + return ( + <> + {Object.entries(pipelines).map(([pipelineName, pipelineDefinition], i) => { + // Expand first 3 pipelines by default + const initialIsOpen = i <= 2; + return ( + <> + +
{pipelineName}
+ + } + extraAction={ + { + const locator = share.url.locators.get('INGEST_PIPELINES_APP_LOCATOR'); + if (!locator) return; + locator.navigate({ + page: 'pipeline_edit', + pipelineId: pipelineName, + absolute: true, + }); + }} + iconType={'documentEdit'} + iconSide="left" + > + + + } + paddingSize="l" + initialIsOpen={initialIsOpen} + > + + {ingestStats?.pipelines ? ( + + + +
+ +
+
+ + +
+
+ ) : null} + + + + +
+ +
+
+ + {JSON.stringify(pipelineDefinition, null, 2)} + +
+
+
+
+ + ); + })} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/trained_models/navigation_bar.tsx b/x-pack/plugins/ml/public/application/trained_models/navigation_bar.tsx index da8605f075c2f..ec91499bdb722 100644 --- a/x-pack/plugins/ml/public/application/trained_models/navigation_bar.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/navigation_bar.tsx @@ -9,6 +9,7 @@ import React, { FC, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiTab, EuiTabs } from '@elastic/eui'; import { useNavigateToPath } from '../contexts/kibana'; +import { checkPermission } from '../capabilities/check_capabilities'; interface Tab { id: string; @@ -21,6 +22,8 @@ export const TrainedModelsNavigationBar: FC<{ }> = ({ selectedTabId }) => { const navigateToPath = useNavigateToPath(); + const canViewMlNodes = checkPermission('canViewMlNodes'); + const tabs = useMemo(() => { const navTabs = [ { @@ -31,17 +34,21 @@ export const TrainedModelsNavigationBar: FC<{ path: '/trained_models', testSubj: 'mlTrainedModelsTab', }, - { - id: 'nodes', - name: i18n.translate('xpack.ml.trainedModels.nodesTabLabel', { - defaultMessage: 'Nodes', - }), - path: '/trained_models/nodes', - testSubj: 'mlNodesOverviewTab', - }, + ...(canViewMlNodes + ? [ + { + id: 'nodes', + name: i18n.translate('xpack.ml.trainedModels.nodesTabLabel', { + defaultMessage: 'Nodes', + }), + path: '/trained_models/nodes', + testSubj: 'mlNodesOverviewTab', + }, + ] + : []), ]; return navTabs; - }, []); + }, [canViewMlNodes]); const onTabClick = useCallback( async (tab: Tab) => { diff --git a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/allocated_models.tsx b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/allocated_models.tsx index 2aad8183b7998..f26be61fce6f7 100644 --- a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/allocated_models.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/allocated_models.tsx @@ -17,15 +17,31 @@ import { FIELD_FORMAT_IDS } from '../../../../../../../src/plugins/field_formats interface AllocatedModelsProps { models: NodeDeploymentStatsResponse['allocated_models']; + hideColumns?: string[]; } -export const AllocatedModels: FC = ({ models }) => { +export const AllocatedModels: FC = ({ + models, + hideColumns = ['node_name'], +}) => { const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES); const dateFormatter = useFieldFormatter(FIELD_FORMAT_IDS.DATE); const durationFormatter = useFieldFormatter(FIELD_FORMAT_IDS.DURATION); const columns: Array> = [ { + id: 'node_name', + field: 'node.name', + name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsList.nodeNameHeader', { + defaultMessage: 'Node name', + }), + width: '200px', + sortable: true, + truncateText: false, + 'data-test-subj': 'mlAllocatedModelsTableNodeName', + }, + { + id: 'model_id', field: 'model_id', name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsList.modelNameHeader', { defaultMessage: 'Name', @@ -84,6 +100,16 @@ export const AllocatedModels: FC = ({ models }) => { return v.node.inference_count; }, }, + { + name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsList.modelStartTimeHeader', { + defaultMessage: 'Start time', + }), + width: '200px', + 'data-test-subj': 'mlAllocatedModelsTableStartedTime', + render: (v: AllocatedModel) => { + return dateFormatter(v.node.start_time); + }, + }, { name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsList.modelLastAccessHeader', { defaultMessage: 'Last access', @@ -94,6 +120,19 @@ export const AllocatedModels: FC = ({ models }) => { return dateFormatter(v.node.last_access); }, }, + { + name: i18n.translate( + 'xpack.ml.trainedModels.nodesList.modelsList.modelNumberOfPendingRequestsHeader', + { + defaultMessage: 'Pending requests', + } + ), + width: '100px', + 'data-test-subj': 'mlAllocatedModelsTableNumberOfPendingRequests', + render: (v: AllocatedModel) => { + return v.node.number_of_pending_requests; + }, + }, { name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsList.modelRoutingStateHeader', { defaultMessage: 'Routing state', @@ -110,7 +149,7 @@ export const AllocatedModels: FC = ({ models }) => { ); }, }, - ]; + ].filter((v) => !hideColumns.includes(v.id!)); return ( diff --git a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/expanded_row.tsx b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/expanded_row.tsx index 508a5689e1c9b..ba5cdd9093210 100644 --- a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/expanded_row.tsx @@ -15,15 +15,19 @@ import { EuiTitle, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { NodeItemWithStats } from './nodes_list'; +import { NodeItem } from './nodes_list'; import { formatToListItems } from '../models_management/expanded_row'; import { AllocatedModels } from './allocated_models'; +import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter'; +import { FIELD_FORMAT_IDS } from '../../../../../../../src/plugins/field_formats/common'; interface ExpandedRowProps { - item: NodeItemWithStats; + item: NodeItem; } export const ExpandedRow: FC = ({ item }) => { + const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES); + const { allocated_models: allocatedModels, attributes, @@ -31,6 +35,11 @@ export const ExpandedRow: FC = ({ item }) => { ...details } = item; + // Process node attributes + attributes['ml.machine_memory'] = bytesFormatter(attributes['ml.machine_memory']); + attributes['ml.max_jvm_size'] = bytesFormatter(attributes['ml.max_jvm_size']); + delete attributes['xpack.installed']; + return ( <> diff --git a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx index b1cc18e698c9d..87211fedaea43 100644 --- a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx @@ -36,10 +36,6 @@ import { useRefresh } from '../../routing/use_refresh'; export type NodeItem = NodeDeploymentStatsResponse; -export interface NodeItemWithStats extends NodeItem { - stats: any; -} - export const getDefaultNodesListState = (): ListingPageUrlState => ({ pageIndex: 0, pageSize: 10, @@ -70,6 +66,14 @@ export const NodesList: FC = () => { try { const nodesResponse = await trainedModelsApiService.getTrainedModelsNodesOverview(); setItems(nodesResponse.nodes); + + // Update expanded rows. + nodesResponse.nodes.forEach((node) => { + if (itemIdToExpandedRowMap[node.id]) { + itemIdToExpandedRowMap[node.id] = ; + } + }); + setIsLoading(false); refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.IDLE); } catch (e) { @@ -80,14 +84,14 @@ export const NodesList: FC = () => { }) ); } - }, []); + }, [itemIdToExpandedRowMap]); const toggleDetails = (item: NodeItem) => { const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; if (itemIdToExpandedRowMapValues[item.id]) { delete itemIdToExpandedRowMapValues[item.id]; } else { - itemIdToExpandedRowMapValues[item.id] = ; + itemIdToExpandedRowMapValues[item.id] = ; } setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); }; diff --git a/x-pack/plugins/ml/public/application/trained_models/page.tsx b/x-pack/plugins/ml/public/application/trained_models/page.tsx index 54849f3e651df..afbebf58937b3 100644 --- a/x-pack/plugins/ml/public/application/trained_models/page.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/page.tsx @@ -28,14 +28,9 @@ import { ModelsList } from './models_management'; import { TrainedModelsNavigationBar } from './navigation_bar'; import { RefreshAnalyticsListButton } from '../data_frame_analytics/pages/analytics_management/components/refresh_analytics_list_button'; import { DatePickerWrapper } from '../components/navigation_menu/date_picker_wrapper'; -import { useRefreshAnalyticsList } from '../data_frame_analytics/common'; -import { useRefreshInterval } from '../data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval'; import { NodesList } from './nodes_overview'; export const Page: FC = () => { - useRefreshInterval(() => {}); - - useRefreshAnalyticsList({ isLoading: () => {} }); const location = useLocation(); const selectedTabId = useMemo(() => location.pathname.split('/').pop(), [location]); diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts index 920e1f703422d..c72b4d5cb5dd7 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts @@ -51,7 +51,7 @@ describe('check_capabilities', () => { ); const { capabilities } = await getCapabilities(); const count = Object.keys(capabilities).length; - expect(count).toBe(31); + expect(count).toBe(32); }); }); @@ -101,6 +101,7 @@ describe('check_capabilities', () => { expect(capabilities.canCreateDataFrameAnalytics).toBe(false); expect(capabilities.canStartStopDataFrameAnalytics).toBe(false); expect(capabilities.canCreateMlAlerts).toBe(false); + expect(capabilities.canViewMlNodes).toBe(false); }); test('full capabilities', async () => { @@ -146,6 +147,7 @@ describe('check_capabilities', () => { expect(capabilities.canDeleteDataFrameAnalytics).toBe(true); expect(capabilities.canCreateDataFrameAnalytics).toBe(true); expect(capabilities.canStartStopDataFrameAnalytics).toBe(true); + expect(capabilities.canViewMlNodes).toBe(true); }); test('upgrade in progress with full capabilities', async () => { diff --git a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts index 6169d9ee9db47..c2b98ab1b0c29 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts @@ -380,11 +380,6 @@ export function getMlClient( async getTrainedModelsStats(...p: Parameters) { return mlClient.getTrainedModelsStats(...p); }, - async getTrainedModelDeploymentStats( - ...p: Parameters - ) { - return mlClient.getTrainedModelDeploymentStats(...p); - }, async startTrainedModelDeployment(...p: Parameters) { return mlClient.startTrainedModelDeployment(...p); }, diff --git a/x-pack/plugins/ml/server/lib/ml_client/types.ts b/x-pack/plugins/ml/server/lib/ml_client/types.ts index d8c65c4f56814..b4778f4e6d5b1 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/types.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/types.ts @@ -48,7 +48,6 @@ export type MlClientParams = | Parameters | Parameters | Parameters - | Parameters | Parameters | Parameters | Parameters diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/__mocks__/mock_deployment_response.json b/x-pack/plugins/ml/server/models/data_frame_analytics/__mocks__/mock_deployment_response.json index 0742c249b67b0..5d80fa26b4c34 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/__mocks__/mock_deployment_response.json +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/__mocks__/mock_deployment_response.json @@ -1,357 +1,355 @@ -{ - "count" : 4, - "deployment_stats" : [ - { - "model_id" : "distilbert-base-uncased-finetuned-sst-2-english", - "model_size_bytes" : 267386880, - "inference_threads" : 1, - "model_threads" : 1, - "state" : "started", - "allocation_status" : { - "allocation_count" : 2, - "target_allocation_count" : 3, - "state" : "started" - }, - "nodes" : [ - { - "node" : { - "3qIoLFnbSi-DwVrYioUCdw" : { - "name" : "node3", - "ephemeral_id" : "WeA49KLuRPmJM_ulLx0ANg", - "transport_address" : "10.142.0.2:9353", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "ingest", - "master", - "ml", - "transform" - ] - } - }, - "routing_state" : { - "routing_state" : "started" - }, - "inference_count" : 0, - "average_inference_time_ms" : 0.0 +[ + { + "model_id": "distilbert-base-uncased-finetuned-sst-2-english", + "model_size_bytes": 267386880, + "inference_threads": 1, + "model_threads": 1, + "state": "started", + "allocation_status": { + "allocation_count": 2, + "target_allocation_count": 3, + "state": "started" + }, + "nodes": [ + { + "node": { + "3qIoLFnbSi-DwVrYioUCdw": { + "name": "node3", + "ephemeral_id": "WeA49KLuRPmJM_ulLx0ANg", + "transport_address": "10.142.0.2:9353", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "ingest", + "master", + "ml", + "transform" + ] + } }, - { - "node" : { - "DpCy7SOBQla3pu0Dq-tnYw" : { - "name" : "node2", - "ephemeral_id" : "17qcsXsNTYqbJ6uwSvdl9g", - "transport_address" : "10.142.0.2:9352", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "master", - "ml", - "transform" - ] - } - }, - "routing_state" : { - "routing_state" : "failed", - "reason" : "The object cannot be set twice!" + "routing_state": { + "routing_state": "started" + }, + "inference_count": 0, + "average_inference_time_ms": 0.0 + }, + { + "node": { + "DpCy7SOBQla3pu0Dq-tnYw": { + "name": "node2", + "ephemeral_id": "17qcsXsNTYqbJ6uwSvdl9g", + "transport_address": "10.142.0.2:9352", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "master", + "ml", + "transform" + ] } }, - { - "node" : { - "pt7s6lKHQJaP4QHKtU-Q0Q" : { - "name" : "node1", - "ephemeral_id" : "nMJBE9WSRQSWotk0zDPi_Q", - "transport_address" : "10.142.0.2:9351", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "master", - "ml" - ] - } - }, - "routing_state" : { - "routing_state" : "started" - }, - "inference_count" : 0, - "average_inference_time_ms" : 0.0 + "routing_state": { + "routing_state": "failed", + "reason": "The object cannot be set twice!" } - ] - }, - { - "model_id" : "elastic__distilbert-base-cased-finetuned-conll03-english", - "model_size_bytes" : 260947500, - "inference_threads" : 1, - "model_threads" : 1, - "state" : "started", - "allocation_status" : { - "allocation_count" : 2, - "target_allocation_count" : 3, - "state" : "started" }, - "nodes" : [ - { - "node" : { - "3qIoLFnbSi-DwVrYioUCdw" : { - "name" : "node3", - "ephemeral_id" : "WeA49KLuRPmJM_ulLx0ANg", - "transport_address" : "10.142.0.2:9353", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "ingest", - "master", - "ml", - "transform" - ] - } - }, - "routing_state" : { - "routing_state" : "started" - }, - "inference_count" : 0, - "average_inference_time_ms" : 0.0 + { + "node": { + "pt7s6lKHQJaP4QHKtU-Q0Q": { + "name": "node1", + "ephemeral_id": "nMJBE9WSRQSWotk0zDPi_Q", + "transport_address": "10.142.0.2:9351", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "master", + "ml" + ] + } }, - { - "node" : { - "DpCy7SOBQla3pu0Dq-tnYw" : { - "name" : "node2", - "ephemeral_id" : "17qcsXsNTYqbJ6uwSvdl9g", - "transport_address" : "10.142.0.2:9352", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "master", - "ml", - "transform" - ] - } - }, - "routing_state" : { - "routing_state" : "failed", - "reason" : "The object cannot be set twice!" + "routing_state": { + "routing_state": "started" + }, + "inference_count": 0, + "average_inference_time_ms": 0.0 + } + ] + }, + { + "model_id": "elastic__distilbert-base-cased-finetuned-conll03-english", + "model_size_bytes": 260947500, + "inference_threads": 1, + "model_threads": 1, + "state": "started", + "allocation_status": { + "allocation_count": 2, + "target_allocation_count": 3, + "state": "started" + }, + "nodes": [ + { + "node": { + "3qIoLFnbSi-DwVrYioUCdw": { + "name": "node3", + "ephemeral_id": "WeA49KLuRPmJM_ulLx0ANg", + "transport_address": "10.142.0.2:9353", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "ingest", + "master", + "ml", + "transform" + ] } }, - { - "node" : { - "pt7s6lKHQJaP4QHKtU-Q0Q" : { - "name" : "node1", - "ephemeral_id" : "nMJBE9WSRQSWotk0zDPi_Q", - "transport_address" : "10.142.0.2:9351", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "master", - "ml" - ] - } - }, - "routing_state" : { - "routing_state" : "started" - }, - "inference_count" : 0, - "average_inference_time_ms" : 0.0 + "routing_state": { + "routing_state": "started" + }, + "inference_count": 0, + "average_inference_time_ms": 0.0 + }, + { + "node": { + "DpCy7SOBQla3pu0Dq-tnYw": { + "name": "node2", + "ephemeral_id": "17qcsXsNTYqbJ6uwSvdl9g", + "transport_address": "10.142.0.2:9352", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "master", + "ml", + "transform" + ] + } + }, + "routing_state": { + "routing_state": "failed", + "reason": "The object cannot be set twice!" } - ] - }, - { - "model_id" : "sentence-transformers__msmarco-minilm-l-12-v3", - "model_size_bytes" : 133378867, - "inference_threads" : 1, - "model_threads" : 1, - "state" : "started", - "allocation_status" : { - "allocation_count" : 2, - "target_allocation_count" : 3, - "state" : "started" }, - "nodes" : [ - { - "node" : { - "3qIoLFnbSi-DwVrYioUCdw" : { - "name" : "node3", - "ephemeral_id" : "WeA49KLuRPmJM_ulLx0ANg", - "transport_address" : "10.142.0.2:9353", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "ingest", - "master", - "ml", - "transform" - ] - } - }, - "routing_state" : { - "routing_state" : "started" - }, - "inference_count" : 0, - "average_inference_time_ms" : 0.0 + { + "node": { + "pt7s6lKHQJaP4QHKtU-Q0Q": { + "name": "node1", + "ephemeral_id": "nMJBE9WSRQSWotk0zDPi_Q", + "transport_address": "10.142.0.2:9351", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "master", + "ml" + ] + } }, - { - "node" : { - "DpCy7SOBQla3pu0Dq-tnYw" : { - "name" : "node2", - "ephemeral_id" : "17qcsXsNTYqbJ6uwSvdl9g", - "transport_address" : "10.142.0.2:9352", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "master", - "ml", - "transform" - ] - } - }, - "routing_state" : { - "routing_state" : "failed", - "reason" : "The object cannot be set twice!" + "routing_state": { + "routing_state": "started" + }, + "inference_count": 0, + "average_inference_time_ms": 0.0 + } + ] + }, + { + "model_id": "sentence-transformers__msmarco-minilm-l-12-v3", + "model_size_bytes": 133378867, + "inference_threads": 1, + "model_threads": 1, + "state": "started", + "allocation_status": { + "allocation_count": 2, + "target_allocation_count": 3, + "state": "started" + }, + "nodes": [ + { + "node": { + "3qIoLFnbSi-DwVrYioUCdw": { + "name": "node3", + "ephemeral_id": "WeA49KLuRPmJM_ulLx0ANg", + "transport_address": "10.142.0.2:9353", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "ingest", + "master", + "ml", + "transform" + ] } }, - { - "node" : { - "pt7s6lKHQJaP4QHKtU-Q0Q" : { - "name" : "node1", - "ephemeral_id" : "nMJBE9WSRQSWotk0zDPi_Q", - "transport_address" : "10.142.0.2:9351", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "master", - "ml" - ] - } - }, - "routing_state" : { - "routing_state" : "started" - }, - "inference_count" : 0, - "average_inference_time_ms" : 0.0 + "routing_state": { + "routing_state": "started" + }, + "inference_count": 0, + "average_inference_time_ms": 0.0 + }, + { + "node": { + "DpCy7SOBQla3pu0Dq-tnYw": { + "name": "node2", + "ephemeral_id": "17qcsXsNTYqbJ6uwSvdl9g", + "transport_address": "10.142.0.2:9352", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "master", + "ml", + "transform" + ] + } + }, + "routing_state": { + "routing_state": "failed", + "reason": "The object cannot be set twice!" } - ] - }, - { - "model_id" : "typeform__mobilebert-uncased-mnli", - "model_size_bytes" : 100139008, - "inference_threads" : 1, - "model_threads" : 1, - "state" : "started", - "allocation_status" : { - "allocation_count" : 2, - "target_allocation_count" : 3, - "state" : "started" }, - "nodes" : [ - { - "node" : { - "3qIoLFnbSi-DwVrYioUCdw" : { - "name" : "node3", - "ephemeral_id" : "WeA49KLuRPmJM_ulLx0ANg", - "transport_address" : "10.142.0.2:9353", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "ingest", - "master", - "ml", - "transform" - ] - } - }, - "routing_state" : { - "routing_state" : "started" - }, - "inference_count" : 0, - "average_inference_time_ms" : 0.0 + { + "node": { + "pt7s6lKHQJaP4QHKtU-Q0Q": { + "name": "node1", + "ephemeral_id": "nMJBE9WSRQSWotk0zDPi_Q", + "transport_address": "10.142.0.2:9351", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "master", + "ml" + ] + } + }, + "routing_state": { + "routing_state": "started" + }, + "inference_count": 0, + "average_inference_time_ms": 0.0 + } + ] + }, + { + "model_id": "typeform__mobilebert-uncased-mnli", + "model_size_bytes": 100139008, + "inference_threads": 1, + "model_threads": 1, + "state": "started", + "allocation_status": { + "allocation_count": 2, + "target_allocation_count": 3, + "state": "started" + }, + "nodes": [ + { + "node": { + "3qIoLFnbSi-DwVrYioUCdw": { + "name": "node3", + "ephemeral_id": "WeA49KLuRPmJM_ulLx0ANg", + "transport_address": "10.142.0.2:9353", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "ingest", + "master", + "ml", + "transform" + ] + } }, - { - "node" : { - "DpCy7SOBQla3pu0Dq-tnYw" : { - "name" : "node2", - "ephemeral_id" : "17qcsXsNTYqbJ6uwSvdl9g", - "transport_address" : "10.142.0.2:9352", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "master", - "ml", - "transform" - ] - } - }, - "routing_state" : { - "routing_state" : "failed", - "reason" : "The object cannot be set twice!" + "routing_state": { + "routing_state": "started" + }, + "inference_count": 0, + "average_inference_time_ms": 0.0 + }, + { + "node": { + "DpCy7SOBQla3pu0Dq-tnYw": { + "name": "node2", + "ephemeral_id": "17qcsXsNTYqbJ6uwSvdl9g", + "transport_address": "10.142.0.2:9352", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "master", + "ml", + "transform" + ] } }, - { - "node" : { - "pt7s6lKHQJaP4QHKtU-Q0Q" : { - "name" : "node1", - "ephemeral_id" : "nMJBE9WSRQSWotk0zDPi_Q", - "transport_address" : "10.142.0.2:9351", - "attributes" : { - "ml.machine_memory" : "15599742976", - "xpack.installed" : "true", - "ml.max_jvm_size" : "1073741824" - }, - "roles" : [ - "data", - "master", - "ml" - ] - } - }, - "routing_state" : { - "routing_state" : "started" - }, - "inference_count" : 0, - "average_inference_time_ms" : 0.0 + "routing_state": { + "routing_state": "failed", + "reason": "The object cannot be set twice!" } - ] - } - ] -} + }, + { + "node": { + "pt7s6lKHQJaP4QHKtU-Q0Q": { + "name": "node1", + "ephemeral_id": "nMJBE9WSRQSWotk0zDPi_Q", + "transport_address": "10.142.0.2:9351", + "attributes": { + "ml.machine_memory": "15599742976", + "xpack.installed": "true", + "ml.max_jvm_size": "1073741824" + }, + "roles": [ + "data", + "master", + "ml" + ] + } + }, + "routing_state": { + "routing_state": "started" + }, + "inference_count": 0, + "average_inference_time_ms": 0.0 + } + ] + } +] + diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/model_provider.test.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/model_provider.test.ts index 4f5e1ee9b230c..c0d70aa471992 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/model_provider.test.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/model_provider.test.ts @@ -104,8 +104,16 @@ describe('Model service', () => { }, } as unknown as jest.Mocked; const mlClient = { - getTrainedModelDeploymentStats: jest.fn(() => { - return Promise.resolve({ body: mockResponse }); + getTrainedModelsStats: jest.fn(() => { + return Promise.resolve({ + body: { + trained_model_stats: mockResponse.map((v) => { + return { + deployment_stats: v, + }; + }), + }, + }); }), } as unknown as jest.Mocked; const memoryOverviewService = { @@ -214,9 +222,7 @@ describe('Model service', () => { 'ml.max_jvm_size': '1073741824', 'xpack.installed': 'true', }, - host: '10.10.10.2', id: '3qIoLFnbSi-DwVrYioUCdw', - ip: '10.10.10.2:9353', memory_overview: { anomaly_detection: { total: 0, @@ -251,7 +257,6 @@ describe('Model service', () => { }, }, roles: ['data', 'ingest', 'master', 'ml', 'transform'], - transport_address: '10.10.10.2:9353', }, { name: 'node2', @@ -334,9 +339,7 @@ describe('Model service', () => { 'ml.max_jvm_size': '1073741824', 'xpack.installed': 'true', }, - host: '10.10.10.2', id: 'DpCy7SOBQla3pu0Dq-tnYw', - ip: '10.10.10.2:9352', memory_overview: { anomaly_detection: { total: 0, @@ -371,7 +374,6 @@ describe('Model service', () => { }, }, roles: ['data', 'master', 'ml', 'transform'], - transport_address: '10.10.10.2:9352', }, { allocated_models: [ @@ -457,9 +459,7 @@ describe('Model service', () => { 'ml.max_jvm_size': '1073741824', 'xpack.installed': 'true', }, - host: '10.10.10.2', id: 'pt7s6lKHQJaP4QHKtU-Q0Q', - ip: '10.10.10.2:9351', memory_overview: { anomaly_detection: { total: 0, @@ -495,7 +495,6 @@ describe('Model service', () => { }, name: 'node1', roles: ['data', 'master', 'ml'], - transport_address: '10.10.10.2:9351', }, ], }); diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts index 2f40081f1458d..104e320e7fab1 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts @@ -19,18 +19,11 @@ import { NATIVE_EXECUTABLE_CODE_OVERHEAD, } from '../memory_overview/memory_overview_service'; import { TrainedModelDeploymentStatsResponse } from '../../../common/types/trained_models'; +import { isDefined } from '../../../common/types/guards'; export type ModelService = ReturnType; -const NODE_FIELDS = [ - 'attributes', - 'name', - 'roles', - 'ip', - 'host', - 'transport_address', - 'version', -] as const; +const NODE_FIELDS = ['attributes', 'name', 'roles', 'version'] as const; export type RequiredNodeFields = Pick; @@ -87,8 +80,11 @@ export function modelsProvider( throw new Error('Memory overview service is not provided'); } - const { body: deploymentStats } = await mlClient.getTrainedModelDeploymentStats({ - model_id: '*', + const { + body: { trained_model_stats: trainedModelStats }, + } = await mlClient.getTrainedModelsStats({ + model_id: '_all', + size: 10000, }); const { @@ -105,7 +101,12 @@ export function modelsProvider( const nodeFields = pick(node, NODE_FIELDS) as RequiredNodeFields; const allocatedModels = ( - deploymentStats.deployment_stats as TrainedModelDeploymentStatsResponse[] + trainedModelStats + .map((v) => { + // @ts-ignore new prop + return v.deployment_stats; + }) + .filter(isDefined) as TrainedModelDeploymentStatsResponse[] ) .filter((v) => v.nodes.some((n) => Object.keys(n.node)[0] === nodeId)) .map(({ nodes, ...rest }) => { diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json index b7bd92c913891..e1a839b21f7b0 100644 --- a/x-pack/plugins/ml/server/routes/apidoc.json +++ b/x-pack/plugins/ml/server/routes/apidoc.json @@ -160,7 +160,6 @@ "TrainedModels", "GetTrainedModel", "GetTrainedModelStats", - "GetTrainedModelDeploymentStats", "GetTrainedModelsNodesOverview", "GetTrainedModelPipelines", "StartTrainedModelDeployment", diff --git a/x-pack/plugins/ml/server/routes/trained_models.ts b/x-pack/plugins/ml/server/routes/trained_models.ts index 1837f9e88edf3..e7696861153ff 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.ts @@ -198,7 +198,11 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) path: '/api/ml/trained_models/nodes_overview', validate: {}, options: { - tags: ['access:ml:canGetDataFrameAnalytics'], + tags: [ + 'access:ml:canViewMlNodes', + 'access:ml:canGetDataFrameAnalytics', + 'access:ml:canGetJobs', + ], }, }, routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response }) => { @@ -281,36 +285,4 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) } }) ); - - /** - * @apiGroup TrainedModels - * - * @api {get} /api/ml/trained_models/:modelId/deployment/_stats Get trained model deployment stats - * @apiName GetTrainedModelDeploymentStats - * @apiDescription Gets trained model deployment stats. - */ - router.get( - { - path: '/api/ml/trained_models/{modelId}/deployment/_stats', - validate: { - params: modelIdSchema, - }, - options: { - tags: ['access:ml:canGetDataFrameAnalytics'], - }, - }, - routeGuard.fullLicenseAPIGuard(async ({ mlClient, request, response }) => { - try { - const { modelId } = request.params; - const { body } = await mlClient.getTrainedModelDeploymentStats({ - model_id: modelId, - }); - return response.ok({ - body, - }); - } catch (e) { - return response.customError(wrapError(e)); - } - }) - ); } diff --git a/x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts b/x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts index 772c16fc9cb99..79a0084f91923 100644 --- a/x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts +++ b/x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts @@ -6,41 +6,10 @@ */ import { MlCapabilitiesResponse } from '../../../ml/common/types/capabilities'; +import { getDefaultMlCapabilities } from '../../../ml/common'; export const emptyMlCapabilities: MlCapabilitiesResponse = { - capabilities: { - canAccessML: false, - canGetAnnotations: false, - canCreateAnnotation: false, - canDeleteAnnotation: false, - canGetJobs: false, - canCreateJob: false, - canDeleteJob: false, - canOpenJob: false, - canCloseJob: false, - canResetJob: false, - canForecastJob: false, - canGetDatafeeds: false, - canStartStopDatafeed: false, - canUpdateJob: false, - canUpdateDatafeed: false, - canPreviewDatafeed: false, - canGetCalendars: false, - canCreateCalendar: false, - canDeleteCalendar: false, - canGetFilters: false, - canCreateFilter: false, - canDeleteFilter: false, - canFindFileStructure: false, - canCreateDatafeed: false, - canDeleteDatafeed: false, - canGetDataFrameAnalytics: false, - canDeleteDataFrameAnalytics: false, - canCreateDataFrameAnalytics: false, - canStartStopDataFrameAnalytics: false, - canCreateMlAlerts: false, - canUseMlAlerts: false, - }, + capabilities: getDefaultMlCapabilities(), isPlatinumOrTrialLicense: false, mlFeatureEnabledInSpace: false, upgradeInProgress: false, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index bf8bb82faf706..a27ec93d02c98 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17806,8 +17806,6 @@ "xpack.ml.trainedModels.modelsList.deleteModelsButtonLabel": "削除", "xpack.ml.trainedModels.modelsList.disableSelectableMessage": "モデルにはパイプラインが関連付けられています", "xpack.ml.trainedModels.modelsList.expandedRow.analyticsConfigTitle": "分析構成", - "xpack.ml.trainedModels.modelsList.expandedRow.byPipelineTitle": "パイプライン別", - "xpack.ml.trainedModels.modelsList.expandedRow.byProcessorTitle": "プロセッサー別", "xpack.ml.trainedModels.modelsList.expandedRow.configTabLabel": "構成", "xpack.ml.trainedModels.modelsList.expandedRow.detailsTabLabel": "詳細", "xpack.ml.trainedModels.modelsList.expandedRow.detailsTitle": "詳細", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 024a10fbc3a8c..e63a873cac3e4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18081,8 +18081,6 @@ "xpack.ml.trainedModels.modelsList.deleteModelsButtonLabel": "删除", "xpack.ml.trainedModels.modelsList.disableSelectableMessage": "模型有关联的管道", "xpack.ml.trainedModels.modelsList.expandedRow.analyticsConfigTitle": "分析配置", - "xpack.ml.trainedModels.modelsList.expandedRow.byPipelineTitle": "按管道", - "xpack.ml.trainedModels.modelsList.expandedRow.byProcessorTitle": "按处理器", "xpack.ml.trainedModels.modelsList.expandedRow.configTabLabel": "配置", "xpack.ml.trainedModels.modelsList.expandedRow.detailsTabLabel": "详情", "xpack.ml.trainedModels.modelsList.expandedRow.detailsTitle": "详情", diff --git a/x-pack/test/api_integration/apis/ml/system/capabilities.ts b/x-pack/test/api_integration/apis/ml/system/capabilities.ts index 4eb040d031c2e..d0df53dfee343 100644 --- a/x-pack/test/api_integration/apis/ml/system/capabilities.ts +++ b/x-pack/test/api_integration/apis/ml/system/capabilities.ts @@ -45,7 +45,7 @@ export default ({ getService }: FtrProviderContext) => { it('should have the right number of capabilities', async () => { const { capabilities } = await runRequest(USER.ML_POWERUSER); - expect(Object.keys(capabilities).length).to.eql(31); + expect(Object.keys(capabilities).length).to.eql(32); }); it('should get viewer capabilities', async () => { @@ -83,6 +83,7 @@ export default ({ getService }: FtrProviderContext) => { canGetAnnotations: true, canCreateAnnotation: true, canDeleteAnnotation: true, + canViewMlNodes: false, }); }); @@ -121,6 +122,7 @@ export default ({ getService }: FtrProviderContext) => { canGetAnnotations: true, canCreateAnnotation: true, canDeleteAnnotation: true, + canViewMlNodes: true, }); }); }); diff --git a/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts b/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts index 6d6a00e882689..b51b87457caa2 100644 --- a/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts +++ b/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts @@ -71,11 +71,11 @@ export default ({ getService }: FtrProviderContext) => { it('should have the right number of capabilities - space with ML', async () => { const { capabilities } = await runRequest(USER.ML_POWERUSER, idSpaceWithMl); - expect(Object.keys(capabilities).length).to.eql(31); + expect(Object.keys(capabilities).length).to.eql(32); }); it('should have the right number of capabilities - space without ML', async () => { const { capabilities } = await runRequest(USER.ML_POWERUSER, idSpaceNoMl); - expect(Object.keys(capabilities).length).to.eql(31); + expect(Object.keys(capabilities).length).to.eql(32); }); it('should get viewer capabilities - space with ML', async () => { @@ -112,6 +112,7 @@ export default ({ getService }: FtrProviderContext) => { canGetAnnotations: true, canCreateAnnotation: true, canDeleteAnnotation: true, + canViewMlNodes: false, }); }); @@ -149,6 +150,7 @@ export default ({ getService }: FtrProviderContext) => { canGetAnnotations: false, canCreateAnnotation: false, canDeleteAnnotation: false, + canViewMlNodes: false, }); }); @@ -186,6 +188,7 @@ export default ({ getService }: FtrProviderContext) => { canGetAnnotations: true, canCreateAnnotation: true, canDeleteAnnotation: true, + canViewMlNodes: true, }); }); @@ -223,6 +226,7 @@ export default ({ getService }: FtrProviderContext) => { canGetAnnotations: false, canCreateAnnotation: false, canDeleteAnnotation: false, + canViewMlNodes: false, }); }); }); diff --git a/x-pack/test/functional/apps/ml/model_management/model_list.ts b/x-pack/test/functional/apps/ml/model_management/model_list.ts index aac1ad5b1e50b..955639dbe60a4 100644 --- a/x-pack/test/functional/apps/ml/model_management/model_list.ts +++ b/x-pack/test/functional/apps/ml/model_management/model_list.ts @@ -10,8 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/118251 - describe.skip('trained models', function () { + describe('trained models', function () { before(async () => { await ml.trainedModels.createTestTrainedModels('classification', 15, true); await ml.trainedModels.createTestTrainedModels('regression', 15); From e9932dc98e635a8543deaf642f803f3a89f72888 Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> Date: Mon, 22 Nov 2021 14:57:37 +0300 Subject: [PATCH 06/11] [Discover] Fix search on page load test (#119087) * [Discover] log failure * [Discover] add one more log * [Discover] suggest fix * [Discover] leave only one describe block * [Discover] remove logs * [Discover] simplify comment * [Discover] add description of the fix Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/functional/apps/discover/_search_on_page_load.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/functional/apps/discover/_search_on_page_load.ts b/test/functional/apps/discover/_search_on_page_load.ts index 2a66e03c3cbb8..277d2e72d729f 100644 --- a/test/functional/apps/discover/_search_on_page_load.ts +++ b/test/functional/apps/discover/_search_on_page_load.ts @@ -76,6 +76,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await testSubjects.exists('refreshDataButton')).to.be(true); await retry.waitFor('number of fetches to be 0', waitForFetches(0)); + /** + * We should wait for debounce timeout expired 100 ms, + * otherwise click event will be skipped. See getFetch$ implementation. + */ + await PageObjects.common.sleep(100); await testSubjects.click('refreshDataButton'); await retry.waitFor('number of fetches to be 1', waitForFetches(1)); From 3cc0875f635c8713df9eddae268ad10f116105b3 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 22 Nov 2021 13:22:36 +0000 Subject: [PATCH 07/11] skip flaky suite (#116533) --- x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js index d7c4e5dd12f52..80c8c13b16ddf 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js @@ -17,7 +17,8 @@ export default function ({ getService, getPageObjects }) { // FF issue: https://github.com/elastic/kibana/issues/35551 this.tags(['skipFirefox']); - describe('with offline node', () => { + // FLAKY: https://github.com/elastic/kibana/issues/116533 + describe.skip('with offline node', () => { const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects); before(async () => { From bc1163cb0c4594646390afb562f8cbd2b3cfcc83 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 22 Nov 2021 13:29:40 +0000 Subject: [PATCH 08/11] skip flaky suite (#116725) --- test/api_integration/apis/stats/stats.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/api_integration/apis/stats/stats.js b/test/api_integration/apis/stats/stats.js index 61936a73da38d..c15fa782ea3ae 100644 --- a/test/api_integration/apis/stats/stats.js +++ b/test/api_integration/apis/stats/stats.js @@ -58,7 +58,8 @@ export default function ({ getService }) { ); }); - describe('basic', () => { + // FLAKY: https://github.com/elastic/kibana/issues/116725 + describe.skip('basic', () => { it('should return the stats without cluster_uuid with no query string params', () => { return supertest .get('/api/stats') From 43f7fc0a8e5e835ad04eb307bad3b14c795ee638 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 22 Nov 2021 16:35:10 +0300 Subject: [PATCH 09/11] [Lens] Mosaic / mekko vis type (#117668) * [Lens] Mosaic / mekko vis type Closes: #104223 * some updates * fix color palette logic * fix suggestions * fix JEST * fix some parts * update labels * Fix JEST * add showExperimentalBadge * add sorting * fix toolbar options * fix Marco suggestion Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/expressions/pie_chart/pie_chart.ts | 2 +- .../common/expressions/pie_chart/types.ts | 6 +- .../lens/public/assets/chart_mosaic.tsx | 31 +++ .../config_panel/layer_panel.test.tsx | 34 ++- .../editor_frame/config_panel/layer_panel.tsx | 30 ++- .../public/pie_visualization/constants.ts | 63 +++++- .../pie_visualization/render_function.tsx | 61 ++++- .../pie_visualization/render_helpers.test.ts | 121 +++++++++- .../pie_visualization/render_helpers.ts | 66 +++++- .../pie_visualization/suggestions.test.ts | 208 +++++++++++++++++- .../public/pie_visualization/suggestions.ts | 75 ++++++- .../lens/public/pie_visualization/toolbar.tsx | 84 ++----- .../pie_visualization/visualization.tsx | 142 ++++++------ x-pack/plugins/lens/public/types.ts | 1 + .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 16 files changed, 753 insertions(+), 173 deletions(-) create mode 100644 x-pack/plugins/lens/public/assets/chart_mosaic.tsx diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts index ed0391a16af25..053b46e480c7b 100644 --- a/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts +++ b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts @@ -49,7 +49,7 @@ export const pie: ExpressionFunctionDefinition< }, shape: { types: ['string'], - options: ['pie', 'donut', 'treemap'], + options: ['pie', 'donut', 'treemap', 'mosaic'], help: '', }, hideLabels: { diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/types.ts b/x-pack/plugins/lens/common/expressions/pie_chart/types.ts index 8712675740f1c..00fc7abaa043b 100644 --- a/x-pack/plugins/lens/common/expressions/pie_chart/types.ts +++ b/x-pack/plugins/lens/common/expressions/pie_chart/types.ts @@ -8,6 +8,8 @@ import type { PaletteOutput } from '../../../../../../src/plugins/charts/common'; import type { LensMultiTable, LayerType } from '../../types'; +export type PieChartTypes = 'donut' | 'pie' | 'treemap' | 'mosaic'; + export interface SharedPieLayerState { groups: string[]; metric?: string; @@ -27,7 +29,7 @@ export type PieLayerState = SharedPieLayerState & { }; export interface PieVisualizationState { - shape: 'donut' | 'pie' | 'treemap'; + shape: PieChartTypes; layers: PieLayerState[]; palette?: PaletteOutput; } @@ -35,7 +37,7 @@ export interface PieVisualizationState { export type PieExpressionArgs = SharedPieLayerState & { title?: string; description?: string; - shape: 'pie' | 'donut' | 'treemap'; + shape: PieChartTypes; hideLabels: boolean; palette: PaletteOutput; }; diff --git a/x-pack/plugins/lens/public/assets/chart_mosaic.tsx b/x-pack/plugins/lens/public/assets/chart_mosaic.tsx new file mode 100644 index 0000000000000..c385f0df1a008 --- /dev/null +++ b/x-pack/plugins/lens/public/assets/chart_mosaic.tsx @@ -0,0 +1,31 @@ +/* + * 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 React from 'react'; +import type { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartMosaic = ({ title, titleId, ...props }: Omit) => ( + + {title ? : null} + + + +); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 92633d5e7305b..a6be4acfbbcf1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -225,7 +225,39 @@ describe('LayerPanel', () => { const group = instance .find(EuiFormRow) - .findWhere((e) => e.prop('error') === 'Required dimension'); + .findWhere((e) => e.prop('error') === 'Requires field'); + + expect(group).toHaveLength(1); + }); + + it('should render the required warning when only one group is configured (with requiredMinDimensionCount)', async () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [{ columnId: 'x' }], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + { + groupLabel: 'B', + groupId: 'b', + accessors: [{ columnId: 'y' }], + filterOperations: () => true, + supportsMoreColumns: true, + dataTestSubj: 'lnsGroup', + requiredMinDimensionCount: 2, + }, + ], + }); + + const { instance } = await mountWithProvider(); + + const group = instance + .find(EuiFormRow) + .findWhere((e) => e.prop('error') === 'Requires 2 fields'); expect(group).toHaveLength(1); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 6af3d88b17d41..84c7722ca1b88 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -385,7 +385,27 @@ export function LayerPanel( {groups.map((group, groupIndex) => { - const isMissing = !isEmptyLayer && group.required && group.accessors.length === 0; + let isMissing = false; + + if (!isEmptyLayer) { + if (group.requiredMinDimensionCount) { + isMissing = group.accessors.length < group.requiredMinDimensionCount; + } else if (group.required) { + isMissing = group.accessors.length === 0; + } + } + + const isMissingError = group.requiredMinDimensionCount + ? i18n.translate('xpack.lens.editorFrame.requiresTwoOrMoreFieldsWarningLabel', { + defaultMessage: 'Requires {requiredMinDimensionCount} fields', + values: { + requiredMinDimensionCount: group.requiredMinDimensionCount, + }, + }) + : i18n.translate('xpack.lens.editorFrame.requiresFieldWarningLabel', { + defaultMessage: 'Requires field', + }); + const isOptional = !group.required; return ( <> {group.accessors.length ? ( diff --git a/x-pack/plugins/lens/public/pie_visualization/constants.ts b/x-pack/plugins/lens/public/pie_visualization/constants.ts index 9a2f39e7d34a5..be0afc65aed3b 100644 --- a/x-pack/plugins/lens/public/pie_visualization/constants.ts +++ b/x-pack/plugins/lens/public/pie_visualization/constants.ts @@ -6,41 +6,100 @@ */ import { i18n } from '@kbn/i18n'; +import { PartitionLayout } from '@elastic/charts'; import { LensIconChartDonut } from '../assets/chart_donut'; import { LensIconChartPie } from '../assets/chart_pie'; import { LensIconChartTreemap } from '../assets/chart_treemap'; +import { LensIconChartMosaic } from '../assets/chart_mosaic'; + +import type { SharedPieLayerState } from '../../common/expressions'; + +interface CategoryOption { + value: SharedPieLayerState['categoryDisplay']; + inputDisplay: string; +} const groupLabel = i18n.translate('xpack.lens.pie.groupLabel', { defaultMessage: 'Proportion', }); +const categoryOptions: CategoryOption[] = [ + { + value: 'default', + inputDisplay: i18n.translate('xpack.lens.pieChart.showCategoriesLabel', { + defaultMessage: 'Inside or outside', + }), + }, + { + value: 'inside', + inputDisplay: i18n.translate('xpack.lens.pieChart.fitInsideOnlyLabel', { + defaultMessage: 'Inside only', + }), + }, + { + value: 'hide', + inputDisplay: i18n.translate('xpack.lens.pieChart.categoriesInLegendLabel', { + defaultMessage: 'Hide labels', + }), + }, +]; + +const categoryOptionsTreemap: CategoryOption[] = [ + { + value: 'default', + inputDisplay: i18n.translate('xpack.lens.pieChart.showTreemapCategoriesLabel', { + defaultMessage: 'Show labels', + }), + }, + { + value: 'hide', + inputDisplay: i18n.translate('xpack.lens.pieChart.categoriesInLegendLabel', { + defaultMessage: 'Hide labels', + }), + }, +]; + export const CHART_NAMES = { donut: { icon: LensIconChartDonut, label: i18n.translate('xpack.lens.pie.donutLabel', { defaultMessage: 'Donut', }), + partitionType: PartitionLayout.sunburst, groupLabel, + categoryOptions, }, pie: { icon: LensIconChartPie, label: i18n.translate('xpack.lens.pie.pielabel', { defaultMessage: 'Pie', }), - + partitionType: PartitionLayout.sunburst, groupLabel, + categoryOptions, }, treemap: { icon: LensIconChartTreemap, label: i18n.translate('xpack.lens.pie.treemaplabel', { defaultMessage: 'Treemap', }), - + partitionType: PartitionLayout.treemap, + groupLabel, + categoryOptions: categoryOptionsTreemap, + }, + mosaic: { + icon: LensIconChartMosaic, + label: i18n.translate('xpack.lens.pie.mosaiclabel', { + defaultMessage: 'Mosaic', + }), + partitionType: PartitionLayout.mosaic, groupLabel, + categoryOptions: [] as CategoryOption[], }, }; export const MAX_PIE_BUCKETS = 3; export const MAX_TREEMAP_BUCKETS = 2; +export const MAX_MOSAIC_BUCKETS = 2; export const DEFAULT_PERCENT_DECIMALS = 2; diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index 05b9ca9c34168..2bf9827bb976e 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -16,7 +16,6 @@ import { Partition, PartitionConfig, PartitionLayer, - PartitionLayout, PartitionFillLabel, RecursivePartial, Position, @@ -29,7 +28,13 @@ import { VisualizationContainer } from '../visualization_container'; import { CHART_NAMES, DEFAULT_PERCENT_DECIMALS } from './constants'; import type { FormatFactory } from '../../common'; import type { PieExpressionProps } from '../../common/expressions'; -import { getSliceValue, getFilterContext } from './render_helpers'; +import { + getSliceValue, + getFilterContext, + isTreemapOrMosaicShape, + byDataColorPaletteMap, + extractUniqTermsMap, +} from './render_helpers'; import { EmptyPlaceholder } from '../shared_components'; import './visualization.scss'; import { @@ -110,6 +115,22 @@ export function PieComponent( }) ).length; + const shouldUseByDataPalette = !syncColors && ['mosaic'].includes(shape) && bucketColumns[1]?.id; + let byDataPalette: ReturnType; + if (shouldUseByDataPalette) { + byDataPalette = byDataColorPaletteMap( + firstTable, + bucketColumns[1].id, + paletteService.get(palette.name), + palette + ); + } + + let sortingMap: Record; + if (shape === 'mosaic') { + sortingMap = extractUniqTermsMap(firstTable, bucketColumns[0].id); + } + const layers: PartitionLayer[] = bucketColumns.map((col, layerIndex) => { return { groupByRollup: (d: Datum) => d[col.id] ?? EMPTY_SLICE, @@ -124,13 +145,29 @@ export function PieComponent( return String(d); }, fillLabel, + sortPredicate: + shape === 'mosaic' + ? ([name1, node1], [, node2]) => { + // Sorting for first group + if (bucketColumns.length === 1 || (node1.children.length && name1 in sortingMap)) { + return sortingMap[name1]; + } + // Sorting for second group + return node2.value - node1.value; + } + : undefined, shape: { fillColor: (d) => { const seriesLayers: SeriesLayer[] = []; + // Mind the difference here: the contrast computation for the text ignores the alpha/opacity + // therefore change it for dask mode + const defaultColor = isDarkMode ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)'; + // Color is determined by round-robin on the index of the innermost slice // This has to be done recursively until we get to the slice index let tempParent: typeof d | typeof d['parent'] = d; + while (tempParent.parent && tempParent.depth > 0) { seriesLayers.unshift({ name: String(tempParent.parent.children[tempParent.sortIndex][0]), @@ -140,12 +177,14 @@ export function PieComponent( tempParent = tempParent.parent; } - if (shape === 'treemap') { + if (byDataPalette && seriesLayers[1]) { + return byDataPalette.getColor(seriesLayers[1].name) || defaultColor; + } + + if (isTreemapOrMosaicShape(shape)) { // Only highlight the innermost color of the treemap, as it accurately represents area if (layerIndex < bucketColumns.length - 1) { - // Mind the difference here: the contrast computation for the text ignores the alpha/opacity - // therefore change it for dask mode - return isDarkMode ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)'; + return defaultColor; } // only use the top level series layer for coloring if (seriesLayers.length > 1) { @@ -164,14 +203,14 @@ export function PieComponent( palette.params ); - return outputColor || 'rgba(0,0,0,0)'; + return outputColor || defaultColor; }, }, }; }); const config: RecursivePartial = { - partitionLayout: shape === 'treemap' ? PartitionLayout.treemap : PartitionLayout.sunburst, + partitionLayout: CHART_NAMES[shape].partitionType, fontFamily: chartTheme.barSeriesStyle?.displayValue?.fontFamily, outerSizeRatio: 1, specialFirstInnermostSector: true, @@ -191,7 +230,7 @@ export function PieComponent( sectorLineWidth: 1.5, circlePadding: 4, }; - if (shape === 'treemap') { + if (isTreemapOrMosaicShape(shape)) { if (hideLabels || categoryDisplay === 'hide') { config.fillLabel = { textColor: 'rgba(0,0,0,0)' }; } @@ -279,7 +318,9 @@ export function PieComponent( showLegend={ !hideLabels && (legendDisplay === 'show' || - (legendDisplay === 'default' && bucketColumns.length > 1 && shape !== 'treemap')) + (legendDisplay === 'default' && + bucketColumns.length > 1 && + !isTreemapOrMosaicShape(shape))) } legendPosition={legendPosition || Position.Right} legendMaxDepth={nestedLegend ? undefined : 1 /* Color is based only on first layer */} diff --git a/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts b/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts index 7c55c0fa61931..dd27632b36e44 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts +++ b/x-pack/plugins/lens/public/pie_visualization/render_helpers.test.ts @@ -5,8 +5,16 @@ * 2.0. */ -import { Datatable } from 'src/plugins/expressions/public'; -import { getSliceValue, getFilterContext } from './render_helpers'; +import type { Datatable } from 'src/plugins/expressions/public'; +import type { PaletteDefinition, PaletteOutput } from 'src/plugins/charts/public'; + +import { + getSliceValue, + getFilterContext, + byDataColorPaletteMap, + extractUniqTermsMap, +} from './render_helpers'; +import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; describe('render helpers', () => { describe('#getSliceValue', () => { @@ -200,4 +208,113 @@ describe('render helpers', () => { }); }); }); + + describe('#extractUniqTermsMap', () => { + it('should extract map', () => { + const table: Datatable = { + type: 'datatable', + columns: [ + { id: 'a', name: 'A', meta: { type: 'string' } }, + { id: 'b', name: 'B', meta: { type: 'string' } }, + { id: 'c', name: 'C', meta: { type: 'number' } }, + ], + rows: [ + { a: 'Hi', b: 'Two', c: 2 }, + { a: 'Test', b: 'Two', c: 5 }, + { a: 'Foo', b: 'Three', c: 6 }, + ], + }; + expect(extractUniqTermsMap(table, 'a')).toMatchInlineSnapshot(` + Object { + "Foo": 2, + "Hi": 0, + "Test": 1, + } + `); + expect(extractUniqTermsMap(table, 'b')).toMatchInlineSnapshot(` + Object { + "Three": 1, + "Two": 0, + } + `); + }); + }); + + describe('#byDataColorPaletteMap', () => { + let datatable: Datatable; + let paletteDefinition: PaletteDefinition; + let palette: PaletteOutput; + const columnId = 'foo'; + + beforeEach(() => { + datatable = { + rows: [ + { + [columnId]: '1', + }, + { + [columnId]: '2', + }, + ], + } as unknown as Datatable; + paletteDefinition = chartPluginMock.createPaletteRegistry().get('default'); + palette = { type: 'palette' } as PaletteOutput; + }); + + it('should create byDataColorPaletteMap', () => { + expect(byDataColorPaletteMap(datatable, columnId, paletteDefinition, palette)) + .toMatchInlineSnapshot(` + Object { + "getColor": [Function], + } + `); + }); + + it('should get color', () => { + const colorPaletteMap = byDataColorPaletteMap( + datatable, + columnId, + paletteDefinition, + palette + ); + + expect(colorPaletteMap.getColor('1')).toBe('black'); + }); + + it('should return undefined in case if values not in datatable', () => { + const colorPaletteMap = byDataColorPaletteMap( + datatable, + columnId, + paletteDefinition, + palette + ); + + expect(colorPaletteMap.getColor('wrong')).toBeUndefined(); + }); + + it('should increase rankAtDepth for each new value', () => { + const colorPaletteMap = byDataColorPaletteMap( + datatable, + columnId, + paletteDefinition, + palette + ); + colorPaletteMap.getColor('1'); + colorPaletteMap.getColor('2'); + + expect(paletteDefinition.getCategoricalColor).toHaveBeenNthCalledWith( + 1, + [{ name: '1', rankAtDepth: 0, totalSeriesAtDepth: 2 }], + { behindText: false }, + undefined + ); + + expect(paletteDefinition.getCategoricalColor).toHaveBeenNthCalledWith( + 2, + [{ name: '2', rankAtDepth: 1, totalSeriesAtDepth: 2 }], + { behindText: false }, + undefined + ); + }); + }); }); diff --git a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts index d2858efa90153..bdffacde65639 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts +++ b/x-pack/plugins/lens/public/pie_visualization/render_helpers.ts @@ -5,9 +5,11 @@ * 2.0. */ -import { Datum, LayerValue } from '@elastic/charts'; -import { Datatable, DatatableColumn } from 'src/plugins/expressions/public'; -import { LensFilterEvent } from '../types'; +import type { Datum, LayerValue } from '@elastic/charts'; +import type { Datatable, DatatableColumn } from 'src/plugins/expressions/public'; +import type { LensFilterEvent } from '../types'; +import type { PieChartTypes } from '../../common/expressions/pie_chart/types'; +import type { PaletteDefinition, PaletteOutput } from '../../../../../src/plugins/charts/public'; export function getSliceValue(d: Datum, metricColumn: DatatableColumn) { const value = d[metricColumn.id]; @@ -35,3 +37,61 @@ export function getFilterContext( })), }; } + +export const isPartitionShape = (shape: PieChartTypes | string) => + ['donut', 'pie', 'treemap', 'mosaic'].includes(shape); + +export const isTreemapOrMosaicShape = (shape: PieChartTypes | string) => + ['treemap', 'mosaic'].includes(shape); + +export const extractUniqTermsMap = (dataTable: Datatable, columnId: string) => + [...new Set(dataTable.rows.map((item) => item[columnId]))].reduce( + (acc, item, index) => ({ + ...acc, + [item]: index, + }), + {} + ); + +export const byDataColorPaletteMap = ( + dataTable: Datatable, + columnId: string, + paletteDefinition: PaletteDefinition, + { params }: PaletteOutput +) => { + const colorMap = new Map( + dataTable.rows.map((item) => [String(item[columnId]), undefined]) + ); + let rankAtDepth = 0; + + return { + getColor: (item: unknown) => { + const key = String(item); + + if (colorMap.has(key)) { + let color = colorMap.get(key); + + if (color) { + return color; + } + color = + paletteDefinition.getCategoricalColor( + [ + { + name: key, + totalSeriesAtDepth: colorMap.size, + rankAtDepth: rankAtDepth++, + }, + ], + { + behindText: false, + }, + params + ) || undefined; + + colorMap.set(key, color); + return color; + } + }, + }; +}; diff --git a/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts b/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts index 5a57371eb6459..656d00960766e 100644 --- a/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts +++ b/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts @@ -6,9 +6,9 @@ */ import { PaletteOutput } from 'src/plugins/charts/public'; -import { DataType, SuggestionRequest } from '../types'; import { suggestions } from './suggestions'; -import { PieVisualizationState } from '../../common/expressions'; +import type { DataType, SuggestionRequest } from '../types'; +import type { PieLayerState, PieVisualizationState } from '../../common/expressions'; import { layerTypes } from '../../common'; describe('suggestions', () => { @@ -144,6 +144,38 @@ describe('suggestions', () => { ).toHaveLength(0); }); + it('should not reject histogram operations in case of switching between partition charts', () => { + expect( + suggestions({ + table: { + layerId: 'first', + isMultiRow: true, + columns: [ + { + columnId: 'b', + operation: { + label: 'Durations', + dataType: 'number' as DataType, + isBucketed: true, + scale: 'interval', + }, + }, + { + columnId: 'c', + operation: { label: 'Count', dataType: 'number' as DataType, isBucketed: false }, + }, + ], + changeType: 'initial', + }, + state: { + shape: 'mosaic', + layers: [{} as PieLayerState], + }, + keptLayerIds: ['first'], + }).length + ).toBeGreaterThan(0); + }); + it('should reject when there are too many buckets', () => { expect( suggestions({ @@ -272,7 +304,7 @@ describe('suggestions', () => { state: undefined, keptLayerIds: ['first'], }); - expect(currentSuggestions).toHaveLength(3); + expect(currentSuggestions).toHaveLength(4); expect(currentSuggestions.every((s) => s.hide)).toEqual(true); }); @@ -292,7 +324,7 @@ describe('suggestions', () => { state: undefined, keptLayerIds: ['first'], }); - expect(currentSuggestions).toHaveLength(3); + expect(currentSuggestions).toHaveLength(4); expect(currentSuggestions.every((s) => s.hide)).toEqual(true); }); @@ -721,4 +753,172 @@ describe('suggestions', () => { ); }); }); + + describe('mosaic', () => { + it('should reject when currently active and unchanged data', () => { + expect( + suggestions({ + table: { + layerId: 'first', + isMultiRow: true, + columns: [], + changeType: 'unchanged', + }, + state: { + shape: 'mosaic', + layers: [ + { + layerId: 'first', + layerType: layerTypes.DATA, + groups: [], + metric: 'a', + + numberDisplay: 'hidden', + categoryDisplay: 'default', + legendDisplay: 'default', + }, + ], + }, + keptLayerIds: ['first'], + }) + ).toHaveLength(0); + }); + + it('mosaic type should be added only in case of 2 groups', () => { + expect( + suggestions({ + table: { + layerId: 'first', + isMultiRow: true, + columns: [ + { + columnId: 'a', + operation: { label: 'Top 5', dataType: 'string' as DataType, isBucketed: true }, + }, + { + columnId: 'b', + operation: { label: 'Top 6', dataType: 'string' as DataType, isBucketed: true }, + }, + { + columnId: 'c', + operation: { label: 'Count', dataType: 'number' as DataType, isBucketed: false }, + }, + ], + changeType: 'unchanged', + }, + state: { + shape: 'treemap', + layers: [ + { + layerId: 'first', + layerType: layerTypes.DATA, + groups: ['a', 'b'], + metric: 'c', + + numberDisplay: 'hidden', + categoryDisplay: 'inside', + legendDisplay: 'show', + percentDecimals: 0, + legendMaxLines: 1, + truncateLegend: true, + nestedLegend: true, + }, + ], + }, + keptLayerIds: ['first'], + }).filter(({ hide, state }) => !hide && state.shape === 'mosaic') + ).toMatchInlineSnapshot(` + Array [ + Object { + "hide": false, + "previewIcon": "bullseye", + "score": 0.6, + "state": Object { + "layers": Array [ + Object { + "categoryDisplay": "default", + "groups": Array [ + "a", + "b", + ], + "layerId": "first", + "layerType": "data", + "legendDisplay": "show", + "legendMaxLines": 1, + "metric": "c", + "nestedLegend": true, + "numberDisplay": "hidden", + "percentDecimals": 0, + "truncateLegend": true, + }, + ], + "palette": undefined, + "shape": "mosaic", + }, + "title": "As Mosaic", + }, + ] + `); + }); + + it('mosaic type should be added only in case of 2 groups (negative test)', () => { + const meta: Parameters[0] = { + table: { + layerId: 'first', + isMultiRow: true, + columns: [ + { + columnId: 'a', + operation: { label: 'Top 5', dataType: 'string' as DataType, isBucketed: true }, + }, + { + columnId: 'c', + operation: { label: 'Count', dataType: 'number' as DataType, isBucketed: false }, + }, + ], + changeType: 'unchanged', + }, + state: { + shape: 'pie', + layers: [ + { + layerId: 'first', + layerType: layerTypes.DATA, + groups: ['a', 'b'], + metric: 'c', + + numberDisplay: 'hidden', + categoryDisplay: 'inside', + legendDisplay: 'show', + percentDecimals: 0, + legendMaxLines: 1, + truncateLegend: true, + nestedLegend: true, + }, + ], + }, + keptLayerIds: ['first'], + }; + + // test with 1 group + expect( + suggestions(meta).filter(({ hide, state }) => !hide && state.shape === 'mosaic') + ).toMatchInlineSnapshot(`Array []`); + + meta.table.columns.push({ + columnId: 'b', + operation: { label: 'Top 6', dataType: 'string' as DataType, isBucketed: true }, + }); + + meta.table.columns.push({ + columnId: 'c', + operation: { label: 'Top 7', dataType: 'string' as DataType, isBucketed: true }, + }); + + // test with 3 groups + expect( + suggestions(meta).filter(({ hide, state }) => !hide && state.shape === 'mosaic') + ).toMatchInlineSnapshot(`Array []`); + }); + }); }); diff --git a/x-pack/plugins/lens/public/pie_visualization/suggestions.ts b/x-pack/plugins/lens/public/pie_visualization/suggestions.ts index 9078e18588a2f..30cd63752f420 100644 --- a/x-pack/plugins/lens/public/pie_visualization/suggestions.ts +++ b/x-pack/plugins/lens/public/pie_visualization/suggestions.ts @@ -7,17 +7,26 @@ import { partition } from 'lodash'; import { i18n } from '@kbn/i18n'; -import type { SuggestionRequest, VisualizationSuggestion } from '../types'; +import type { SuggestionRequest, TableSuggestionColumn, VisualizationSuggestion } from '../types'; import { layerTypes } from '../../common'; import type { PieVisualizationState } from '../../common/expressions'; -import { CHART_NAMES, MAX_PIE_BUCKETS, MAX_TREEMAP_BUCKETS } from './constants'; +import { CHART_NAMES, MAX_MOSAIC_BUCKETS, MAX_PIE_BUCKETS, MAX_TREEMAP_BUCKETS } from './constants'; +import { isPartitionShape, isTreemapOrMosaicShape } from './render_helpers'; + +function hasIntervalScale(columns: TableSuggestionColumn[]) { + return columns.some((col) => col.operation.scale === 'interval'); +} + +function shouldReject({ table, keptLayerIds, state }: SuggestionRequest) { + // Histograms are not good for pi. But we should not reject them on switching between partition charts. + const shouldRejectIntervals = + state?.shape && isPartitionShape(state.shape) ? false : hasIntervalScale(table.columns); -function shouldReject({ table, keptLayerIds }: SuggestionRequest) { return ( keptLayerIds.length > 1 || (keptLayerIds.length && table.layerId !== keptLayerIds[0]) || table.changeType === 'reorder' || - table.columns.some((col) => col.operation.scale === 'interval') // Histograms are not good for pie + shouldRejectIntervals ); } @@ -52,7 +61,7 @@ export function suggestions({ const results: Array> = []; - if (groups.length <= MAX_PIE_BUCKETS && subVisualizationId !== 'treemap') { + if (groups.length <= MAX_PIE_BUCKETS && !isTreemapOrMosaicShape(subVisualizationId!)) { let newShape: PieVisualizationState['shape'] = (subVisualizationId as PieVisualizationState['shape']) || 'donut'; if (groups.length !== 1 && !subVisualizationId) { @@ -65,7 +74,7 @@ export function suggestions({ values: { chartName: CHART_NAMES[newShape].label }, description: 'chartName is already translated', }), - score: state && state.shape !== 'treemap' ? 0.6 : 0.4, + score: state && !isTreemapOrMosaicShape(state.shape) ? 0.6 : 0.4, state: { shape: newShape, palette: mainPalette || state?.palette, @@ -92,7 +101,10 @@ export function suggestions({ }, previewIcon: 'bullseye', // dont show suggestions for same type - hide: table.changeType === 'reduced' || (state && state.shape !== 'treemap'), + hide: + table.changeType === 'reduced' || + hasIntervalScale(groups) || + (state && !isTreemapOrMosaicShape(state.shape)), }; results.push(baseSuggestion); @@ -153,7 +165,54 @@ export function suggestions({ }, previewIcon: 'bullseye', // hide treemap suggestions from bottom bar, but keep them for chart switcher - hide: table.changeType === 'reduced' || !state || (state && state.shape === 'treemap'), + hide: + table.changeType === 'reduced' || + !state || + hasIntervalScale(groups) || + (state && state.shape === 'treemap'), + }); + } + + if ( + groups.length <= MAX_MOSAIC_BUCKETS && + (!subVisualizationId || subVisualizationId === 'mosaic') + ) { + results.push({ + title: i18n.translate('xpack.lens.pie.mosaicSuggestionLabel', { + defaultMessage: 'As Mosaic', + }), + score: state?.shape === 'mosaic' ? 0.7 : 0.5, + state: { + shape: 'mosaic', + palette: mainPalette || state?.palette, + layers: [ + state?.layers[0] + ? { + ...state.layers[0], + layerId: table.layerId, + groups: groups.map((col) => col.columnId), + metric: metricColumnId, + categoryDisplay: 'default', + layerType: layerTypes.DATA, + } + : { + layerId: table.layerId, + groups: groups.map((col) => col.columnId), + metric: metricColumnId, + numberDisplay: 'percent', + categoryDisplay: 'default', + legendDisplay: 'default', + nestedLegend: false, + layerType: layerTypes.DATA, + }, + ], + }, + previewIcon: 'bullseye', + hide: + groups.length !== 2 || + table.changeType === 'reduced' || + hasIntervalScale(groups) || + (state && state.shape === 'mosaic'), }); } diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx index 685a8392dcfd3..23003a4ec3404 100644 --- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import type { Position } from '@elastic/charts'; import type { PaletteRegistry } from 'src/plugins/charts/public'; -import { DEFAULT_PERCENT_DECIMALS } from './constants'; +import { DEFAULT_PERCENT_DECIMALS, CHART_NAMES } from './constants'; import type { PieVisualizationState, SharedPieLayerState } from '../../common/expressions'; import { VisualizationDimensionEditorProps, VisualizationToolbarProps } from '../types'; import { ToolbarPopover, LegendSettingsPopover, useDebouncedValue } from '../shared_components'; @@ -47,48 +47,6 @@ const numberOptions: Array<{ }, ]; -const categoryOptions: Array<{ - value: SharedPieLayerState['categoryDisplay']; - inputDisplay: string; -}> = [ - { - value: 'default', - inputDisplay: i18n.translate('xpack.lens.pieChart.showCategoriesLabel', { - defaultMessage: 'Inside or outside', - }), - }, - { - value: 'inside', - inputDisplay: i18n.translate('xpack.lens.pieChart.fitInsideOnlyLabel', { - defaultMessage: 'Inside only', - }), - }, - { - value: 'hide', - inputDisplay: i18n.translate('xpack.lens.pieChart.categoriesInLegendLabel', { - defaultMessage: 'Hide labels', - }), - }, -]; - -const categoryOptionsTreemap: Array<{ - value: SharedPieLayerState['categoryDisplay']; - inputDisplay: string; -}> = [ - { - value: 'default', - inputDisplay: i18n.translate('xpack.lens.pieChart.showTreemapCategoriesLabel', { - defaultMessage: 'Show labels', - }), - }, - { - value: 'hide', - inputDisplay: i18n.translate('xpack.lens.pieChart.categoriesInLegendLabel', { - defaultMessage: 'Hide labels', - }), - }, -]; - const legendOptions: Array<{ value: SharedPieLayerState['legendDisplay']; label: string; @@ -133,25 +91,27 @@ export function PieToolbar(props: VisualizationToolbarProps - - { - setState({ - ...state, - layers: [{ ...layer, categoryDisplay: option }], - }); - }} - /> - + {state.shape && CHART_NAMES[state.shape].categoryOptions.length ? ( + + { + setState({ + ...state, + layers: [{ ...layer, categoryDisplay: option }], + }); + }} + /> + + ) : null} op.isBucketed; const numberMetricOperations = (op: OperationMetadata) => !op.isBucketed && op.dataType === 'number'; +const applyPaletteToColumnConfig = ( + columns: AccessorConfig[], + { shape, palette }: PieVisualizationState, + paletteService: PaletteRegistry +) => { + const colorPickerIndex = shape === 'mosaic' ? columns.length - 1 : 0; + + if (colorPickerIndex >= 0) { + columns[colorPickerIndex] = { + columnId: columns[colorPickerIndex].columnId, + triggerIcon: 'colorBy', + palette: paletteService + .get(palette?.name || 'default') + .getCategoricalColors(10, palette?.params), + }; + } +}; + export const getPieVisualization = ({ paletteService, }: { @@ -61,6 +84,13 @@ export const getPieVisualization = ({ label: CHART_NAMES.treemap.label, groupLabel: CHART_NAMES.treemap.groupLabel, }, + { + id: 'mosaic', + icon: CHART_NAMES.mosaic.icon, + label: CHART_NAMES.mosaic.label, + showExperimentalBadge: true, + groupLabel: CHART_NAMES.mosaic.groupLabel, + }, ], getVisualizationTypeId(state) { @@ -79,13 +109,7 @@ export const getPieVisualization = ({ }, getDescription(state) { - if (state.shape === 'treemap') { - return CHART_NAMES.treemap; - } - if (state.shape === 'donut') { - return CHART_NAMES.donut; - } - return CHART_NAMES.pie; + return CHART_NAMES[state.shape] ?? CHART_NAMES.pie; }, switchVisualizationType: (visualizationTypeId, state) => ({ @@ -122,76 +146,58 @@ export const getPieVisualization = ({ const sortedColumns: AccessorConfig[] = Array.from( new Set(originalOrder.concat(layer.groups)) ).map((accessor) => ({ columnId: accessor })); - if (sortedColumns.length > 0) { - sortedColumns[0] = { - columnId: sortedColumns[0].columnId, - triggerIcon: 'colorBy', - palette: paletteService - .get(state.palette?.name || 'default') - .getCategoricalColors(10, state.palette?.params), - }; + + if (sortedColumns.length) { + applyPaletteToColumnConfig(sortedColumns, state, paletteService); } - if (state.shape === 'treemap') { - return { - groups: [ - { - groupId: 'groups', + const getSliceByGroup = (): VisualizationDimensionGroupConfig => { + const baseProps = { + required: true, + groupId: 'groups', + accessors: sortedColumns, + enableDimensionEditor: true, + filterOperations: bucketedOperations, + }; + + switch (state.shape) { + case 'mosaic': + case 'treemap': + return { + ...baseProps, groupLabel: i18n.translate('xpack.lens.pie.treemapGroupLabel', { defaultMessage: 'Group by', }), - layerId, - accessors: sortedColumns, supportsMoreColumns: sortedColumns.length < MAX_TREEMAP_BUCKETS, - filterOperations: bucketedOperations, - required: true, dataTestSubj: 'lnsPie_groupByDimensionPanel', - enableDimensionEditor: true, - }, - { - groupId: 'metric', - groupLabel: i18n.translate('xpack.lens.pie.groupsizeLabel', { - defaultMessage: 'Size by', + requiredMinDimensionCount: state.shape === 'mosaic' ? 2 : undefined, + }; + default: + return { + ...baseProps, + groupLabel: i18n.translate('xpack.lens.pie.sliceGroupLabel', { + defaultMessage: 'Slice by', }), - layerId, - accessors: layer.metric ? [{ columnId: layer.metric }] : [], - supportsMoreColumns: !layer.metric, - filterOperations: numberMetricOperations, - required: true, - dataTestSubj: 'lnsPie_sizeByDimensionPanel', - }, - ], - }; - } + supportsMoreColumns: sortedColumns.length < MAX_PIE_BUCKETS, + dataTestSubj: 'lnsPie_sliceByDimensionPanel', + }; + } + }; + + const getMetricGroup = (): VisualizationDimensionGroupConfig => ({ + groupId: 'metric', + groupLabel: i18n.translate('xpack.lens.pie.groupsizeLabel', { + defaultMessage: 'Size by', + }), + accessors: layer.metric ? [{ columnId: layer.metric }] : [], + supportsMoreColumns: !layer.metric, + filterOperations: numberMetricOperations, + required: true, + dataTestSubj: 'lnsPie_sizeByDimensionPanel', + }); return { - groups: [ - { - groupId: 'groups', - groupLabel: i18n.translate('xpack.lens.pie.sliceGroupLabel', { - defaultMessage: 'Slice by', - }), - layerId, - accessors: sortedColumns, - supportsMoreColumns: sortedColumns.length < MAX_PIE_BUCKETS, - filterOperations: bucketedOperations, - required: true, - dataTestSubj: 'lnsPie_sliceByDimensionPanel', - enableDimensionEditor: true, - }, - { - groupId: 'metric', - groupLabel: i18n.translate('xpack.lens.pie.groupsizeLabel', { - defaultMessage: 'Size by', - }), - layerId, - accessors: layer.metric ? [{ columnId: layer.metric }] : [], - supportsMoreColumns: !layer.metric, - filterOperations: numberMetricOperations, - required: true, - dataTestSubj: 'lnsPie_sizeByDimensionPanel', - }, - ], + groups: [getSliceByGroup(), getMetricGroup()], }; }, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index a9a9539064659..ab6f1d8d55082 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -470,6 +470,7 @@ export type VisualizationDimensionGroupConfig = SharedDimensionProps & { supportsMoreColumns: boolean; /** If required, a warning will appear if accessors are empty */ required?: boolean; + requiredMinDimensionCount?: number; dataTestSubj?: string; /** diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a27ec93d02c98..a681581edbf82 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -14116,7 +14116,6 @@ "xpack.lens.editorFrame.noColorIndicatorLabel": "このディメンションには個別の色がありません", "xpack.lens.editorFrame.paletteColorIndicatorLabel": "このディメンションはパレットを使用しています", "xpack.lens.editorFrame.previewErrorLabel": "レンダリングのプレビューに失敗しました", - "xpack.lens.editorFrame.requiredDimensionWarningLabel": "必要な次元", "xpack.lens.editorFrame.suggestionPanelTitle": "提案", "xpack.lens.editorFrame.workspaceLabel": "ワークスペース", "xpack.lens.embeddable.failure": "ビジュアライゼーションを表示できませんでした", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e63a873cac3e4..ab953dcdb49a2 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -14304,7 +14304,6 @@ "xpack.lens.editorFrame.noColorIndicatorLabel": "此维度没有单独的颜色", "xpack.lens.editorFrame.paletteColorIndicatorLabel": "此维度正在使用调色板", "xpack.lens.editorFrame.previewErrorLabel": "预览呈现失败", - "xpack.lens.editorFrame.requiredDimensionWarningLabel": "所需尺寸", "xpack.lens.editorFrame.suggestionPanelTitle": "建议", "xpack.lens.editorFrame.workspaceLabel": "工作区", "xpack.lens.embeddable.failure": "无法显示可视化", From 38e0cb94a4dccaad1f5a62ed8eed10a7485f648e Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Mon, 22 Nov 2021 08:45:53 -0500 Subject: [PATCH 10/11] [APM] Fixes navigation bugs in APM tutorial/onboarding (#119249) --- .../components/tutorial/instruction_set.js | 5 +- .../services/tutorials/lib/tutorial_schema.ts | 1 + .../tutorial_fleet_instructions/index.tsx | 30 +++- .../plugins/apm/server/routes/fleet/route.ts | 13 +- .../apm/server/tutorial/envs/elastic_cloud.ts | 17 ++- .../apm/server/tutorial/envs/on_prem.ts | 124 +--------------- .../on_prem_apm_server_instruction_set.ts | 136 ++++++++++++++++++ x-pack/plugins/apm/server/tutorial/index.ts | 6 +- 8 files changed, 197 insertions(+), 135 deletions(-) create mode 100644 x-pack/plugins/apm/server/tutorial/envs/on_prem_apm_server_instruction_set.ts diff --git a/src/plugins/home/public/application/components/tutorial/instruction_set.js b/src/plugins/home/public/application/components/tutorial/instruction_set.js index 822c60cdc31ba..e434f2483fd19 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction_set.js +++ b/src/plugins/home/public/application/components/tutorial/instruction_set.js @@ -37,6 +37,7 @@ class InstructionSetUi extends React.Component { return { id: variant.id, name: getDisplayText(variant.id), + initialSelected: variant.initialSelected, }; }); @@ -45,7 +46,8 @@ class InstructionSetUi extends React.Component { }; if (this.tabs.length > 0) { - this.state.selectedTabId = this.tabs[0].id; + this.state.selectedTabId = + this.tabs.find(({ initialSelected }) => initialSelected)?.id ?? this.tabs[0].id; } } @@ -298,6 +300,7 @@ const instructionShape = PropTypes.shape({ const instructionVariantShape = PropTypes.shape({ id: PropTypes.string.isRequired, instructions: PropTypes.arrayOf(instructionShape).isRequired, + initialSelected: PropTypes.bool, }); const statusCheckConfigShape = PropTypes.shape({ diff --git a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts index 114afa644c0c2..f05c0af01cb1e 100644 --- a/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts +++ b/src/plugins/home/server/services/tutorials/lib/tutorial_schema.ts @@ -63,6 +63,7 @@ export type Instruction = TypeOf; const instructionVariantSchema = schema.object({ id: schema.string(), instructions: schema.arrayOf(instructionSchema), + initialSelected: schema.maybe(schema.boolean()), }); export type InstructionVariant = TypeOf; diff --git a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx index a922a10d6d6fc..69f3c4bbbc407 100644 --- a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx +++ b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx @@ -34,7 +34,7 @@ const CentralizedContainer = styled.div` align-items: center; `; -type APIResponseType = APIReturnType<'GET /internal/apm/fleet/has_data'>; +type APIResponseType = APIReturnType<'GET /internal/apm/fleet/migration_check'>; function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { const [data, setData] = useState(); @@ -44,7 +44,7 @@ function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { async function fetchData() { setIsLoading(true); try { - const response = await http.get('/internal/apm/fleet/has_data'); + const response = await http.get('/internal/apm/fleet/migration_check'); setData(response as APIResponseType); } catch (e) { setIsLoading(false); @@ -55,6 +55,22 @@ function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { fetchData(); }, [http]); + const hasApmIntegrations = !!data?.has_apm_integrations; + const cloudApmMigrationEnabled = !!data?.cloud_apm_migration_enabled; + const hasCloudAgentPolicy = !!data?.has_cloud_agent_policy; + const cloudApmPackagePolicy = data?.cloud_apm_package_policy; + const hasCloudApmPackagePolicy = !!cloudApmPackagePolicy; + const hasRequiredRole = !!data?.has_required_role; + const shouldLinkToMigration = + cloudApmMigrationEnabled && + hasCloudAgentPolicy && + !hasCloudApmPackagePolicy && + hasRequiredRole; + + const apmIntegrationHref = shouldLinkToMigration + ? `${basePath}/app/apm/settings/schema` + : `${basePath}/app/integrations/detail/apm-${SUPPORTED_APM_PACKAGE_VERSION}/overview`; + if (isLoading) { return ( @@ -64,9 +80,13 @@ function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { } // When APM integration is enable in Fleet - if (data?.hasData) { + if (hasApmIntegrations) { return ( - + {i18n.translate( 'xpack.apm.tutorial.apmServer.fleet.manageApmIntegration.button', { @@ -99,7 +119,7 @@ function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { {i18n.translate( 'xpack.apm.tutorial.apmServer.fleet.apmIntegration.button', diff --git a/x-pack/plugins/apm/server/routes/fleet/route.ts b/x-pack/plugins/apm/server/routes/fleet/route.ts index e9e7f2254bcfe..b64d1764c8465 100644 --- a/x-pack/plugins/apm/server/routes/fleet/route.ts +++ b/x-pack/plugins/apm/server/routes/fleet/route.ts @@ -128,14 +128,16 @@ const getMigrationCheckRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/fleet/migration_check', options: { tags: ['access:apm'] }, handler: async (resources) => { - const { plugins, context, config, request } = resources; + const { core, plugins, context, config, request } = resources; const cloudApmMigrationEnabled = config.agent.migrations.enabled; if (!plugins.fleet || !plugins.security) { throw Boom.internal(FLEET_SECURITY_REQUIRED_MESSAGE); } const savedObjectsClient = context.core.savedObjects.client; - const fleetPluginStart = await plugins.fleet.start(); - const securityPluginStart = await plugins.security.start(); + const [fleetPluginStart, securityPluginStart] = await Promise.all([ + plugins.fleet.start(), + plugins.security.start(), + ]); const hasRequiredRole = isSuperuser({ securityPluginStart, request }); const cloudAgentPolicy = hasRequiredRole ? await getCloudAgentPolicy({ @@ -144,12 +146,17 @@ const getMigrationCheckRoute = createApmServerRoute({ }) : undefined; const apmPackagePolicy = getApmPackagePolicy(cloudAgentPolicy); + const packagePolicies = await getApmPackgePolicies({ + core, + fleetPluginStart, + }); return { has_cloud_agent_policy: !!cloudAgentPolicy, has_cloud_apm_package_policy: !!apmPackagePolicy, cloud_apm_migration_enabled: cloudApmMigrationEnabled, has_required_role: hasRequiredRole, cloud_apm_package_policy: apmPackagePolicy, + has_apm_integrations: packagePolicies.total > 0, }; }, }); diff --git a/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts index c62e42f222194..654c0e675a053 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts @@ -25,10 +25,18 @@ import { createPhpAgentInstructions, } from '../../../common/tutorial/instructions/apm_agent_instructions'; import { CloudSetup } from '../../../../cloud/server'; +import { APMConfig } from '../..'; +import { getOnPremApmServerInstructionSet } from './on_prem_apm_server_instruction_set'; -export function createElasticCloudInstructions( - cloudSetup?: CloudSetup -): TutorialSchema['elasticCloud'] { +export function createElasticCloudInstructions({ + cloudSetup, + apmConfig, + isFleetPluginEnabled, +}: { + cloudSetup?: CloudSetup; + apmConfig: APMConfig; + isFleetPluginEnabled: boolean; +}): TutorialSchema['elasticCloud'] { const apmServerUrl = cloudSetup?.apm.url; const instructionSets = []; @@ -36,6 +44,9 @@ export function createElasticCloudInstructions( instructionSets.push(getApmServerInstructionSet(cloudSetup)); } + instructionSets.push( + getOnPremApmServerInstructionSet({ apmConfig, isFleetPluginEnabled }) + ); instructionSets.push(getApmAgentInstructionSet(cloudSetup)); return { diff --git a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts index 8051ef2a72b6a..18e30fe07808a 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts @@ -23,15 +23,7 @@ import { createRackAgentInstructions, createRailsAgentInstructions, } from '../../../common/tutorial/instructions/apm_agent_instructions'; -import { - createDownloadServerDeb, - createDownloadServerOsx, - createDownloadServerRpm, - createEditConfig, - createStartServerUnix, - createStartServerUnixSysv, - createWindowsServerInstructions, -} from '../../../common/tutorial/instructions/apm_server_instructions'; +import { getOnPremApmServerInstructionSet } from './on_prem_apm_server_instruction_set'; export function onPremInstructions({ apmConfig, @@ -40,121 +32,9 @@ export function onPremInstructions({ apmConfig: APMConfig; isFleetPluginEnabled: boolean; }): InstructionsSchema { - const EDIT_CONFIG = createEditConfig(); - const START_SERVER_UNIX = createStartServerUnix(); - const START_SERVER_UNIX_SYSV = createStartServerUnixSysv(); - return { instructionSets: [ - { - title: i18n.translate('xpack.apm.tutorial.apmServer.title', { - defaultMessage: 'APM Server', - }), - callOut: { - title: i18n.translate('xpack.apm.tutorial.apmServer.callOut.title', { - defaultMessage: 'Important: Updating to 7.0 or higher', - }), - message: i18n.translate( - 'xpack.apm.tutorial.apmServer.callOut.message', - { - defaultMessage: `Please make sure your APM Server is updated to 7.0 or higher. \ - You can also migrate your 6.x data with the migration assistant found in Kibana's management section.`, - } - ), - iconType: 'alert', - }, - instructionVariants: [ - { - id: INSTRUCTION_VARIANT.DEB, - instructions: [ - createDownloadServerDeb(), - EDIT_CONFIG, - START_SERVER_UNIX_SYSV, - ], - }, - { - id: INSTRUCTION_VARIANT.RPM, - instructions: [ - createDownloadServerRpm(), - EDIT_CONFIG, - START_SERVER_UNIX_SYSV, - ], - }, - { - id: INSTRUCTION_VARIANT.OSX, - instructions: [ - createDownloadServerOsx(), - EDIT_CONFIG, - START_SERVER_UNIX, - ], - }, - { - id: INSTRUCTION_VARIANT.WINDOWS, - instructions: createWindowsServerInstructions(), - }, - // hides fleet section when plugin is disabled - ...(isFleetPluginEnabled - ? [ - { - id: INSTRUCTION_VARIANT.FLEET, - instructions: [ - { - title: i18n.translate('xpack.apm.tutorial.fleet.title', { - defaultMessage: 'Fleet', - }), - customComponentName: 'TutorialFleetInstructions', - }, - ], - }, - ] - : []), - ], - statusCheck: { - title: i18n.translate( - 'xpack.apm.tutorial.apmServer.statusCheck.title', - { - defaultMessage: 'APM Server status', - } - ), - text: i18n.translate( - 'xpack.apm.tutorial.apmServer.statusCheck.text', - { - defaultMessage: - 'Make sure APM Server is running before you start implementing the APM agents.', - } - ), - btnLabel: i18n.translate( - 'xpack.apm.tutorial.apmServer.statusCheck.btnLabel', - { - defaultMessage: 'Check APM Server status', - } - ), - success: i18n.translate( - 'xpack.apm.tutorial.apmServer.statusCheck.successMessage', - { - defaultMessage: 'You have correctly setup APM Server', - } - ), - error: i18n.translate( - 'xpack.apm.tutorial.apmServer.statusCheck.errorMessage', - { - defaultMessage: - 'No APM Server detected. Please make sure it is running and you have updated to 7.0 or higher.', - } - ), - esHitsCheck: { - index: apmConfig.indices.onboarding, - query: { - bool: { - filter: [ - { term: { 'processor.event': 'onboarding' } }, - { range: { 'observer.version_major': { gte: 7 } } }, - ], - }, - }, - }, - }, - }, + getOnPremApmServerInstructionSet({ apmConfig, isFleetPluginEnabled }), { title: i18n.translate('xpack.apm.tutorial.apmAgents.title', { defaultMessage: 'APM Agents', diff --git a/x-pack/plugins/apm/server/tutorial/envs/on_prem_apm_server_instruction_set.ts b/x-pack/plugins/apm/server/tutorial/envs/on_prem_apm_server_instruction_set.ts new file mode 100644 index 0000000000000..b9c491082f787 --- /dev/null +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem_apm_server_instruction_set.ts @@ -0,0 +1,136 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { APMConfig } from '../..'; +import { + InstructionsSchema, + INSTRUCTION_VARIANT, +} from '../../../../../../src/plugins/home/server'; +import { + createDownloadServerDeb, + createDownloadServerOsx, + createDownloadServerRpm, + createEditConfig, + createStartServerUnix, + createStartServerUnixSysv, + createWindowsServerInstructions, +} from '../../../common/tutorial/instructions/apm_server_instructions'; + +const EDIT_CONFIG = createEditConfig(); +const START_SERVER_UNIX = createStartServerUnix(); +const START_SERVER_UNIX_SYSV = createStartServerUnixSysv(); + +export function getOnPremApmServerInstructionSet({ + apmConfig, + isFleetPluginEnabled, +}: { + apmConfig: APMConfig; + isFleetPluginEnabled: boolean; +}): InstructionsSchema['instructionSets'][0] { + return { + title: i18n.translate('xpack.apm.tutorial.apmServer.title', { + defaultMessage: 'APM Server', + }), + callOut: { + title: i18n.translate('xpack.apm.tutorial.apmServer.callOut.title', { + defaultMessage: 'Important: Updating to 7.0 or higher', + }), + message: i18n.translate('xpack.apm.tutorial.apmServer.callOut.message', { + defaultMessage: `Please make sure your APM Server is updated to 7.0 or higher. \ + You can also migrate your 6.x data with the migration assistant found in Kibana's management section.`, + }), + iconType: 'alert', + }, + instructionVariants: [ + { + id: INSTRUCTION_VARIANT.DEB, + instructions: [ + createDownloadServerDeb(), + EDIT_CONFIG, + START_SERVER_UNIX_SYSV, + ], + }, + { + id: INSTRUCTION_VARIANT.RPM, + instructions: [ + createDownloadServerRpm(), + EDIT_CONFIG, + START_SERVER_UNIX_SYSV, + ], + }, + { + id: INSTRUCTION_VARIANT.OSX, + instructions: [ + createDownloadServerOsx(), + EDIT_CONFIG, + START_SERVER_UNIX, + ], + }, + { + id: INSTRUCTION_VARIANT.WINDOWS, + instructions: createWindowsServerInstructions(), + }, + // hides fleet section when plugin is disabled + ...(isFleetPluginEnabled + ? [ + { + id: INSTRUCTION_VARIANT.FLEET, + instructions: [ + { + title: i18n.translate('xpack.apm.tutorial.fleet.title', { + defaultMessage: 'Fleet', + }), + customComponentName: 'TutorialFleetInstructions', + }, + ], + initialSelected: true, + }, + ] + : []), + ], + statusCheck: { + title: i18n.translate('xpack.apm.tutorial.apmServer.statusCheck.title', { + defaultMessage: 'APM Server status', + }), + text: i18n.translate('xpack.apm.tutorial.apmServer.statusCheck.text', { + defaultMessage: + 'Make sure APM Server is running before you start implementing the APM agents.', + }), + btnLabel: i18n.translate( + 'xpack.apm.tutorial.apmServer.statusCheck.btnLabel', + { + defaultMessage: 'Check APM Server status', + } + ), + success: i18n.translate( + 'xpack.apm.tutorial.apmServer.statusCheck.successMessage', + { + defaultMessage: 'You have correctly setup APM Server', + } + ), + error: i18n.translate( + 'xpack.apm.tutorial.apmServer.statusCheck.errorMessage', + { + defaultMessage: + 'No APM Server detected. Please make sure it is running and you have updated to 7.0 or higher.', + } + ), + esHitsCheck: { + index: apmConfig.indices.onboarding, + query: { + bool: { + filter: [ + { term: { 'processor.event': 'onboarding' } }, + { range: { 'observer.version_major': { gte: 7 } } }, + ], + }, + }, + }, + }, + }; +} diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts index 5d3ff8636df4d..c9fc7eb6467d4 100644 --- a/x-pack/plugins/apm/server/tutorial/index.ts +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -107,7 +107,11 @@ It allows you to monitor the performance of thousands of applications in real ti artifacts, customStatusCheckName: 'apm_fleet_server_status_check', onPrem: onPremInstructions({ apmConfig, isFleetPluginEnabled }), - elasticCloud: createElasticCloudInstructions(cloud), + elasticCloud: createElasticCloudInstructions({ + apmConfig, + isFleetPluginEnabled, + cloudSetup: cloud, + }), previewImagePath: '/plugins/apm/assets/apm.png', savedObjects, savedObjectsInstallMsg: i18n.translate( From 562384f6929f8fd52bb51f5ba5436bbddd2a2383 Mon Sep 17 00:00:00 2001 From: Andrew Tate Date: Mon, 22 Nov 2021 08:06:14 -0600 Subject: [PATCH 11/11] [Vega] timelion & vega apply dataview from first filter (#119209) --- .../public/helpers/timelion_request_handler.ts | 13 +++++++++++-- .../vega/public/vega_request_handler.ts | 17 ++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts b/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts index e9a076b4dc832..69a818b4ae16e 100644 --- a/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts +++ b/src/plugins/vis_types/timelion/public/helpers/timelion_request_handler.ts @@ -8,11 +8,12 @@ import { i18n } from '@kbn/i18n'; import type { KibanaExecutionContext } from 'kibana/public'; +import { DataView } from 'src/plugins/data/common'; import { KibanaContext, TimeRange, Filter, esQuery, Query } from '../../../../data/public'; import { TimelionVisDependencies } from '../plugin'; import { getTimezone } from './get_timezone'; import { TimelionVisParams } from '../timelion_vis_fn'; -import { getDataSearch } from '../helpers/plugin_services'; +import { getDataSearch, getIndexPatterns } from '../helpers/plugin_services'; import { VisSeries } from '../../common/vis_data'; interface Stats { @@ -81,6 +82,14 @@ export function getTimelionRequestHandler({ ); } + let dataView: DataView | undefined; + const firstFilterIndex = filters[0]?.meta.index; + if (firstFilterIndex) { + dataView = await getIndexPatterns() + .get(firstFilterIndex) + .catch(() => undefined); + } + const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); // parse the time range client side to make sure it behaves like other charts @@ -100,7 +109,7 @@ export function getTimelionRequestHandler({ sheet: [expression], extended: { es: { - filter: esQuery.buildEsQuery(undefined, query, filters, esQueryConfigs), + filter: esQuery.buildEsQuery(dataView, query, filters, esQueryConfigs), }, }, time: { diff --git a/src/plugins/vis_types/vega/public/vega_request_handler.ts b/src/plugins/vis_types/vega/public/vega_request_handler.ts index 2ae7169c2f732..78552ea2a170a 100644 --- a/src/plugins/vis_types/vega/public/vega_request_handler.ts +++ b/src/plugins/vis_types/vega/public/vega_request_handler.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ import type { KibanaExecutionContext } from 'src/core/public'; +import { DataView } from 'src/plugins/data/common'; import { Filter, esQuery, TimeRange, Query } from '../../../data/public'; import { SearchAPI } from './data_model/search_api'; @@ -18,7 +19,7 @@ import { VegaInspectorAdapters } from './vega_inspector'; interface VegaRequestHandlerParams { query: Query; - filters: Filter; + filters: Filter[]; timeRange: TimeRange; visParams: VisParams; searchSessionId?: string; @@ -46,14 +47,14 @@ export function createVegaRequestHandler( searchSessionId, executionContext, }: VegaRequestHandlerParams) { - if (!searchAPI) { - const { search, indexPatterns } = getData(); + const { dataViews, search } = getData(); + if (!searchAPI) { searchAPI = new SearchAPI( { uiSettings, search, - indexPatterns, + indexPatterns: dataViews, injectedMetadata: getInjectedMetadata(), }, context.abortSignal, @@ -65,8 +66,14 @@ export function createVegaRequestHandler( timeCache.setTimeRange(timeRange); + let dataView: DataView; + const firstFilterIndex = filters[0]?.meta.index; + if (firstFilterIndex) { + dataView = await dataViews.get(firstFilterIndex).catch(() => undefined); + } + const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); - const filtersDsl = esQuery.buildEsQuery(undefined, query, filters, esQueryConfigs); + const filtersDsl = esQuery.buildEsQuery(dataView, query, filters, esQueryConfigs); const { VegaParser } = await import('./data_model/vega_parser'); const vp = new VegaParser(visParams.spec, searchAPI, timeCache, filtersDsl, getServiceSettings);