diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/cluster_settings_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/cluster_settings_deprecation_flyout.test.ts deleted file mode 100644 index d74b9e2cfef07..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/cluster_settings_deprecation_flyout.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 '@testing-library/jest-dom'; -import { fireEvent, screen, waitFor, within } from '@testing-library/react'; - -import { setupEnvironment } from '../helpers/setup_environment'; -import { setupElasticsearchPage } from './es_deprecations.helpers'; -import { esDeprecationsMockResponse } from './mocked_responses'; -import { MOCK_REINDEX_DEPRECATION } from './mocked_responses'; - -describe('Cluster settings deprecation flyout', () => { - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - let httpSetup: ReturnType['httpSetup']; - const clusterSettingDeprecation = esDeprecationsMockResponse.migrationsDeprecations[4]; - - const openFlyout = async () => { - fireEvent.click(screen.getAllByTestId('deprecation-clusterSetting')[0]); - return await screen.findByTestId('clusterSettingsDetails'); - }; - - beforeEach(() => { - const mockEnvironment = setupEnvironment(); - httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; - httpSetup = mockEnvironment.httpSetup; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse); - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'foo', - reindexName: 'reindexed-foo', - aliases: [], - }, - }); - }); - - test('renders a flyout with deprecation details', async () => { - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - expect(flyout).toBeInTheDocument(); - expect(within(flyout).getByTestId('flyoutTitle')).toHaveTextContent( - clusterSettingDeprecation.message - ); - expect( - (within(flyout).getByTestId('documentationLink') as HTMLAnchorElement).getAttribute('href') - ).toBe(clusterSettingDeprecation.url); - expect(within(flyout).getByTestId('removeClusterSettingsPrompt')).toBeInTheDocument(); - }); - - it('removes deprecated cluster settings', async () => { - httpRequestsMockHelpers.setClusterSettingsResponse({ - acknowledged: true, - persistent: {}, - transietn: {}, - }); - - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - expect(within(flyout).getByTestId('warningDeprecationBadge')).toBeInTheDocument(); - - fireEvent.click(within(flyout).getByTestId('deleteClusterSettingsButton')); - - expect(httpSetup.post).toHaveBeenLastCalledWith( - `/api/upgrade_assistant/cluster_settings`, - expect.anything() - ); - - // Verify the "Resolution" column of the table is updated - await waitFor(() => { - expect(screen.getAllByTestId('clusterSettingsResolutionStatusCell')[0]).toHaveTextContent( - 'Deprecated settings removed' - ); - }); - - await waitFor(() => { - expect(screen.queryByTestId('clusterSettingsDetails')).toBeNull(); - }); - - // Reopen the flyout - const reopenedFlyout = await openFlyout(); - - // Verify prompt to remove setting no longer displays - expect(within(reopenedFlyout).queryByTestId('removeClusterSettingsPrompt')).toBeNull(); - // Verify the action button no longer displays - expect(within(reopenedFlyout).queryByTestId('deleteClusterSettingsButton')).toBeNull(); - // Verify the badge got marked as resolved - expect(within(reopenedFlyout).getByTestId('resolvedDeprecationBadge')).toBeInTheDocument(); - }); - - it('handles failure', async () => { - const error = { - statusCode: 500, - error: 'Remove cluster settings error', - message: 'Remove cluster settings error', - }; - - httpRequestsMockHelpers.setClusterSettingsResponse(undefined, error); - - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - fireEvent.click(within(flyout).getByTestId('deleteClusterSettingsButton')); - - expect(httpSetup.post).toHaveBeenLastCalledWith( - `/api/upgrade_assistant/cluster_settings`, - expect.anything() - ); - - // Verify the "Resolution" column of the table is updated - await waitFor(() => { - expect(screen.getAllByTestId('clusterSettingsResolutionStatusCell')[0]).toHaveTextContent( - 'Settings removal failed' - ); - }); - - await waitFor(() => { - expect(screen.queryByTestId('clusterSettingsDetails')).toBeNull(); - }); - - // Reopen the flyout - const reopenedFlyout = await openFlyout(); - - // Verify the flyout shows an error message - expect(within(reopenedFlyout).getByTestId('deleteClusterSettingsError')).toHaveTextContent( - 'Error deleting cluster settings' - ); - // Verify the remove settings button text changes - expect(within(reopenedFlyout).getByTestId('deleteClusterSettingsButton')).toHaveTextContent( - 'Retry removing deprecated settings' - ); - }); -}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/data_streams_deprecation.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/data_streams_deprecation.test.ts deleted file mode 100644 index f09a3c3ac8b65..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/data_streams_deprecation.test.ts +++ /dev/null @@ -1,599 +0,0 @@ -/* - * 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 moment from 'moment'; - -import numeral from '@elastic/numeral'; -import '@testing-library/jest-dom'; -import { fireEvent, screen, waitFor, within } from '@testing-library/react'; - -import { setupEnvironment } from '../helpers/setup_environment'; -import { setupElasticsearchPage } from './es_deprecations.helpers'; -import { - esDeprecationsMockResponse, - MOCK_DS_DEPRECATION, - MOCK_REINDEX_DEPRECATION, - MOCK_DS_DEPRECATION_REINDEX, - MOCK_DS_DEPRECATION_READ_ONLY, -} from './mocked_responses'; -import { DataStreamMigrationStatus } from '../../../common/data_stream_types'; - -const DATE_FORMAT = 'dddd, MMMM Do YYYY, h:mm:ss a'; -const FILE_SIZE_DISPLAY_FORMAT = '0,0.[0] b'; - -const defaultMetaResponse = { - dataStreamName: MOCK_DS_DEPRECATION.index!, - documentationUrl: MOCK_DS_DEPRECATION.url, - lastIndexRequiringUpgradeCreationDate: 1483228800000, - allIndices: ['ds_index'], - allIndicesCount: 1, - indicesRequiringUpgradeCount: 1, - indicesRequiringUpgrade: ['ds_index'], - indicesRequiringUpgradeDocsSize: 51200, - indicesRequiringUpgradeDocsCount: 12, -}; - -const getMetaResponseForDataStream = (dataStreamName: string, documentationUrl: string) => ({ - ...defaultMetaResponse, - dataStreamName, - documentationUrl, -}); - -const defaultMigrationResponse = { - hasRequiredPrivileges: true, - migrationOp: { status: DataStreamMigrationStatus.notStarted }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'reindex', - }, - { - warningType: 'incompatibleDataStream', - resolutionType: 'readonly', - }, - ], -}; - -describe('Data streams deprecation', () => { - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - let httpSetup: ReturnType['httpSetup']; - beforeEach(() => { - const mockEnvironment = setupEnvironment(); - httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; - httpSetup = mockEnvironment.httpSetup; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse); - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION.index!, - defaultMigrationResponse - ); - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_REINDEX.index!, - defaultMigrationResponse - ); - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - defaultMigrationResponse - ); - - httpRequestsMockHelpers.setDataStreamMetadataResponse( - MOCK_DS_DEPRECATION.index!, - getMetaResponseForDataStream(MOCK_DS_DEPRECATION.index!, MOCK_DS_DEPRECATION.url) - ); - httpRequestsMockHelpers.setDataStreamMetadataResponse( - MOCK_DS_DEPRECATION_REINDEX.index!, - getMetaResponseForDataStream( - MOCK_DS_DEPRECATION_REINDEX.index!, - MOCK_DS_DEPRECATION_REINDEX.url - ) - ); - httpRequestsMockHelpers.setDataStreamMetadataResponse( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - getMetaResponseForDataStream( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - MOCK_DS_DEPRECATION_READ_ONLY.url - ) - ); - - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'foo', - reindexName: 'reindexed-foo', - aliases: [], - }, - }); - - httpRequestsMockHelpers.setLoadNodeDiskSpaceResponse([]); - }); - - const setupPage = async () => { - await setupElasticsearchPage(httpSetup); - }; - - const openReindexFlyoutAt = async (index: number) => { - fireEvent.click(screen.getAllByTestId('deprecation-dataStream-reindex')[index]); - await waitFor(() => { - const flyout = - screen.queryByTestId('reindexDataStreamDetails') ?? - screen.queryByTestId('dataStreamMigrationChecklistFlyout'); - - expect(flyout).not.toBeNull(); - }); - - return (screen.queryByTestId('reindexDataStreamDetails') ?? - screen.queryByTestId('dataStreamMigrationChecklistFlyout'))!; - }; - - const openReadOnlyModalAt = async (index: number) => { - fireEvent.click(screen.getAllByTestId('deprecation-dataStream-readonly')[index]); - await waitFor(() => { - const modal = - screen.queryByTestId('updateIndexModal') ?? - screen.queryByTestId('dataStreamMigrationChecklistModal'); - - expect(modal).not.toBeNull(); - }); - - return (screen.queryByTestId('updateIndexModal') ?? - screen.queryByTestId('dataStreamMigrationChecklistModal'))!; - }; - - const checkMigrationWarningCheckbox = async () => { - const checkbox = screen.getByTestId('migrationWarningCheckbox'); - fireEvent.click(within(checkbox).getByRole('checkbox')); - - await waitFor(() => { - expect(screen.getByTestId('startActionButton')).toBeEnabled(); - }); - }; - - describe('reindexing flyout', () => { - beforeEach(async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_REINDEX.index!, - defaultMigrationResponse - ); - httpRequestsMockHelpers.setDataStreamMetadataResponse( - MOCK_DS_DEPRECATION_REINDEX.index!, - getMetaResponseForDataStream( - MOCK_DS_DEPRECATION_REINDEX.index!, - MOCK_DS_DEPRECATION_REINDEX.url - ) - ); - }); - it('renders a warning callout if nodes detected with low disk space', async () => { - httpRequestsMockHelpers.setLoadNodeDiskSpaceResponse([ - { - nodeId: '9OFkjpAKS_aPzJAuEOSg7w', - nodeName: 'MacBook-Pro.local', - available: '25%', - }, - ]); - - await setupPage(); - const flyout = await openReindexFlyoutAt(0); - - await within(flyout).findByTestId('dataStreamLastIndexCreationDate'); - - expect(within(flyout).getByTestId('lowDiskSpaceCallout')).toHaveTextContent( - 'Nodes with low disk space' - ); - expect(within(flyout).getAllByTestId('impactedNodeListItem')).toHaveLength(1); - expect(within(flyout).getAllByTestId('impactedNodeListItem')[0]).toHaveTextContent( - 'MacBook-Pro.local (25% available)' - ); - }); - - it('renders a flyout with data stream confirm step for reindex', async () => { - const dataStreamDeprecation = esDeprecationsMockResponse.migrationsDeprecations[6]; - await setupPage(); - const flyout = await openReindexFlyoutAt(1); - - expect(within(flyout).getByTestId('flyoutTitle')).toHaveTextContent( - `${dataStreamDeprecation.index}` - ); - - expect(await screen.findByTestId('dataStreamLastIndexCreationDate')).toHaveTextContent( - `Migration required for indices created on or before${moment( - defaultMetaResponse.lastIndexRequiringUpgradeCreationDate - ).format(DATE_FORMAT)}` - ); - - expect(screen.getByTestId('dataStreamSize')).toHaveTextContent( - `Size${numeral(defaultMetaResponse.indicesRequiringUpgradeDocsSize).format( - FILE_SIZE_DISPLAY_FORMAT - )}` - ); - - expect(screen.getByTestId('dataStreamDocumentCount')).toHaveTextContent( - `Document Count${defaultMetaResponse.indicesRequiringUpgradeDocsCount}` - ); - - expect(screen.getByTestId('dataStreamMigrationWarningsCallout')).toHaveTextContent( - `Indices created on or before ${moment( - defaultMetaResponse.lastIndexRequiringUpgradeCreationDate - ).format(DATE_FORMAT)} need to be reindexed to a compatible format or set to read-only.` - ); - - expect(screen.getByTestId('reindexDsWarningCallout')).toHaveTextContent( - `This operation requires destructive changes that cannot be reversed` - ); - - expect(screen.getByTestId('migrationWarningCheckbox')).toHaveTextContent( - 'Reindex all incompatible data for this data stream' - ); - expect(screen.getByTestId('startActionButton')).toHaveTextContent('Start reindexing'); - expect(screen.getByTestId('startActionButton')).toBeDisabled(); - expect(screen.getByTestId('closeDataStreamConfirmStepButton')).toBeInTheDocument(); - - await checkMigrationWarningCheckbox(); - - expect(screen.getByTestId('startActionButton')).toBeEnabled(); - }); - describe('reindexing progress', () => { - it('reindexing pending', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_REINDEX.index!, - { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'reindex', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, // now - 10 seconds - successCount: 0, - pendingCount: 1, - inProgressCount: 0, - errorsCount: 0, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'reindex', - }, - ], - } - ); - await setupPage(); - const flyout = await openReindexFlyoutAt(1); - - const checklist = await within(flyout).findByTestId('dataStreamMigrationChecklistFlyout'); - expect(checklist).toHaveTextContent( - `Reindexing ${MOCK_DS_DEPRECATION_REINDEX.index} in progress…` - ); - expect(checklist).toHaveTextContent('0 Indices successfully reindexed.'); - expect(checklist).toHaveTextContent('0 Indices currently getting reindexed.'); - expect(checklist).toHaveTextContent('1 Index waiting to start.'); - expect(screen.getByTestId('startDataStreamMigrationButton')).toBeDisabled(); - expect(screen.getByTestId('cancelDataStreamMigrationButton')).toBeInTheDocument(); - }); - it('reindexing in progress', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_REINDEX.index!, - { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'reindex', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, // now - 10 seconds - successCount: 0, - pendingCount: 0, - inProgressCount: 1, - errorsCount: 0, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'reindex', - }, - ], - } - ); - await setupPage(); - const flyout = await openReindexFlyoutAt(1); - - const checklist = await within(flyout).findByTestId('dataStreamMigrationChecklistFlyout'); - expect(checklist).toHaveTextContent('0 Indices successfully reindexed.'); - expect(checklist).toHaveTextContent('1 Index currently getting reindexed.'); - expect(checklist).toHaveTextContent('0 Indices waiting to start.'); - - expect(screen.getByTestId('startDataStreamMigrationButton')).toBeDisabled(); - expect(screen.getByTestId('cancelDataStreamMigrationButton')).toBeInTheDocument(); - }); - it('reindexing success', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_REINDEX.index!, - { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'reindex', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, // now - 10 seconds - successCount: 1, - pendingCount: 0, - inProgressCount: 0, - errorsCount: 0, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'reindex', - }, - ], - } - ); - await setupPage(); - const flyout = await openReindexFlyoutAt(1); - - const checklist = await within(flyout).findByTestId('dataStreamMigrationChecklistFlyout'); - expect(checklist).toHaveTextContent('1 Index successfully reindexed.'); - expect(checklist).toHaveTextContent('0 Indices currently getting reindexed.'); - expect(checklist).toHaveTextContent('0 Indices waiting to start.'); - - expect(screen.getByTestId('startDataStreamMigrationButton')).toBeDisabled(); - expect(screen.getByTestId('cancelDataStreamMigrationButton')).toBeInTheDocument(); - }); - it('reindexing error', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_REINDEX.index!, - { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'reindex', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, // now - 10 seconds - successCount: 0, - pendingCount: 0, - inProgressCount: 0, - errorsCount: 1, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'reindex', - }, - ], - } - ); - await setupPage(); - const flyout = await openReindexFlyoutAt(1); - - const checklist = await within(flyout).findByTestId('dataStreamMigrationChecklistFlyout'); - expect(checklist).toHaveTextContent('1 Index failed to get reindexed.'); - expect(checklist).toHaveTextContent('0 Indices successfully reindexed.'); - expect(checklist).toHaveTextContent('0 Indices currently getting reindexed.'); - expect(checklist).toHaveTextContent('0 Indices waiting to start.'); - - expect(screen.getByTestId('startDataStreamMigrationButton')).toBeDisabled(); - expect(screen.getByTestId('cancelDataStreamMigrationButton')).toBeInTheDocument(); - }); - }); - }); - describe('read-only modal', () => { - beforeEach(() => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - defaultMigrationResponse - ); - httpRequestsMockHelpers.setDataStreamMetadataResponse( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - getMetaResponseForDataStream( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - MOCK_DS_DEPRECATION_READ_ONLY.url - ) - ); - }); - - it('renders a warning callout if nodes detected with low disk space', async () => { - httpRequestsMockHelpers.setLoadNodeDiskSpaceResponse([ - { - nodeId: '9OFkjpAKS_aPzJAuEOSg7w', - nodeName: 'MacBook-Pro.local', - available: '25%', - }, - ]); - - await setupPage(); - const modal = await openReadOnlyModalAt(1); - - await within(modal).findByTestId('readonlyDataStreamModalTitle'); - - expect(within(modal).getByTestId('lowDiskSpaceCallout')).toHaveTextContent( - 'Nodes with low disk space' - ); - expect(within(modal).getAllByTestId('impactedNodeListItem')).toHaveLength(1); - expect(within(modal).getAllByTestId('impactedNodeListItem')[0]).toHaveTextContent( - 'MacBook-Pro.local (25% available)' - ); - }); - - it('renders a modal with data stream confirm step for read-only', async () => { - await setupPage(); - const modal = await openReadOnlyModalAt(1); - - expect(await within(modal).findByTestId('readOnlyDsWarningCallout')).toHaveTextContent( - 'Setting this data to read-only could affect some of the existing setups' - ); - - expect(screen.getByTestId('migrationWarningCheckbox')).toHaveTextContent( - 'Reindex all incompatible data for this data stream' - ); - expect(screen.getByTestId('startActionButton')).toHaveTextContent('Set all to read-only'); - expect(screen.getByTestId('startActionButton')).toBeDisabled(); - expect(within(modal).getByTestId('cancelDataStreamMigrationModal')).toBeInTheDocument(); - - await checkMigrationWarningCheckbox(); - - expect(screen.getByTestId('startActionButton')).toBeEnabled(); - }); - describe('read-only progress', () => { - it('pending', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'readonly', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, - successCount: 0, - pendingCount: 1, - inProgressCount: 0, - errorsCount: 0, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'readonly', - }, - ], - } - ); - await setupPage(); - await openReadOnlyModalAt(1); - - const checklist = screen.getByTestId('dataStreamMigrationChecklistModal'); - expect(checklist).toHaveTextContent( - `Setting to read-only ${MOCK_DS_DEPRECATION_READ_ONLY.index} in progress…` - ); - expect(checklist).toHaveTextContent('0 Indices successfully set to read-only.'); - expect(checklist).toHaveTextContent('0 Indices currently getting set to read-only.'); - expect(checklist).toHaveTextContent('1 Index waiting to start.'); - - expect(screen.getByTestId('startDataStreamMigrationButton')).toBeDisabled(); - expect(screen.getByTestId('cancelDataStreamMigrationButton')).toBeInTheDocument(); - }); - it('read-only in progress', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'readonly', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, // now - 10 seconds - successCount: 0, - pendingCount: 0, - inProgressCount: 1, - errorsCount: 0, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'readonly', - }, - ], - } - ); - await setupPage(); - await openReadOnlyModalAt(1); - - const checklist = screen.getByTestId('dataStreamMigrationChecklistModal'); - expect(checklist).toHaveTextContent('0 Indices successfully set to read-only.'); - expect(checklist).toHaveTextContent('1 Index currently getting set to read-only.'); - expect(checklist).toHaveTextContent('0 Indices waiting to start.'); - - expect(screen.getByTestId('startDataStreamMigrationButton')).toBeDisabled(); - expect(screen.getByTestId('cancelDataStreamMigrationButton')).toBeInTheDocument(); - }); - it('read-only success', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'readonly', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, // now - 10 seconds - successCount: 1, - pendingCount: 0, - inProgressCount: 0, - errorsCount: 0, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'readonly', - }, - ], - } - ); - await setupPage(); - await openReadOnlyModalAt(1); - - const checklist = screen.getByTestId('dataStreamMigrationChecklistModal'); - expect(checklist).toHaveTextContent('1 Index successfully set to read-only.'); - expect(checklist).toHaveTextContent('0 Indices currently getting set to read-only.'); - expect(checklist).toHaveTextContent('0 Indices waiting to start.'); - - expect(screen.getByTestId('startDataStreamMigrationButton')).toBeDisabled(); - expect(screen.getByTestId('cancelDataStreamMigrationButton')).toBeInTheDocument(); - }); - it('reindexing error', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'readonly', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, - successCount: 0, - pendingCount: 0, - inProgressCount: 0, - errorsCount: 1, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'readonly', - }, - ], - } - ); - await setupPage(); - await openReadOnlyModalAt(1); - - const checklist = screen.getByTestId('dataStreamMigrationChecklistModal'); - - expect(checklist).toHaveTextContent('1 Index failed to get set to read-only.'); - expect(checklist).toHaveTextContent('0 Indices successfully set to read-only.'); - expect(checklist).toHaveTextContent('0 Indices currently getting set to read-only.'); - expect(checklist).toHaveTextContent('0 Indices waiting to start.'); - - expect(screen.getByTestId('startDataStreamMigrationButton')).toBeDisabled(); - expect(screen.getByTestId('cancelDataStreamMigrationButton')).toBeInTheDocument(); - }); - }); - }); -}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/default_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/default_deprecation_flyout.test.ts deleted file mode 100644 index d78b68aaabd6a..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/default_deprecation_flyout.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 '@testing-library/jest-dom'; -import { fireEvent, screen, within } from '@testing-library/react'; - -import { setupEnvironment } from '../helpers/setup_environment'; -import { setupElasticsearchPage } from './es_deprecations.helpers'; -import { esDeprecationsMockResponse, MOCK_SNAPSHOT_ID, MOCK_JOB_ID } from './mocked_responses'; - -describe('Default deprecation flyout', () => { - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - let httpSetup: ReturnType['httpSetup']; - - beforeEach(() => { - const mockEnvironment = setupEnvironment(); - httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; - httpSetup = mockEnvironment.httpSetup; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse); - httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'idle', - }); - httpRequestsMockHelpers.setReindexStatusResponse('reindex_index', { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'foo', - reindexName: 'reindexed-foo', - aliases: [], - }, - }); - }); - - test('renders a flyout with deprecation details', async () => { - const multiFieldsDeprecation = esDeprecationsMockResponse.migrationsDeprecations[2]; - await setupElasticsearchPage(httpSetup); - - fireEvent.click(screen.getAllByTestId('deprecation-default')[0]); - - const flyout = await screen.findByTestId('defaultDeprecationDetails'); - - expect(within(flyout).getByTestId('flyoutTitle')).toHaveTextContent( - multiFieldsDeprecation.message - ); - expect( - (within(flyout).getByTestId('documentationLink') as HTMLAnchorElement).getAttribute('href') - ).toBe(multiFieldsDeprecation.url); - expect(within(flyout).getByTestId('flyoutDescription')).toHaveTextContent( - String(multiFieldsDeprecation.index) - ); - }); -}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts deleted file mode 100644 index f3bd67e18e2df..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts +++ /dev/null @@ -1,994 +0,0 @@ -/* - * 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 '@testing-library/jest-dom'; -import { fireEvent, screen, waitFor, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import { API_BASE_PATH } from '../../../common/constants'; -import type { - ESUpgradeStatus, - EnrichedDeprecationInfo, - MlAction, - ReindexAction, - UnfreezeAction, -} from '../../../common/types'; -import { DataStreamMigrationStatus } from '../../../common/types'; -import { ReindexStep } from '@kbn/reindex-service-plugin/common'; -import { ReindexStatus } from '@kbn/upgrade-assistant-pkg-common'; -import { REINDEX_SERVICE_BASE_PATH } from '@kbn/reindex-service-plugin/server'; -import { setupEnvironment } from '../helpers/setup_environment'; -import { setupElasticsearchPage } from './es_deprecations.helpers'; -import { - esDeprecationsMockResponse, - MOCK_SNAPSHOT_ID, - MOCK_JOB_ID, - createEsDeprecationsMockResponse, - MOCK_DS_DEPRECATION, - MOCK_DS_DEPRECATION_REINDEX, - MOCK_DS_DEPRECATION_READ_ONLY, -} from './mocked_responses'; - -// Failing: See https://github.com/elastic/kibana/issues/248433 -describe.skip('ES deprecations table', () => { - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - let httpSetup: ReturnType['httpSetup']; - - const waitForResolutionCellsToSettle = async () => { - if (screen.queryAllByTestId('deprecationTableRow').length === 0) { - return; - } - - // Wait on a visible UI boundary that indicates mount-time status providers have settled. - // (Avoids act warnings without introducing fake timers.) - await waitFor(() => { - expect(screen.queryAllByText('Loading status…')).toHaveLength(0); - }); - - await waitFor(() => { - const indexResolutionCells = screen.queryAllByTestId('reindexTableCell-correctiveAction'); - const dataStreamResolutionCells = screen.queryAllByTestId( - 'dataStreamReindexTableCell-correctiveAction' - ); - - for (const cell of [...indexResolutionCells, ...dataStreamResolutionCells]) { - expect(cell).not.toHaveTextContent('Loading status…'); - } - }); - }; - - const setupPage = async () => { - await setupElasticsearchPage(httpSetup); - // Avoid act() warnings from async status-fetch effects by waiting for the resolution cells - // to exit their "Loading status…" placeholder state. - await waitForResolutionCellsToSettle(); - }; - - beforeEach(() => { - const mockEnvironment = setupEnvironment(); - httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; - httpSetup = mockEnvironment.httpSetup; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse); - httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'idle', - }); - httpRequestsMockHelpers.setReindexStatusResponse('reindex_index', { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'foo', - reindexName: 'reindexed-foo', - aliases: [], - }, - }); - httpRequestsMockHelpers.setLoadRemoteClustersResponse([]); - }); - - afterEach(async () => { - await waitForResolutionCellsToSettle(); - }); - - it('renders deprecations', async () => { - await setupPage(); - - // Verify container exists - expect(screen.getByTestId('esDeprecationsContent')).toBeInTheDocument(); - - // Verify all deprecations appear in the table - expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( - esDeprecationsMockResponse.migrationsDeprecations.length - ); - }); - - it('refreshes deprecation data', async () => { - await setupPage(); - fireEvent.click(screen.getByTestId('refreshButton')); - - const mlDeprecation = esDeprecationsMockResponse.migrationsDeprecations[0]; - const reindexDeprecation = esDeprecationsMockResponse.migrationsDeprecations[3]; - - // Since upgradeStatusMockResponse includes ML and reindex actions (which require fetching status), there will be 4 requests made - await waitFor(() => { - expect(httpSetup.get).toHaveBeenCalledWith( - `${API_BASE_PATH}/es_deprecations`, - expect.anything() - ); - expect(httpSetup.get).toHaveBeenCalledWith( - `${API_BASE_PATH}/ml_snapshots/${(mlDeprecation.correctiveAction as MlAction).jobId}/${ - (mlDeprecation.correctiveAction as MlAction).snapshotId - }`, - expect.anything() - ); - expect(httpSetup.get).toHaveBeenCalledWith( - `${REINDEX_SERVICE_BASE_PATH}/${reindexDeprecation.index}`, - expect.anything() - ); - expect(httpSetup.get).toHaveBeenCalledWith( - `${API_BASE_PATH}/ml_upgrade_mode`, - expect.anything() - ); - }); - }); - - it('shows critical and warning deprecations count', async () => { - await setupPage(); - - const criticalDeprecations = esDeprecationsMockResponse.migrationsDeprecations.filter( - (deprecation) => deprecation.level === 'critical' - ); - const warningDeprecations = esDeprecationsMockResponse.migrationsDeprecations.filter( - (deprecation) => deprecation.level !== 'critical' - ); - - expect(screen.getByTestId('criticalDeprecationsCount')).toHaveTextContent( - String(criticalDeprecations.length) - ); - expect(screen.getByTestId('warningDeprecationsCount')).toHaveTextContent( - String(warningDeprecations.length) - ); - }); - - describe('remote clusters callout', () => { - beforeEach(() => { - httpRequestsMockHelpers.setLoadRemoteClustersResponse(['test_remote_cluster']); - }); - - it('shows a warning message if a user has remote clusters configured', async () => { - await setupPage(); - // Verify warning exists - expect(screen.getByTestId('remoteClustersWarningCallout')).toBeInTheDocument(); - }); - }); - - describe('search bar', () => { - let user: ReturnType; - - beforeEach(() => { - user = userEvent.setup(); - }); - - const clickFilterByIndex = async (index: number) => { - const searchBar = screen.getByTestId('searchBarContainer'); - const filterButtons = searchBar.querySelectorAll('button.euiFilterButton'); - - expect(filterButtons[index]).toBeDefined(); - await user.click(filterButtons[index] as HTMLButtonElement); - }; - - const clickFilterByTitle = async (title: string) => { - const filterButton = await waitFor(() => { - const el: HTMLButtonElement | null = document.body.querySelector( - `.euiSelectableListItem[title="${title}"]` - ); - expect(el).not.toBeNull(); - return el!; - }); - - await user.click(filterButton); - }; - - const setSearchInputValue = async (searchValue: string) => { - const input = within(screen.getByTestId('searchBarContainer')).getByRole('searchbox'); - fireEvent.change(input, { target: { value: searchValue } }); - fireEvent.keyUp(input, { target: { value: searchValue } }); - }; - - it('filters results by status', async () => { - await setupPage(); - - await clickFilterByIndex(0); // status filter is first - await clickFilterByTitle('Critical'); - - const criticalDeprecations = esDeprecationsMockResponse.migrationsDeprecations.filter( - (deprecation) => deprecation.level === 'critical' - ); - - await waitFor(() => - expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( - criticalDeprecations.length - ) - ); - - await clickFilterByIndex(0); - await clickFilterByTitle('Critical'); // Reset filter - - await waitFor(() => - expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( - esDeprecationsMockResponse.migrationsDeprecations.length - ) - ); - }); - - it('filters results by type', async () => { - await setupPage(); - - await clickFilterByIndex(1); // type filter is second - await clickFilterByTitle('Cluster'); - - const clusterDeprecations = esDeprecationsMockResponse.migrationsDeprecations.filter( - (deprecation) => deprecation.type === 'cluster_settings' - ); - - await waitFor(() => - expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( - clusterDeprecations.length - ) - ); - }); - - it('filters results by query string', async () => { - const multiFieldsDeprecation = esDeprecationsMockResponse.migrationsDeprecations[2]; - - await setupPage(); - await setSearchInputValue(multiFieldsDeprecation.message); - - await waitFor(() => expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength(1)); - expect(screen.getAllByTestId('deprecationTableRow')[0]).toHaveTextContent( - multiFieldsDeprecation.message - ); - }); - - it('shows error for invalid search queries', async () => { - await setupPage(); - - await setSearchInputValue('%'); - - expect(screen.getByTestId('invalidSearchQueryMessage')).toBeInTheDocument(); - expect(screen.getByTestId('invalidSearchQueryMessage')).toHaveTextContent('Invalid search'); - }); - - it('shows message when search query does not return results', async () => { - await setupPage(); - - await setSearchInputValue('foobarbaz'); - - expect(screen.getByTestId('noDeprecationsRow')).toBeInTheDocument(); - expect(screen.getByTestId('noDeprecationsRow')).toHaveTextContent( - 'No Elasticsearch deprecation issues found' - ); - }); - }); - - describe('pagination', () => { - let user: ReturnType; - - const esDeprecationsMockResponseWithManyDeprecations = createEsDeprecationsMockResponse(20); - const { migrationsDeprecations } = esDeprecationsMockResponseWithManyDeprecations; - - const getPaginationItemsCount = () => { - const pagination = screen.getByTestId('esDeprecationsPagination'); - return pagination.querySelectorAll('.euiPagination__item').length; - }; - - const waitForStatusCellsToSettle = async () => { - // Fast-path: if there is no loading placeholder visible, there is nothing to wait for. - if (screen.queryAllByText('Loading status…').length === 0) { - return; - } - await waitFor(() => { - expect(screen.queryAllByText('Loading status…')).toHaveLength(0); - }); - }; - - beforeEach(() => { - user = userEvent.setup(); - httpRequestsMockHelpers.setLoadEsDeprecationsResponse( - esDeprecationsMockResponseWithManyDeprecations - ); - httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'idle', - }); - }); - - it('shows the correct number of pages and deprecations per page', async () => { - await setupPage(); - - expect(getPaginationItemsCount()).toEqual( - Math.round(migrationsDeprecations.length / 50) // Default rows per page is 50 - ); - expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength(50); - - // Navigate to the next page - await user.click(screen.getByTestId('pagination-button-1')); - - // On the second (last) page, we expect to see the remaining deprecations - await waitFor(() => - expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( - migrationsDeprecations.length - 50 - ) - ); - await waitForStatusCellsToSettle(); - }); - - it('allows the number of viewable rows to change', async () => { - await setupPage(); - await user.click(screen.getByTestId('tablePaginationPopoverButton')); - - // Rows-per-page options are rendered in a portal; query the whole document. - const rowsPerPageButton = await screen.findByTestId('tablePagination-100-rows'); - - await user.click(rowsPerPageButton); - - await waitFor(() => { - expect(getPaginationItemsCount()).toEqual( - Math.round(migrationsDeprecations.length / 100) // Rows per page is now 100 - ); - expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( - migrationsDeprecations.length - ); - }); - await waitForStatusCellsToSettle(); - }, 7000); - - it('updates pagination when filters change', async () => { - const criticalDeprecations = migrationsDeprecations.filter( - (deprecation) => deprecation.level === 'critical' - ); - - await setupPage(); - - // Status filter is the first filter button - const searchBar = screen.getByTestId('searchBarContainer'); - const filterButtons = searchBar.querySelectorAll('button.euiFilterButton'); - await user.click(filterButtons[0] as HTMLButtonElement); - - const criticalFilterButton = await waitFor(() => { - const el: HTMLButtonElement | null = document.body.querySelector( - `.euiSelectableListItem[title="Critical"]` - ); - expect(el).not.toBeNull(); - return el!; - }); - - await user.click(criticalFilterButton); - - // Only 40 critical deprecations, so only one page should show - await waitFor(() => { - expect(getPaginationItemsCount()).toEqual(1); - expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( - criticalDeprecations.length - ); - }); - await waitForStatusCellsToSettle(); - }, 7000); - - it('updates pagination on search', async () => { - const reindexDeprecations = migrationsDeprecations.filter( - (deprecation) => deprecation.correctiveAction?.type === 'reindex' - ); - - await setupPage(); - - const input = within(screen.getByTestId('searchBarContainer')).getByRole('searchbox'); - fireEvent.change(input, { target: { value: 'Index created before 7.0' } }); - fireEvent.keyUp(input, { target: { value: 'Index created before 7.0' } }); - - // Only 20 deprecations that match, so only one page should show - await waitFor(() => { - expect(getPaginationItemsCount()).toEqual(1); - expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( - reindexDeprecations.length - ); - }); - await waitForStatusCellsToSettle(); - }); - - it('maintains correct row state across pagination', async () => { - await setupPage(); - - const getFirstRowMessageCellText = () => { - const row = screen.getAllByTestId('deprecationTableRow')[0]; - const messageCell = row.querySelector('[data-test-subj$="-message"]'); - expect(messageCell).not.toBeNull(); - return messageCell!.textContent; - }; - - // Verify we have multiple pages - expect(getPaginationItemsCount()).toBeGreaterThan(1); - - // Get the message of the first deprecation on page 1 - const firstDeprecationMessagePage1 = getFirstRowMessageCellText(); - - // Navigate to page 2 - await user.click(screen.getByTestId('pagination-button-1')); - - // Wait for the first row message to change on page 2 - await waitFor(() => { - expect(getFirstRowMessageCellText()).not.toEqual(firstDeprecationMessagePage1); - }); - const firstDeprecationMessagePage2 = getFirstRowMessageCellText(); - - // The first items on different pages should be different - expect(firstDeprecationMessagePage1).not.toEqual(firstDeprecationMessagePage2); - - // Navigate back to page 1 - await user.click(screen.getByTestId('pagination-button-0')); - - // Verify the first deprecation on page 1 is still the same - await waitFor(() => { - expect(getFirstRowMessageCellText()).toEqual(firstDeprecationMessagePage1); - }); - await waitForStatusCellsToSettle(); - }); - }); - - describe('no deprecations', () => { - beforeEach(() => { - const noDeprecationsResponse = { - totalCriticalDeprecations: 0, - migrationsDeprecations: [], - totalCriticalHealthIssues: 0, - enrichedHealthIndicators: [], - }; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(noDeprecationsResponse); - }); - - test('renders prompt', async () => { - await setupPage(); - - expect(screen.getByTestId('noDeprecationsPrompt')).toBeInTheDocument(); - expect(screen.getByTestId('noDeprecationsPrompt')).toHaveTextContent( - 'Your Elasticsearch configuration is up to date' - ); - }); - }); - - describe('recommended actions for indices', () => { - // Helper to DRY up repeated setup - const setupRecommendedActionTest = async ({ - correctiveAction, - reindexMeta = {}, - deprecationIndex = 3, - }: { - correctiveAction: any; - reindexMeta?: Partial; - deprecationIndex?: number; - }) => { - httpRequestsMockHelpers.setLoadEsDeprecationsResponse({ - ...esDeprecationsMockResponse, - migrationsDeprecations: esDeprecationsMockResponse.migrationsDeprecations.map( - (deprecation, idx) => - idx === deprecationIndex - ? ({ - level: 'critical', - resolveDuringUpgrade: false, - type: 'index_settings', - message: 'Index created before 7.0', - details: 'deprecation details', - url: 'doc_url', - index: correctiveAction.index || 'reindex_index', - correctiveAction, - } as EnrichedDeprecationInfo) - : deprecation - ), - } as ESUpgradeStatus); - - if (correctiveAction.type === 'reindex' || correctiveAction.type === 'unfreeze') { - httpRequestsMockHelpers.setReindexStatusResponse( - correctiveAction.index || 'reindex_index', - { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: reindexMeta.indexName || 'foo', - reindexName: reindexMeta.reindexName || 'reindexed-foo', - aliases: [], - isFrozen: reindexMeta.isFrozen || false, - isReadonly: reindexMeta.isReadonly || false, - isInDataStream: reindexMeta.isInDataStream || false, - isFollowerIndex: reindexMeta.isFollowerIndex || false, - ...reindexMeta, - }, - } - ); - } - - await setupPage(); - }; - - it('recommends set unfreeze if index is frozen', async () => { - await setupRecommendedActionTest({ - correctiveAction: { - type: 'unfreeze', - index: 'reindex_index', - metadata: { - isClosedIndex: false, - isFrozenIndex: true, - isInDataStream: false, - }, - }, - }); - expect(screen.getAllByTestId('reindexTableCell-correctiveAction')).toHaveLength(1); - expect(screen.getByTestId('reindexTableCell-correctiveAction')).toHaveTextContent( - 'Recommended: unfreeze' - ); - }); - - it('recommends set as read-only if index is a follower index', async () => { - await setupRecommendedActionTest({ - correctiveAction: { - type: 'reindex', - index: 'reindex_index', - metadata: { - isClosedIndex: false, - isFrozenIndex: false, - isInDataStream: false, - }, - }, - reindexMeta: { - isFollowerIndex: true, - indexName: 'follower-index', - reindexName: 'reindexed-follower-index', - }, - }); - expect(screen.getAllByTestId('reindexTableCell-correctiveAction')).toHaveLength(1); - expect(screen.getByTestId('reindexTableCell-correctiveAction')).toHaveTextContent( - 'Recommended: set to read-only' - ); - }); - - it('recommends set as read-only if index is bigger than 1GB', async () => { - await setupRecommendedActionTest({ - correctiveAction: { - type: 'reindex', - index: 'reindex_index', - indexSizeInBytes: 1173741824, // > 1GB - metadata: { - isClosedIndex: false, - isFrozenIndex: false, - isInDataStream: false, - }, - }, - reindexMeta: { - indexName: 'large-index', - reindexName: 'reindexed-large-index', - }, - }); - expect(screen.getAllByTestId('reindexTableCell-correctiveAction')).toHaveLength(1); - expect(screen.getByTestId('reindexTableCell-correctiveAction')).toHaveTextContent( - 'Recommended: set to read-only' - ); - }); - - it('recommends reindexing if index is already read-only', async () => { - await setupRecommendedActionTest({ - correctiveAction: { - type: 'reindex', - index: 'reindex_index', - metadata: { - isClosedIndex: false, - isFrozenIndex: false, - isInDataStream: false, - }, - }, - reindexMeta: { - isReadonly: true, - indexName: 'readonly-index', - reindexName: 'reindexed-readonly-index', - }, - }); - expect(screen.getAllByTestId('reindexTableCell-correctiveAction')).toHaveLength(1); - expect(screen.getByTestId('reindexTableCell-correctiveAction')).toHaveTextContent( - 'Recommended: reindex' - ); - }); - - it('recommends set as read-only if reindexing is excluded', async () => { - await setupRecommendedActionTest({ - correctiveAction: { - type: 'reindex', - index: 'reindex_index', - excludedActions: ['readOnly'], - metadata: { - isClosedIndex: false, - isFrozenIndex: false, - isInDataStream: false, - }, - }, - reindexMeta: { - indexName: 'excluded-index', - reindexName: 'reindexed-excluded-index', - }, - }); - expect(screen.getAllByTestId('reindexTableCell-correctiveAction')).toHaveLength(1); - expect(screen.getByTestId('reindexTableCell-correctiveAction')).toHaveTextContent( - 'Recommended: reindex' - ); - }); - - it('recommends manual fix if follower index and already read-only', async () => { - await setupRecommendedActionTest({ - correctiveAction: { - type: 'reindex', - index: 'large_and_readonly_index', - metadata: { - isClosedIndex: false, - isFrozenIndex: false, - isInDataStream: false, - }, - }, - reindexMeta: { - isFollowerIndex: true, - isReadonly: true, - indexName: 'large_and_readonly_index', - reindexName: 'reindexed-large_and_readonly_index', - }, - }); - expect(screen.getAllByTestId('reindexTableCell-correctiveAction')).toHaveLength(1); - expect(screen.getByTestId('reindexTableCell-correctiveAction')).toHaveTextContent( - 'Resolve manually' - ); - }); - it('recommends reindexing by default', async () => { - await setupRecommendedActionTest({ - correctiveAction: { - type: 'reindex', - index: 'reindex_index', - metadata: { - isClosedIndex: false, - isFrozenIndex: false, - isInDataStream: false, - }, - }, - }); - expect(screen.getAllByTestId('reindexTableCell-correctiveAction')).toHaveLength(1); - expect(screen.getByTestId('reindexTableCell-correctiveAction')).toHaveTextContent( - 'Recommended: reindex' - ); - }); - }); - describe('recommended actions for data streams', () => { - it('recommends read-only by default', async () => { - await setupPage(); - - expect( - screen.getAllByTestId('dataStreamReindexTableCell-correctiveAction')[0] - ).toHaveTextContent('Recommended: set to read-only'); - }); - - it('recommends reindexing if read-only is excluded', async () => { - await setupPage(); - - expect( - screen.getAllByTestId('dataStreamReindexTableCell-correctiveAction')[1] - ).toHaveTextContent('Recommended: reindex'); - }); - }); - - describe('action buttons', () => { - describe('frozen indices', () => { - beforeEach(() => { - httpRequestsMockHelpers.setLoadEsDeprecationsResponse({ - ...esDeprecationsMockResponse, - migrationsDeprecations: esDeprecationsMockResponse.migrationsDeprecations.map( - (deprecation) => - deprecation === esDeprecationsMockResponse.migrationsDeprecations[3] - ? ({ - level: 'critical', - resolveDuringUpgrade: false, - type: 'index_settings', - message: 'Index created before 7.0', - details: 'deprecation details', - url: 'doc_url', - index: 'reindex_index', - correctiveAction: { - type: 'unfreeze', - metadata: { - isClosedIndex: false, - isFrozenIndex: true, - isInDataStream: false, - }, - } as UnfreezeAction, - } as EnrichedDeprecationInfo) - : deprecation - ), - } as ESUpgradeStatus); - - httpRequestsMockHelpers.setUpdateIndexResponse( - esDeprecationsMockResponse.migrationsDeprecations[3].index!, - { data: '', error: null } - ); - }); - it('it displays reindexing and unfreeze button for frozen index', async () => { - await setupPage(); - - expect(screen.getAllByTestId('reindexTableCell-actions')).toHaveLength(1); - - expect(screen.queryByTestId('deprecation-unfreeze-unfreeze')).not.toBeNull(); - expect(screen.queryByTestId('deprecation-unfreeze-reindex')).not.toBeNull(); - }); - it('it only displays reindexing button if reindex in progress', async () => { - httpRequestsMockHelpers.setReindexStatusResponse( - esDeprecationsMockResponse.migrationsDeprecations[3].index!, - { - reindexOp: { - status: ReindexStatus.inProgress, - lastCompletedStep: ReindexStep.readonly, - reindexTaskPercComplete: null, - }, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'foo', - reindexName: 'reindexed-foo', - aliases: [], - isFrozen: false, - isReadonly: false, - isInDataStream: false, - isFollowerIndex: false, - }, - } - ); - - await setupPage(); - - // Ensure the async status load has completed before the test ends (prevents act warnings - // from a pending updateStatus() resolving after unmount). - await waitFor(() => { - expect(screen.getByText(/Reindexing in progress/)).toBeInTheDocument(); - }); - - expect(screen.getAllByTestId('reindexTableCell-actions')).toHaveLength(1); - - expect(screen.queryByTestId('deprecation-unfreeze-unfreeze')).toBeNull(); - expect(screen.queryByTestId('deprecation-unfreeze-reindex')).not.toBeNull(); - }); - it('it only displays unfreeze button if unfreezing in progress', async () => { - await setupPage(); - - fireEvent.click(screen.getAllByTestId('deprecation-unfreeze-unfreeze')[0]); - const modal = await screen.findByTestId('updateIndexModal'); - fireEvent.click(within(modal).getByTestId('startIndexUnfreezeButton')); - - expect(screen.getAllByTestId('reindexTableCell-actions')).toHaveLength(1); - - await waitFor(() => { - expect(screen.queryByTestId('deprecation-unfreeze-unfreeze')).not.toBeNull(); - expect(screen.queryByTestId('deprecation-unfreeze-reindex')).toBeNull(); - }); - }); - }); - describe('reindexing indices', () => { - const setupReindexingTest = async ({ - excludedActions = [], - index = 'reindex_index', - metaOverrides = {}, - }: { - excludedActions?: string[]; - index?: string; - metaOverrides?: Partial; - } = {}) => { - httpRequestsMockHelpers.setLoadEsDeprecationsResponse({ - ...esDeprecationsMockResponse, - migrationsDeprecations: esDeprecationsMockResponse.migrationsDeprecations.map( - (deprecation) => - deprecation === esDeprecationsMockResponse.migrationsDeprecations[3] - ? ({ - level: 'critical', - resolveDuringUpgrade: false, - type: 'index_settings', - message: 'Index created before 7.0', - details: 'deprecation details', - url: 'doc_url', - index, - correctiveAction: { - type: 'reindex', - excludedActions, - metadata: { - isClosedIndex: false, - isFrozenIndex: false, - isInDataStream: false, - ...metaOverrides, - }, - } as ReindexAction, - } as EnrichedDeprecationInfo) - : deprecation - ), - } as ESUpgradeStatus); - - httpRequestsMockHelpers.setReindexStatusResponse(index, { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'excluded-index', - reindexName: 'reindexed-excluded-index', - aliases: [], - isFrozen: false, - isReadonly: false, - isInDataStream: false, - isFollowerIndex: false, - ...metaOverrides, - }, - }); - - await setupPage(); - }; - - it('it displays reindexing and readonly for indices if both are valid', async () => { - await setupReindexingTest(); - expect(screen.getAllByTestId('reindexTableCell-actions')).toHaveLength(1); - expect(screen.queryByTestId('deprecation-reindex-readonly')).not.toBeNull(); - expect(screen.queryByTestId('deprecation-reindex-reindex')).not.toBeNull(); - }); - it('only displays read-only button if reindexing is excluded', async () => { - await setupReindexingTest({ excludedActions: ['readOnly'] }); - expect(screen.getAllByTestId('reindexTableCell-actions')).toHaveLength(1); - expect(screen.queryByTestId('deprecation-reindex-readonly')).toBeNull(); - expect(screen.queryByTestId('deprecation-reindex-reindex')).not.toBeNull(); - }); - it('only displays read-only button if index is a follower index', async () => { - await setupReindexingTest({ metaOverrides: { isFollowerIndex: true } }); - expect(screen.getAllByTestId('reindexTableCell-actions')).toHaveLength(1); - expect(screen.queryByTestId('deprecation-reindex-readonly')).not.toBeNull(); - expect(screen.queryByTestId('deprecation-reindex-reindex')).toBeNull(); - }); - it('only displays reindex button if read-only is excluded', async () => { - await setupReindexingTest({ - excludedActions: ['reindex'], - index: 'readonly_index', - }); - expect(screen.getAllByTestId('reindexTableCell-actions')).toHaveLength(1); - expect(screen.queryByTestId('deprecation-reindex-readonly')).not.toBeNull(); - expect(screen.queryByTestId('deprecation-reindex-reindex')).toBeNull(); - }); - it('it only displays readonly button if readonly in progress', async () => { - await setupReindexingTest(); - - fireEvent.click(screen.getAllByTestId('deprecation-reindex-readonly')[0]); - const modal = await screen.findByTestId('updateIndexModal'); - fireEvent.click(within(modal).getByTestId('startIndexReadonlyButton')); - - await waitFor(() => { - expect(screen.queryByTestId('deprecation-reindex-readonly')).not.toBeNull(); - expect(screen.queryByTestId('deprecation-reindex-reindex')).toBeNull(); - }); - }); - }); - }); - describe('datastreams', () => { - const defaultMigrationResponse = { - hasRequiredPrivileges: true, - migrationOp: { status: DataStreamMigrationStatus.notStarted }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'reindex', - }, - { - warningType: 'incompatibleDataStream', - resolutionType: 'readonly', - }, - ], - }; - beforeEach(() => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION.index!, - defaultMigrationResponse - ); - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_READ_ONLY.index!, - defaultMigrationResponse - ); - - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse( - MOCK_DS_DEPRECATION_REINDEX.index!, - defaultMigrationResponse - ); - }); - - it('displays read-only and reindex depending if both are valid', async () => { - await setupPage(); - - const actionsCell = screen.getAllByTestId('dataStreamReindexTableCell-actions')[0]; - expect(within(actionsCell).queryByTestId('deprecation-dataStream-reindex')).not.toBeNull(); - expect(within(actionsCell).queryByTestId('deprecation-dataStream-readonly')).not.toBeNull(); - }); - - it('recommends reindexing if read-only is excluded', async () => { - await setupPage(); - - const actionsCell = screen.getAllByTestId('dataStreamReindexTableCell-actions')[1]; - expect(within(actionsCell).queryByTestId('deprecation-dataStream-reindex')).not.toBeNull(); - expect(within(actionsCell).queryByTestId('deprecation-dataStream-readonly')).toBeNull(); - }); - - it('recommends readonly if reindex is excluded', async () => { - await setupPage(); - - const actionsCell = screen.getAllByTestId('dataStreamReindexTableCell-actions')[2]; - expect(within(actionsCell).queryByTestId('deprecation-dataStream-reindex')).toBeNull(); - expect(within(actionsCell).queryByTestId('deprecation-dataStream-readonly')).not.toBeNull(); - }); - it('only displays reindex button if reindexing is in progress', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse(MOCK_DS_DEPRECATION.index!, { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'reindex', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, // now - 10 seconds - successCount: 0, - pendingCount: 1, - inProgressCount: 0, - errorsCount: 0, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'reindex', - }, - ], - }); - await setupPage(); - - const actionsCell = screen.getAllByTestId('dataStreamReindexTableCell-actions')[0]; - expect(within(actionsCell).queryByTestId('deprecation-dataStream-reindex')).not.toBeNull(); - expect(within(actionsCell).queryByTestId('deprecation-dataStream-readonly')).toBeNull(); - }); - it('only displays readonly button if setting read-only is in progress', async () => { - httpRequestsMockHelpers.setDataStreamMigrationStatusResponse(MOCK_DS_DEPRECATION.index!, { - hasRequiredPrivileges: true, - migrationOp: { - resolutionType: 'readonly', - status: DataStreamMigrationStatus.inProgress, - taskPercComplete: 1, - progressDetails: { - startTimeMs: Date.now() - 10000, // now - 10 seconds - successCount: 0, - pendingCount: 1, - inProgressCount: 0, - errorsCount: 0, - }, - }, - warnings: [ - { - warningType: 'incompatibleDataStream', - resolutionType: 'readonly', - }, - ], - }); - await setupPage(); - - const actionsCell = screen.getAllByTestId('dataStreamReindexTableCell-actions')[0]; - expect(within(actionsCell).queryByTestId('deprecation-dataStream-reindex')).toBeNull(); - expect(within(actionsCell).queryByTestId('deprecation-dataStream-readonly')).not.toBeNull(); - }); - }); -}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/error_handling.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/error_handling.test.ts deleted file mode 100644 index ae2b2ab8dd4ce..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/error_handling.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 '@testing-library/jest-dom'; -import { screen } from '@testing-library/react'; - -import { setupEnvironment } from '../helpers/setup_environment'; -import { setupElasticsearchPage } from './es_deprecations.helpers'; - -describe('Error handling', () => { - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - let httpSetup: ReturnType['httpSetup']; - - beforeEach(() => { - const mockEnvironment = setupEnvironment(); - httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; - httpSetup = mockEnvironment.httpSetup; - }); - - it('handles 403', async () => { - const error = { - statusCode: 403, - error: 'Forbidden', - message: 'Forbidden', - }; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); - - await setupElasticsearchPage(httpSetup); - - expect(await screen.findByTestId('deprecationsPageLoadingError')).toHaveTextContent( - 'You are not authorized to view Elasticsearch deprecation issues.' - ); - }); - - it('shows upgraded message when all nodes have been upgraded', async () => { - const error = { - statusCode: 426, - error: 'Upgrade required', - message: 'There are some nodes running a different version of Elasticsearch', - attributes: { - // This is marked true in the scenario where none of the nodes have the same major version of Kibana, - // and therefore we assume all have been upgraded - allNodesUpgraded: true, - }, - }; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); - - await setupElasticsearchPage(httpSetup); - - expect(await screen.findByTestId('deprecationsPageLoadingError')).toHaveTextContent( - 'All Elasticsearch nodes have been upgraded.' - ); - }); - - it('shows partially upgrade error when nodes are running different versions', async () => { - const error = { - statusCode: 426, - error: 'Upgrade required', - message: 'There are some nodes running a different version of Elasticsearch', - attributes: { - allNodesUpgraded: false, - }, - }; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); - - await setupElasticsearchPage(httpSetup); - - expect(await screen.findByTestId('deprecationsPageLoadingError')).toHaveTextContent( - 'Upgrade Kibana to the same version as your Elasticsearch cluster. One or more nodes in the cluster is running a different version than Kibana.' - ); - }); - - it('handles generic error', async () => { - const error = { - statusCode: 500, - error: 'Internal server error', - message: 'Internal server error', - }; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); - - await setupElasticsearchPage(httpSetup); - - expect(await screen.findByTestId('deprecationsPageLoadingError')).toHaveTextContent( - 'Could not retrieve Elasticsearch deprecation issues.' - ); - }); -}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/es_deprecations.helpers.tsx b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/es_deprecations.helpers.tsx deleted file mode 100644 index 8cba1edecb8e6..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/es_deprecations.helpers.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 type { HttpSetup } from '@kbn/core/public'; -import React from 'react'; -import { screen, waitFor } from '@testing-library/react'; -import { renderWithI18n } from '@kbn/test-jest-helpers'; -import { createMemoryHistory } from 'history'; -import { Router, Routes, Route } from '@kbn/shared-ux-router'; -import { act } from 'react-dom/test-utils'; -import { EsDeprecations } from '../../../public/application/components'; -import { WithAppDependencies } from '../helpers/setup_environment'; - -export const setupElasticsearchPage = async ( - httpSetup: HttpSetup, - overrides?: Record -): Promise => { - const EsDeprecationsWithDependencies = WithAppDependencies(EsDeprecations, httpSetup, overrides); - const history = createMemoryHistory({ initialEntries: ['/es_deprecations'] }); - - renderWithI18n( - - - - - - ); - - // In suites that opt into fake timers, some mount-time requests resolve via timers. - // Flush pending timers inside act() so their React state updates are correctly wrapped. - if (jest.isMockFunction(setTimeout) && jest.getTimerCount() > 0) { - await act(async () => { - await jest.runOnlyPendingTimersAsync(); - }); - } - - // Wait for the initial render baseline (mount-time requests/async state). - await waitFor(() => { - const settledElement = - screen.queryByTestId('esDeprecationsContent') ?? - screen.queryByTestId('noDeprecationsPrompt') ?? - screen.queryByTestId('deprecationsPageLoadingError'); - - expect(settledElement).not.toBeNull(); - }); - - // Some providers start in LoadingState.Loading and then resolve status asynchronously. - // If "Loading status…" is present, wait for it to disappear so those mount-time state updates - // happen while RTL is actively awaiting a UI boundary (prevents act warnings). - const hasLoadingStatus = screen.queryAllByText('Loading status…').length > 0; - if (hasLoadingStatus) { - await waitFor(() => { - expect(screen.queryAllByText('Loading status…')).toHaveLength(0); - }); - } - - // Some rows fetch "status" asynchronously on mount (IndexStatusProvider/DataStreamMigrationStatusProvider). - // Wait for table rows to exist first, then wait for the visible boundary that indicates those - // mount-time async updates have settled (prevents React act() warnings). - const hasTable = screen.queryByTestId('esDeprecationsContent') !== null; - if (hasTable) { - await screen.findAllByTestId('deprecationTableRow'); - await waitFor(() => { - const indexResolutionCells = screen.queryAllByTestId('reindexTableCell-correctiveAction'); - const dataStreamResolutionCells = screen.queryAllByTestId( - 'dataStreamReindexTableCell-correctiveAction' - ); - - const resolutionCells = [...indexResolutionCells, ...dataStreamResolutionCells]; - - // If there are no resolution cells on the page, there's nothing to wait for. - if (resolutionCells.length === 0) { - return; - } - - // Ensure async status providers have settled (no "Loading status…" placeholders remaining). - for (const cell of resolutionCells) { - expect(cell).not.toHaveTextContent('Loading status…'); - } - }); - } -}; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/index_settings_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/index_settings_deprecation_flyout.test.ts deleted file mode 100644 index 40cb427247366..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/index_settings_deprecation_flyout.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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 '@testing-library/jest-dom'; -import { fireEvent, screen, waitFor, within } from '@testing-library/react'; - -import { setupEnvironment } from '../helpers/setup_environment'; -import { setupElasticsearchPage } from './es_deprecations.helpers'; -import { - esDeprecationsMockResponse, - MOCK_SNAPSHOT_ID, - MOCK_JOB_ID, - MOCK_REINDEX_DEPRECATION, -} from './mocked_responses'; - -describe('Index settings deprecation flyout', () => { - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - let httpSetup: ReturnType['httpSetup']; - const indexSettingDeprecation = esDeprecationsMockResponse.migrationsDeprecations[1]; - - const openFlyout = async () => { - fireEvent.click(screen.getAllByTestId('deprecation-indexSetting')[0]); - return await screen.findByTestId('indexSettingsDetails'); - }; - - beforeEach(() => { - const mockEnvironment = setupEnvironment(); - httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; - httpSetup = mockEnvironment.httpSetup; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse); - httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'idle', - }); - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'foo', - reindexName: 'reindexed-foo', - aliases: [], - }, - }); - }); - - it('renders a flyout with deprecation details', async () => { - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - expect(flyout).toBeInTheDocument(); - expect(within(flyout).getByTestId('flyoutTitle')).toHaveTextContent( - indexSettingDeprecation.message - ); - expect( - (within(flyout).getByTestId('documentationLink') as HTMLAnchorElement).getAttribute('href') - ).toBe(indexSettingDeprecation.url); - expect(within(flyout).getByTestId('removeSettingsPrompt')).toBeInTheDocument(); - }); - - it('removes deprecated index settings', async () => { - httpRequestsMockHelpers.setUpdateIndexSettingsResponse(indexSettingDeprecation.index!, { - acknowledged: true, - }); - - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - expect(within(flyout).getByTestId('warningDeprecationBadge')).toBeInTheDocument(); - - fireEvent.click(within(flyout).getByTestId('deleteSettingsButton')); - - expect(httpSetup.post).toHaveBeenLastCalledWith( - `/api/upgrade_assistant/${indexSettingDeprecation.index!}/index_settings`, - expect.anything() - ); - - // Verify the "Resolution" column of the table is updated - await waitFor(() => { - expect(screen.getAllByTestId('indexSettingsResolutionStatusCell')[0]).toHaveTextContent( - 'Deprecated settings removed' - ); - }); - - await waitFor(() => { - expect(screen.queryByTestId('indexSettingsDetails')).toBeNull(); - }); - - // Reopen the flyout - const reopenedFlyout = await openFlyout(); - - // Verify prompt to remove setting no longer displays - expect(within(reopenedFlyout).queryByTestId('removeSettingsPrompt')).toBeNull(); - // Verify the action button no longer displays - expect(within(reopenedFlyout).queryByTestId('deleteSettingsButton')).toBeNull(); - // Verify the badge got marked as resolved - expect(within(reopenedFlyout).getByTestId('resolvedDeprecationBadge')).toBeInTheDocument(); - }); - - it('handles failure', async () => { - const error = { - statusCode: 500, - error: 'Remove index settings error', - message: 'Remove index settings error', - }; - - httpRequestsMockHelpers.setUpdateIndexSettingsResponse( - indexSettingDeprecation.index!, - undefined, - error - ); - - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - fireEvent.click(within(flyout).getByTestId('deleteSettingsButton')); - - expect(httpSetup.post).toHaveBeenLastCalledWith( - `/api/upgrade_assistant/${indexSettingDeprecation.index!}/index_settings`, - expect.anything() - ); - - // Verify the "Resolution" column of the table is updated - await waitFor(() => { - expect(screen.getAllByTestId('indexSettingsResolutionStatusCell')[0]).toHaveTextContent( - 'Settings removal failed' - ); - }); - - await waitFor(() => { - expect(screen.queryByTestId('indexSettingsDetails')).toBeNull(); - }); - - // Reopen the flyout - const reopenedFlyout = await openFlyout(); - - // Verify the flyout shows an error message - expect(within(reopenedFlyout).getByTestId('deleteSettingsError')).toHaveTextContent( - 'Error deleting index settings' - ); - // Verify the remove settings button text changes - expect(within(reopenedFlyout).getByTestId('deleteSettingsButton')).toHaveTextContent( - 'Retry removing deprecated settings' - ); - }); -}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/ml_snapshots_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/ml_snapshots_deprecation_flyout.test.ts deleted file mode 100644 index 5aaeaa9a36b8e..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/ml_snapshots_deprecation_flyout.test.ts +++ /dev/null @@ -1,291 +0,0 @@ -/* - * 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 '@testing-library/jest-dom'; -import { fireEvent, screen, waitFor, within } from '@testing-library/react'; - -import type { MlAction } from '../../../common/types'; -import { setupEnvironment } from '../helpers/setup_environment'; -import { setupElasticsearchPage } from './es_deprecations.helpers'; -import { esDeprecationsMockResponse, MOCK_SNAPSHOT_ID, MOCK_JOB_ID } from './mocked_responses'; - -describe('Machine learning deprecation flyout', () => { - let esDeprecationsResponse: typeof esDeprecationsMockResponse; - let mlDeprecation: (typeof esDeprecationsMockResponse.migrationsDeprecations)[number]; - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - let httpSetup: ReturnType['httpSetup']; - - const openFlyout = async () => { - fireEvent.click(screen.getAllByTestId('deprecation-mlSnapshot')[0]); - return await screen.findByTestId('mlSnapshotDetails'); - }; - - beforeEach(() => { - const mockEnvironment = setupEnvironment(); - httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; - httpSetup = mockEnvironment.httpSetup; - - // Tests can mutate the loaded deprecations in-memory; ensure isolation by cloning per test. - esDeprecationsResponse = JSON.parse(JSON.stringify(esDeprecationsMockResponse)); - mlDeprecation = esDeprecationsResponse.migrationsDeprecations[0]; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsResponse); - httpRequestsMockHelpers.setLoadMlUpgradeModeResponse({ mlUpgradeModeEnabled: false }); - httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'idle', - }); - httpRequestsMockHelpers.setReindexStatusResponse('reindex_index', { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'foo', - reindexName: 'reindexed-foo', - aliases: [], - }, - }); - }); - - test('renders a flyout with deprecation details', async () => { - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - expect(flyout).toBeInTheDocument(); - expect(within(flyout).getByTestId('flyoutTitle')).toHaveTextContent( - 'Upgrade or delete model snapshot' - ); - expect( - (within(flyout).getByTestId('documentationLink') as HTMLAnchorElement).getAttribute('href') - ).toBe(mlDeprecation.url); - }); - - describe('upgrade snapshots', () => { - it('successfully upgrades snapshots', async () => { - httpRequestsMockHelpers.setUpgradeMlSnapshotResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'in_progress', - }); - - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - expect(within(flyout).getByTestId('criticalDeprecationBadge')).toBeInTheDocument(); - expect(within(flyout).getByTestId('upgradeSnapshotButton')).toHaveTextContent('Upgrade'); - - fireEvent.click(within(flyout).getByTestId('upgradeSnapshotButton')); - - // After the upgrade request starts, the status polling should resolve as complete. - httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'complete', - }); - - // First, we expect a POST request to upgrade the snapshot - await waitFor(() => { - expect(httpSetup.post).toHaveBeenLastCalledWith( - '/api/upgrade_assistant/ml_snapshots', - expect.anything() - ); - }); - - // Next, we expect a GET request to check the status of the upgrade - await waitFor(() => { - expect(httpSetup.get).toHaveBeenLastCalledWith( - `/api/upgrade_assistant/ml_snapshots/${MOCK_JOB_ID}/${MOCK_SNAPSHOT_ID}`, - expect.anything() - ); - }); - - // Verify the "Resolution" column of the table is updated - await waitFor(() => { - expect(screen.getAllByTestId('mlActionResolutionCell')[0]).toHaveTextContent( - 'Upgrade complete' - ); - }); - - await waitFor(() => { - expect(screen.queryByTestId('mlSnapshotDetails')).toBeNull(); - }); - - // Reopen the flyout - const reopenedFlyout = await openFlyout(); - - // Flyout actions should be hidden if deprecation was resolved - expect(within(reopenedFlyout).queryByTestId('upgradeSnapshotButton')).toBeNull(); - expect(within(reopenedFlyout).queryByTestId('deleteSnapshotButton')).toBeNull(); - // Badge should be updated in flyout title - expect(within(reopenedFlyout).getByTestId('resolvedDeprecationBadge')).toBeInTheDocument(); - }); - - it('handles upgrade failure', async () => { - const error = { - statusCode: 500, - error: 'Upgrade snapshot error', - message: 'Upgrade snapshot error', - }; - - httpRequestsMockHelpers.setUpgradeMlSnapshotResponse(undefined, error); - httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'error', - error, - }); - - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - fireEvent.click(within(flyout).getByTestId('upgradeSnapshotButton')); - - await waitFor(() => { - expect(httpSetup.post).toHaveBeenLastCalledWith( - '/api/upgrade_assistant/ml_snapshots', - expect.anything() - ); - }); - - // Verify the "Resolution" column of the table is updated - await waitFor(() => { - expect(screen.getAllByTestId('mlActionResolutionCell')[0]).toHaveTextContent( - 'Upgrade failed' - ); - }); - - await waitFor(() => { - expect(screen.queryByTestId('mlSnapshotDetails')).toBeNull(); - }); - - // Reopen the flyout - const reopenedFlyout = await openFlyout(); - - // Verify the flyout shows an error message - expect(within(reopenedFlyout).getByTestId('resolveSnapshotError')).toHaveTextContent( - 'Error upgrading snapshot' - ); - // Verify the upgrade button text changes - expect(within(reopenedFlyout).getByTestId('upgradeSnapshotButton')).toHaveTextContent( - 'Retry upgrade' - ); - }); - - it('Disables actions if ml_upgrade_mode is enabled', async () => { - httpRequestsMockHelpers.setLoadMlUpgradeModeResponse({ - mlUpgradeModeEnabled: true, - }); - - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - // Shows an error callout with a docs link - expect(within(flyout).getByTestId('mlUpgradeModeEnabledError')).toBeInTheDocument(); - expect(within(flyout).getByTestId('setUpgradeModeDocsLink')).toBeInTheDocument(); - // Flyout actions should be hidden - expect(within(flyout).queryByTestId('upgradeSnapshotButton')).toBeNull(); - expect(within(flyout).queryByTestId('deleteSnapshotButton')).toBeNull(); - }); - }); - - describe('delete snapshots', () => { - it('successfully deletes snapshots', async () => { - const jobId = (mlDeprecation.correctiveAction! as MlAction).jobId; - const snapshotId = (mlDeprecation.correctiveAction! as MlAction).snapshotId; - httpRequestsMockHelpers.setDeleteMlSnapshotResponse(jobId, snapshotId, { - acknowledged: true, - }); - - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - expect(within(flyout).getByTestId('criticalDeprecationBadge')).toBeInTheDocument(); - expect(within(flyout).getByTestId('deleteSnapshotButton')).toHaveTextContent('Delete'); - - fireEvent.click(within(flyout).getByTestId('deleteSnapshotButton')); - - await waitFor(() => { - expect(httpSetup.delete).toHaveBeenLastCalledWith( - `/api/upgrade_assistant/ml_snapshots/${jobId}/${snapshotId}`, - expect.anything() - ); - }); - - // Verify the "Resolution" column of the table is updated - await waitFor(() => { - expect(screen.getAllByTestId('mlActionResolutionCell')[0]).toHaveTextContent( - 'Deletion complete' - ); - }); - - await waitFor(() => { - expect(screen.queryByTestId('mlSnapshotDetails')).toBeNull(); - }); - - // Reopen the flyout - const reopenedFlyout = await openFlyout(); - - // Flyout actions should be hidden if deprecation was resolved - expect(within(reopenedFlyout).queryByTestId('upgradeSnapshotButton')).toBeNull(); - expect(within(reopenedFlyout).queryByTestId('deleteSnapshotButton')).toBeNull(); - // Badge should be updated in flyout title - expect(within(reopenedFlyout).getByTestId('resolvedDeprecationBadge')).toBeInTheDocument(); - }); - - it('handles delete failure', async () => { - const error = { - statusCode: 500, - error: 'Upgrade snapshot error', - message: 'Upgrade snapshot error', - }; - - const jobId = (mlDeprecation.correctiveAction! as MlAction).jobId; - const snapshotId = (mlDeprecation.correctiveAction! as MlAction).snapshotId; - httpRequestsMockHelpers.setDeleteMlSnapshotResponse(jobId, snapshotId, undefined, error); - - await setupElasticsearchPage(httpSetup); - const flyout = await openFlyout(); - - fireEvent.click(within(flyout).getByTestId('deleteSnapshotButton')); - - await waitFor(() => { - expect(httpSetup.delete).toHaveBeenLastCalledWith( - `/api/upgrade_assistant/ml_snapshots/${jobId}/${snapshotId}`, - expect.anything() - ); - }); - - // Verify the "Resolution" column of the table is updated - await waitFor(() => { - expect(screen.getAllByTestId('mlActionResolutionCell')[0]).toHaveTextContent( - 'Deletion failed' - ); - }); - - await waitFor(() => { - expect(screen.queryByTestId('mlSnapshotDetails')).toBeNull(); - }); - - // Reopen the flyout - const reopenedFlyout = await openFlyout(); - - // Verify the flyout shows an error message - expect(within(reopenedFlyout).getByTestId('resolveSnapshotError')).toHaveTextContent( - 'Error deleting snapshot' - ); - // Verify the upgrade button text changes - expect(within(reopenedFlyout).getByTestId('deleteSnapshotButton')).toHaveTextContent( - 'Retry delete' - ); - }); - }); -}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/mocked_responses.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/mocked_responses.ts deleted file mode 100644 index 7bcbcc24963e1..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/mocked_responses.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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 type { ESUpgradeStatus, EnrichedDeprecationInfo } from '../../../common/types'; - -export const MOCK_SNAPSHOT_ID = '1'; -export const MOCK_JOB_ID = 'deprecation_check_job'; - -export const MOCK_ML_DEPRECATION: EnrichedDeprecationInfo = { - level: 'critical', - resolveDuringUpgrade: false, - type: 'ml_settings', - message: 'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded', - details: - 'model snapshot [%s] for job [%s] supports minimum version [%s] and needs to be at least [%s]', - url: 'doc_url', - correctiveAction: { - type: 'mlSnapshot', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - }, -}; - -export const MOCK_REINDEX_DEPRECATION: EnrichedDeprecationInfo = { - level: 'critical', - resolveDuringUpgrade: false, - type: 'index_settings', - message: 'Index created before 7.0', - details: 'deprecation details', - url: 'doc_url', - index: 'reindex_index', - correctiveAction: { - type: 'reindex', - metadata: { - isClosedIndex: false, - isFrozenIndex: false, - isInDataStream: false, - }, - }, -}; - -const MOCK_INDEX_SETTING_DEPRECATION: EnrichedDeprecationInfo = { - level: 'warning', - resolveDuringUpgrade: false, - type: 'index_settings', - message: 'Setting [index.routing.allocation.include._tier] is deprecated', - details: 'deprecation details', - url: 'doc_url', - index: 'my_index', - correctiveAction: { - type: 'indexSetting', - deprecatedSettings: ['index.routing.allocation.include._tier'], - }, -}; - -const MOCK_CLUSTER_SETTING_DEPRECATION: EnrichedDeprecationInfo = { - level: 'warning', - resolveDuringUpgrade: false, - type: 'cluster_settings', - message: 'Setting [cluster.routing.allocation.require._tier] is deprecated', - details: 'deprecation details', - url: 'doc_url', - correctiveAction: { - type: 'clusterSetting', - deprecatedSettings: ['cluster.routing.allocation.require._tier'], - }, -}; - -const MOCK_DEFAULT_DEPRECATION: EnrichedDeprecationInfo = { - level: 'warning', - resolveDuringUpgrade: false, - type: 'index_settings', - message: 'multi-fields within multi-fields', - details: 'deprecation details', - url: 'doc_url', - index: 'nested_multi-fields', -}; - -export const MOCK_DS_DEPRECATION: EnrichedDeprecationInfo = { - index: 'reindex_or_readonly_ds', - type: 'data_streams', - details: 'Data stream deprecation details', - message: 'Outdated data stream', - url: 'doc_url', - level: 'critical', - resolveDuringUpgrade: false, - correctiveAction: { - type: 'dataStream', - metadata: { - excludedActions: [], - reindexRequired: true, - totalBackingIndices: 1, - indicesRequiringUpgradeCount: 1, - indicesRequiringUpgrade: ['ds_index'], - ignoredIndicesRequiringUpgrade: [], - ignoredIndicesRequiringUpgradeCount: 0, - }, - }, -}; - -export const MOCK_DS_DEPRECATION_REINDEX: EnrichedDeprecationInfo = { - index: 'reindex_ds', - type: 'data_streams', - details: 'Data stream deprecation details', - message: 'Outdated data stream', - url: 'doc_url', - level: 'critical', - resolveDuringUpgrade: false, - correctiveAction: { - type: 'dataStream', - metadata: { - excludedActions: ['readOnly'], - reindexRequired: true, - totalBackingIndices: 1, - indicesRequiringUpgradeCount: 1, - indicesRequiringUpgrade: ['ds_index'], - ignoredIndicesRequiringUpgrade: [], - ignoredIndicesRequiringUpgradeCount: 0, - }, - }, -}; - -export const MOCK_DS_DEPRECATION_READ_ONLY: EnrichedDeprecationInfo = { - index: 'readonly_ds', - type: 'data_streams', - details: 'Data stream deprecation details', - message: 'Outdated data stream', - url: 'doc_url', - level: 'critical', - resolveDuringUpgrade: false, - correctiveAction: { - type: 'dataStream', - metadata: { - excludedActions: ['reindex'], - reindexRequired: true, - totalBackingIndices: 1, - indicesRequiringUpgradeCount: 1, - indicesRequiringUpgrade: ['ds_index'], - ignoredIndicesRequiringUpgrade: [], - ignoredIndicesRequiringUpgradeCount: 0, - }, - }, -}; - -export const esDeprecationsMockResponse: ESUpgradeStatus = { - totalCriticalDeprecations: 2, - migrationsDeprecations: [ - MOCK_ML_DEPRECATION, - MOCK_INDEX_SETTING_DEPRECATION, - MOCK_DEFAULT_DEPRECATION, - MOCK_REINDEX_DEPRECATION, - MOCK_CLUSTER_SETTING_DEPRECATION, - MOCK_DS_DEPRECATION, - MOCK_DS_DEPRECATION_REINDEX, - MOCK_DS_DEPRECATION_READ_ONLY, - ], - totalCriticalHealthIssues: 0, - enrichedHealthIndicators: [], -}; - -// Useful for testing pagination where a large number of deprecations are needed -export const createEsDeprecationsMockResponse = ( - numDeprecationsPerType: number -): ESUpgradeStatus => { - const mlDeprecations: EnrichedDeprecationInfo[] = Array.from( - { - length: numDeprecationsPerType, - }, - () => MOCK_ML_DEPRECATION - ); - - const indexSettingsDeprecations: EnrichedDeprecationInfo[] = Array.from( - { - length: numDeprecationsPerType, - }, - () => MOCK_INDEX_SETTING_DEPRECATION - ); - - const reindexDeprecations: EnrichedDeprecationInfo[] = Array.from( - { - length: numDeprecationsPerType, - }, - () => MOCK_REINDEX_DEPRECATION - ); - - const defaultDeprecations: EnrichedDeprecationInfo[] = Array.from( - { - length: numDeprecationsPerType, - }, - () => MOCK_DEFAULT_DEPRECATION - ); - - const migrationsDeprecations: EnrichedDeprecationInfo[] = [ - ...defaultDeprecations, - ...reindexDeprecations, - ...indexSettingsDeprecations, - ...mlDeprecations, - ]; - - return { - totalCriticalDeprecations: mlDeprecations.length + reindexDeprecations.length, - migrationsDeprecations, - totalCriticalHealthIssues: esDeprecationsMockResponse.totalCriticalHealthIssues, - enrichedHealthIndicators: esDeprecationsMockResponse.enrichedHealthIndicators, - }; -}; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/readonly_index_modal.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/readonly_index_modal.test.ts deleted file mode 100644 index e8870f9fe1bfc..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/readonly_index_modal.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 '@testing-library/jest-dom'; -import { screen, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -import type { ReindexStatusResponse } from '@kbn/reindex-service-plugin/common'; -import { ReindexStep } from '@kbn/reindex-service-plugin/common'; -import { ReindexStatus } from '@kbn/upgrade-assistant-pkg-common'; -import { setupEnvironment } from '../helpers/setup_environment'; -import { setupElasticsearchPage } from './es_deprecations.helpers'; -import { - esDeprecationsMockResponse, - MOCK_SNAPSHOT_ID, - MOCK_JOB_ID, - MOCK_REINDEX_DEPRECATION, -} from './mocked_responses'; - -const defaultReindexStatusMeta: ReindexStatusResponse['meta'] = { - indexName: 'foo', - aliases: [], - isFrozen: false, - isReadonly: false, - isInDataStream: false, - isFollowerIndex: false, -}; - -describe('Readonly index modal', () => { - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - let httpSetup: ReturnType['httpSetup']; - let user: ReturnType; - - const openReadOnlyModal = async () => { - await user.click(screen.getAllByTestId('deprecation-reindex-readonly')[0]); - return await screen.findByTestId('updateIndexModal'); - }; - - beforeEach(() => { - user = userEvent.setup(); - const mockEnvironment = setupEnvironment(); - httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; - httpSetup = mockEnvironment.httpSetup; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse); - httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'idle', - }); - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'foo', - reindexName: 'reindexed-foo', - aliases: [], - }, - }); - httpRequestsMockHelpers.setLoadNodeDiskSpaceResponse([]); - }); - - describe('low disk space', () => { - it('renders a warning callout if nodes detected with low disk space', async () => { - httpRequestsMockHelpers.setLoadNodeDiskSpaceResponse([ - { - nodeId: '9OFkjpAKS_aPzJAuEOSg7w', - nodeName: 'MacBook-Pro.local', - available: '25%', - }, - ]); - - await setupElasticsearchPage(httpSetup); - const modal = await openReadOnlyModal(); - - expect(await within(modal).findByTestId('lowDiskSpaceCallout')).toHaveTextContent( - 'Nodes with low disk space' - ); - expect(within(modal).getAllByTestId('impactedNodeListItem')).toHaveLength(1); - expect(within(modal).getAllByTestId('impactedNodeListItem')[0]).toHaveTextContent( - 'MacBook-Pro.local (25% available)' - ); - }); - }); - - describe('readonly', () => { - it('renders a modal with index confirm step for read-only', async () => { - await setupElasticsearchPage(httpSetup); - const modal = await openReadOnlyModal(); - - expect(modal).toBeInTheDocument(); - expect(within(modal).getByTestId('updateIndexModalTitle')).toHaveTextContent( - 'Set index to read-only' - ); - }); - - it('shows success state when marking as readonly an index that has failed to reindex', async () => { - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: { - status: ReindexStatus.failed, - lastCompletedStep: ReindexStep.reindexCompleted, - reindexTaskPercComplete: 1, - }, - warnings: [], - hasRequiredPrivileges: true, - meta: defaultReindexStatusMeta, - }); - - await setupElasticsearchPage(httpSetup); - const modal = await openReadOnlyModal(); - - await user.click(within(modal).getByTestId('startIndexReadonlyButton')); - - expect(await screen.findByTestId('updateIndexModalTitle')).toHaveTextContent( - 'Setting index to read-only' - ); - - expect(screen.getByTestId('stepProgressStep')).toHaveTextContent( - 'Setting foo index to read-only.' - ); - }); - }); - - describe('follower index', () => { - it('displays follower index callout and only shows mark as read-only button when index is a follower index', async () => { - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - ...defaultReindexStatusMeta, - isFollowerIndex: true, - }, - }); - - await setupElasticsearchPage(httpSetup); - const modal = await openReadOnlyModal(); - - // Verify follower index callout is displayed - expect(within(modal).getByTestId('followerIndexCallout')).toBeInTheDocument(); - - // Verify only mark as read-only button is available (no reindex button) - expect(within(modal).getByTestId('startIndexReadonlyButton')).toBeInTheDocument(); - expect(within(modal).queryByTestId('startReindexingButton')).toBeNull(); - }); - }); -}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts deleted file mode 100644 index 28e4a5cbfd536..0000000000000 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts +++ /dev/null @@ -1,245 +0,0 @@ -/* - * 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 '@testing-library/jest-dom'; -import { fireEvent, screen, waitFor, within } from '@testing-library/react'; - -import type { ReindexStatusResponse } from '@kbn/reindex-service-plugin/common'; -import { ReindexStep } from '@kbn/reindex-service-plugin/common'; -import { ReindexStatus } from '@kbn/upgrade-assistant-pkg-common'; -import { setupEnvironment } from '../helpers/setup_environment'; -import { setupElasticsearchPage } from './es_deprecations.helpers'; -import { - esDeprecationsMockResponse, - MOCK_SNAPSHOT_ID, - MOCK_JOB_ID, - MOCK_REINDEX_DEPRECATION, -} from './mocked_responses'; - -const defaultReindexStatusMeta: ReindexStatusResponse['meta'] = { - indexName: 'foo', - aliases: [], - isFrozen: false, - isReadonly: false, - isInDataStream: false, - isFollowerIndex: false, -}; - -describe('Reindex deprecation flyout', () => { - afterEach(async () => { - const flyout = screen.queryByTestId('reindexDetails'); - if (flyout) { - const closeButton = within(flyout).getByRole('button', { name: 'Close' }); - fireEvent.click(closeButton); - await waitFor(() => { - expect(screen.queryByTestId('reindexDetails')).toBeNull(); - }); - } - }); - - let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; - let httpSetup: ReturnType['httpSetup']; - - const openReindexFlyout = async () => { - fireEvent.click(screen.getAllByTestId('deprecation-reindex-reindex')[0]); - return await screen.findByTestId('reindexDetails'); - }; - - const proceedToReindexProgress = async () => { - fireEvent.click(screen.getByTestId('startReindexingButton')); - - const warningCheckbox = screen.queryByTestId('warninStepCheckbox'); - if (warningCheckbox) { - fireEvent.click(within(warningCheckbox).getByRole('checkbox')); - - await waitFor(() => { - expect(screen.getByTestId('startReindexingButton')).toBeEnabled(); - }); - - fireEvent.click(screen.getByTestId('startReindexingButton')); - } - }; - - beforeEach(async () => { - const mockEnvironment = setupEnvironment(); - httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; - httpSetup = mockEnvironment.httpSetup; - - httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse); - httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({ - nodeId: 'my_node', - snapshotId: MOCK_SNAPSHOT_ID, - jobId: MOCK_JOB_ID, - status: 'idle', - }); - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: null, - warnings: [], - hasRequiredPrivileges: true, - meta: { - indexName: 'foo', - reindexName: 'reindexed-foo', - aliases: [], - }, - }); - httpRequestsMockHelpers.setLoadNodeDiskSpaceResponse([]); - - await setupElasticsearchPage(httpSetup); - }); - - it('renders error callout when reindex fails', async () => { - httpRequestsMockHelpers.setStartReindexingResponse(MOCK_REINDEX_DEPRECATION.index!, undefined, { - statusCode: 404, - message: 'no such index [test]', - }); - - const flyout = await openReindexFlyout(); - await proceedToReindexProgress(); - - expect(flyout).toBeInTheDocument(); - expect(await within(flyout).findByTestId('reindexingFailedCallout')).toBeInTheDocument(); - }); - - it('renders error callout when fetch status fails', async () => { - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, undefined, { - statusCode: 404, - message: 'no such index [test]', - }); - - const flyout = await openReindexFlyout(); - await proceedToReindexProgress(); - - expect(flyout).toBeInTheDocument(); - expect(await within(flyout).findByTestId('fetchFailedCallout')).toBeInTheDocument(); - }); - - describe('reindexing progress', () => { - it('renders a flyout with index confirm step for reindex', async () => { - const reindexDeprecation = esDeprecationsMockResponse.migrationsDeprecations[3]; - const flyout = await openReindexFlyout(); - - expect(flyout).toBeInTheDocument(); - expect(within(flyout).getByTestId('flyoutTitle')).toHaveTextContent( - `Reindex ${reindexDeprecation.index}` - ); - }); - it('has started but not yet reindexing documents', async () => { - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: { - status: ReindexStatus.inProgress, - lastCompletedStep: ReindexStep.readonly, - reindexTaskPercComplete: null, - }, - warnings: [], - hasRequiredPrivileges: true, - meta: defaultReindexStatusMeta, - }); - - await openReindexFlyout(); - await proceedToReindexProgress(); - - await waitFor(() => { - expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent( - 'Reindexing in progress… 5%' - ); - }); - expect(screen.queryByTestId('cancelReindexingDocumentsButton')).toBeNull(); - }); - - it('has started reindexing documents', async () => { - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: { - status: ReindexStatus.inProgress, - lastCompletedStep: ReindexStep.reindexStarted, - reindexTaskPercComplete: 0.25, - }, - warnings: [], - hasRequiredPrivileges: true, - meta: defaultReindexStatusMeta, - }); - - await openReindexFlyout(); - await proceedToReindexProgress(); - - await waitFor(() => { - expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent( - 'Reindexing in progress… 30%' - ); - }); - expect(await screen.findByTestId('cancelReindexingDocumentsButton')).toBeInTheDocument(); - }); - - it('has completed reindexing documents', async () => { - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: { - status: ReindexStatus.inProgress, - lastCompletedStep: ReindexStep.reindexCompleted, - reindexTaskPercComplete: 1, - }, - warnings: [], - hasRequiredPrivileges: true, - meta: defaultReindexStatusMeta, - }); - - await openReindexFlyout(); - await proceedToReindexProgress(); - - await waitFor(() => { - expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent( - 'Reindexing in progress… 90%' - ); - }); - expect(screen.queryByTestId('cancelReindexingDocumentsButton')).toBeNull(); - }); - - it('has completed', async () => { - httpRequestsMockHelpers.setReindexStatusResponse(MOCK_REINDEX_DEPRECATION.index!, { - reindexOp: { - status: ReindexStatus.completed, - lastCompletedStep: ReindexStep.aliasCreated, - reindexTaskPercComplete: 1, - }, - warnings: [], - hasRequiredPrivileges: true, - meta: defaultReindexStatusMeta, - }); - - await openReindexFlyout(); - await proceedToReindexProgress(); - - await waitFor(() => { - expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent( - 'Reindexing in progress… 95%' - ); - }); - expect(screen.queryByTestId('cancelReindexingDocumentsButton')).toBeNull(); - }); - }); - - describe('low disk space', () => { - it('renders a warning callout if nodes detected with low disk space', async () => { - httpRequestsMockHelpers.setLoadNodeDiskSpaceResponse([ - { - nodeId: '9OFkjpAKS_aPzJAuEOSg7w', - nodeName: 'MacBook-Pro.local', - available: '25%', - }, - ]); - - const flyout = await openReindexFlyout(); - await proceedToReindexProgress(); - - expect(await within(flyout).findByTestId('lowDiskSpaceCallout')).toHaveTextContent( - 'Nodes with low disk space' - ); - expect(within(flyout).getAllByTestId('impactedNodeListItem')).toHaveLength(1); - expect(within(flyout).getAllByTestId('impactedNodeListItem')[0]).toHaveTextContent( - 'MacBook-Pro.local (25% available)' - ); - }); - }); -}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/__fixtures__/es_deprecations.ts b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/__fixtures__/es_deprecations.ts new file mode 100644 index 0000000000000..f036590d7b02e --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/__fixtures__/es_deprecations.ts @@ -0,0 +1,160 @@ +/* + * 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 type { + ClusterSettingAction, + EnrichedDeprecationInfo, + ESUpgradeStatus, + IndexSettingAction, + MlAction, +} from '../../../../../common/types'; + +export const MOCK_SNAPSHOT_ID = '1'; +export const MOCK_JOB_ID = 'deprecation_check_job'; + +export const mockMlDeprecation = { + level: 'critical', + resolveDuringUpgrade: false, + type: 'ml_settings', + message: 'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded', + details: + 'model snapshot [%s] for job [%s] supports minimum version [%s] and needs to be at least [%s]', + url: 'doc_url', + correctiveAction: { + type: 'mlSnapshot', + snapshotId: MOCK_SNAPSHOT_ID, + jobId: MOCK_JOB_ID, + } satisfies MlAction, +} satisfies EnrichedDeprecationInfo; + +export const mockIndexSettingDeprecation = { + level: 'warning', + resolveDuringUpgrade: false, + type: 'index_settings', + message: 'Setting [index.routing.allocation.include._tier] is deprecated', + details: 'deprecation details', + url: 'doc_url', + index: 'my_index', + correctiveAction: { + type: 'indexSetting', + deprecatedSettings: ['index.routing.allocation.include._tier'], + } satisfies IndexSettingAction, +} satisfies EnrichedDeprecationInfo; + +export const mockClusterSettingDeprecation = { + level: 'warning', + resolveDuringUpgrade: false, + type: 'cluster_settings', + message: 'Setting [cluster.routing.allocation.require._tier] is deprecated', + details: 'deprecation details', + url: 'doc_url', + correctiveAction: { + type: 'clusterSetting', + deprecatedSettings: ['cluster.routing.allocation.require._tier'], + } satisfies ClusterSettingAction, +} satisfies EnrichedDeprecationInfo; + +export const mockDefaultDeprecation = { + level: 'warning', + resolveDuringUpgrade: false, + type: 'index_settings', + message: 'multi-fields within multi-fields', + details: 'deprecation details', + url: 'doc_url', + index: 'nested_multi-fields', +} satisfies EnrichedDeprecationInfo; + +export const mockReindexDeprecation = { + level: 'critical', + resolveDuringUpgrade: false, + type: 'index_settings', + message: 'Index created before 7.0', + details: 'deprecation details', + url: 'doc_url', + index: 'reindex_index', + correctiveAction: { + type: 'reindex', + metadata: { + isClosedIndex: false, + isFrozenIndex: false, + isInDataStream: false, + }, + }, +} satisfies EnrichedDeprecationInfo; + +export const mockEsDeprecations = { + totalCriticalDeprecations: 2, + migrationsDeprecations: [ + mockMlDeprecation, + mockIndexSettingDeprecation, + mockDefaultDeprecation, + mockReindexDeprecation, + mockClusterSettingDeprecation, + ], + totalCriticalHealthIssues: 0, + enrichedHealthIndicators: [], +} satisfies ESUpgradeStatus; + +export const createEsDeprecations = (numDeprecationsPerType: number): ESUpgradeStatus => { + const cloneCorrectiveAction = ( + correctiveAction: EnrichedDeprecationInfo['correctiveAction'] + ): EnrichedDeprecationInfo['correctiveAction'] => { + if (!correctiveAction) { + return undefined; + } + + switch (correctiveAction.type) { + case 'mlSnapshot': + return { ...correctiveAction }; + case 'indexSetting': + return { + ...correctiveAction, + deprecatedSettings: [...correctiveAction.deprecatedSettings], + }; + case 'clusterSetting': + return { + ...correctiveAction, + deprecatedSettings: [...correctiveAction.deprecatedSettings], + }; + case 'reindex': + return { + ...correctiveAction, + metadata: { ...correctiveAction.metadata }, + }; + default: + return { ...correctiveAction }; + } + }; + + const cloneDeprecation = (deprecation: EnrichedDeprecationInfo): EnrichedDeprecationInfo => ({ + ...deprecation, + correctiveAction: cloneCorrectiveAction(deprecation.correctiveAction), + }); + + const repeat = (deprecation: EnrichedDeprecationInfo) => + Array.from({ length: numDeprecationsPerType }, () => cloneDeprecation(deprecation)); + + const mlDeprecations = repeat(mockMlDeprecation); + const indexSettingsDeprecations = repeat(mockIndexSettingDeprecation); + const clusterSettingsDeprecations = repeat(mockClusterSettingDeprecation); + const reindexDeprecations = repeat(mockReindexDeprecation); + const defaultDeprecations = repeat(mockDefaultDeprecation); + const migrationsDeprecations = [ + ...defaultDeprecations, + ...reindexDeprecations, + ...indexSettingsDeprecations, + ...clusterSettingsDeprecations, + ...mlDeprecations, + ]; + + return { + totalCriticalDeprecations: migrationsDeprecations.filter((d) => d.level === 'critical').length, + migrationsDeprecations, + totalCriticalHealthIssues: mockEsDeprecations.totalCriticalHealthIssues, + enrichedHealthIndicators: mockEsDeprecations.enrichedHealthIndicators, + }; +}; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/common/nodes_low_disk_space.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/common/nodes_low_disk_space.test.tsx new file mode 100644 index 0000000000000..958920417946d --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/common/nodes_low_disk_space.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import '@testing-library/jest-dom'; +import { screen, within } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import { NodesLowSpaceCallOut } from './nodes_low_disk_space'; + +describe('NodesLowSpaceCallOut', () => { + it('renders impacted nodes list', () => { + renderWithI18n( + + ); + + const callout = screen.getByTestId('lowDiskSpaceCallout'); + expect(callout).toHaveTextContent('Nodes with low disk space'); + + const items = within(callout).getAllByTestId('impactedNodeListItem'); + expect(items).toHaveLength(2); + expect(items[0]).toHaveTextContent('node-1 (25% available)'); + expect(items[1]).toHaveTextContent('node-2 (10% available)'); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/flyout.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/flyout.test.tsx new file mode 100644 index 0000000000000..6e1fde4dc5e41 --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/flyout.test.tsx @@ -0,0 +1,120 @@ +/* + * 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 '@testing-library/jest-dom'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { ResponseError } from '../../../../../../common/types'; +import { mockClusterSettingDeprecation } from '../../__fixtures__/es_deprecations'; +import { RemoveClusterSettingsFlyout } from './flyout'; + +jest.mock('../../../../lib/ui_metric', () => { + const actual = jest.requireActual('../../../../lib/ui_metric'); + + return { + ...actual, + uiMetricService: { + ...actual.uiMetricService, + trackUiMetric: jest.fn(), + }, + }; +}); + +describe('RemoveClusterSettingsFlyout', () => { + const closeFlyout = jest.fn(); + const removeClusterSettings = jest.fn, [settings: string[]]>(); + + beforeEach(() => { + closeFlyout.mockClear(); + removeClusterSettings.mockClear(); + }); + + it('renders deprecation details and prompt', () => { + renderWithI18n( + + ); + + expect(screen.getByTestId('flyoutTitle')).toHaveTextContent( + mockClusterSettingDeprecation.message + ); + expect(screen.getByTestId('flyoutBody')).toHaveTextContent( + mockClusterSettingDeprecation.details + ); + expect(screen.getByTestId('documentationLink')).toHaveAttribute( + 'href', + mockClusterSettingDeprecation.url + ); + expect(screen.getByTestId('removeClusterSettingsPrompt')).toBeInTheDocument(); + expect(screen.getByTestId('deleteClusterSettingsButton')).toHaveTextContent( + 'Remove deprecated settings' + ); + }); + + it('calls removeClusterSettings with deprecated settings', () => { + renderWithI18n( + + ); + + fireEvent.click(screen.getByTestId('deleteClusterSettingsButton')); + expect(removeClusterSettings).toHaveBeenCalledWith([ + 'cluster.routing.allocation.require._tier', + ]); + }); + + it('hides prompt and action button when resolved', () => { + renderWithI18n( + + ); + + expect(screen.queryByTestId('removeClusterSettingsPrompt')).toBeNull(); + expect(screen.queryByTestId('deleteClusterSettingsButton')).toBeNull(); + expect(screen.getByTestId('resolvedDeprecationBadge')).toBeInTheDocument(); + }); + + it('shows error callout and changes button label on failure', () => { + const error: ResponseError = { + statusCode: 500, + message: 'Remove cluster settings error', + }; + + renderWithI18n( + + ); + + expect(screen.getByTestId('deleteClusterSettingsError')).toHaveTextContent( + 'Error deleting cluster settings' + ); + expect(screen.getByTestId('deleteClusterSettingsError')).toHaveTextContent( + 'Remove cluster settings error' + ); + expect(screen.getByTestId('deleteClusterSettingsButton')).toHaveTextContent( + 'Retry removing deprecated settings' + ); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/flyout.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/flyout.tsx index 53e76f3ed20b5..ce039e0733d60 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/flyout.tsx +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/flyout.tsx @@ -112,7 +112,7 @@ export const RemoveClusterSettingsFlyout = ({

{message}

- + {statusType === 'error' && ( <> Promise<{ error: ResponseError | null }>; + +const mockUpdateClusterSettings = jest.fn< + ReturnType, + Parameters +>(); + +const mockAddContent = jest.fn< + void, + [ + { + id: string; + Component: React.ComponentType; + props: { + removeClusterSettings: (settings: string[]) => Promise; + }; + } + ] +>(); +const mockRemoveContent = jest.fn(); + +jest.mock('../../../../app_context', () => { + const actual = jest.requireActual('../../../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + services: { + api: { + updateClusterSettings: (...args: Parameters) => + mockUpdateClusterSettings(...args), + }, + }, + }), + }; +}); + +jest.mock('../../../../../shared_imports', () => { + const actual = jest.requireActual('../../../../../shared_imports'); + + return { + ...actual, + GlobalFlyout: { + ...actual.GlobalFlyout, + useGlobalFlyout: () => ({ + addContent: (...args: Parameters) => mockAddContent(...args), + removeContent: (...args: Parameters) => + mockRemoveContent(...args), + }), + }, + }; +}); + +describe('ClusterSettingsTableRow', () => { + const rowFieldNames: DeprecationTableColumns[] = ['correctiveAction', 'actions']; + + beforeEach(() => { + mockUpdateClusterSettings.mockReset(); + mockAddContent.mockClear(); + mockRemoveContent.mockClear(); + }); + + const renderRow = () => + renderWithI18n( + + + + +
+ ); + + it('opens flyout when action link is clicked', async () => { + renderRow(); + + fireEvent.click(screen.getByTestId('deprecation-clusterSetting')); + + await waitFor(() => { + expect(mockAddContent).toHaveBeenCalled(); + }); + + expect(mockAddContent.mock.calls[0][0].id).toBe('clusterSettingsFlyout'); + }); + + it('updates resolution cell on successful settings removal', async () => { + mockUpdateClusterSettings.mockResolvedValue({ error: null }); + renderRow(); + + fireEvent.click(screen.getByTestId('deprecation-clusterSetting')); + await waitFor(() => { + expect(mockAddContent).toHaveBeenCalled(); + }); + + const { removeClusterSettings } = mockAddContent.mock.calls[0][0].props; + await act(async () => { + await removeClusterSettings(['cluster.routing.allocation.require._tier']); + }); + + expect(mockUpdateClusterSettings).toHaveBeenCalledWith([ + 'cluster.routing.allocation.require._tier', + ]); + expect(mockRemoveContent).toHaveBeenCalledWith('clusterSettingsFlyout'); + + const resolutionCell = within( + screen.getByTestId('clusterSettingsTableCell-correctiveAction') + ).getByTestId('clusterSettingsResolutionStatusCell'); + + await waitFor(() => { + expect(resolutionCell).toHaveTextContent('Deprecated settings removed'); + }); + }); + + it('updates resolution cell on failed settings removal', async () => { + const error: ResponseError = { statusCode: 500, message: 'Remove cluster settings error' }; + mockUpdateClusterSettings.mockResolvedValue({ error }); + renderRow(); + + fireEvent.click(screen.getByTestId('deprecation-clusterSetting')); + await waitFor(() => { + expect(mockAddContent).toHaveBeenCalled(); + }); + + const { removeClusterSettings } = mockAddContent.mock.calls[0][0].props; + await act(async () => { + await removeClusterSettings(['cluster.routing.allocation.require._tier']); + }); + + const resolutionCell = within( + screen.getByTestId('clusterSettingsTableCell-correctiveAction') + ).getByTestId('clusterSettingsResolutionStatusCell'); + + await waitFor(() => { + expect(resolutionCell).toHaveTextContent('Settings removal failed'); + }); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/table_row.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/table_row.tsx index 06cc8acf29585..a1366dabb3400 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/table_row.tsx +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/cluster_settings/table_row.tsx @@ -58,7 +58,9 @@ export const ClusterSettingsTableRow: React.FunctionComponent = ({ statusType: error ? 'error' : 'complete', details: error ?? undefined, }); - closeFlyout(); + if (!error) { + closeFlyout(); + } }, [api, closeFlyout] ); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/actions_table_cell.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/actions_table_cell.test.tsx new file mode 100644 index 0000000000000..20164ddada23e --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/actions_table_cell.test.tsx @@ -0,0 +1,199 @@ +/* + * 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 '@testing-library/jest-dom'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { DataStreamsAction } from '../../../../../../common/types'; +import { DataStreamMigrationStatus } from '../../../../../../common/types'; +import type { MigrationStateContext } from './context'; +import { DataStreamReindexActionsCell } from './actions_table_cell'; +import { LoadingState } from '../../../types'; + +const mockUseDataStreamMigrationContext = jest.fn(); + +jest.mock('./context', () => ({ + useDataStreamMigrationContext: () => mockUseDataStreamMigrationContext(), +})); + +const baseCorrectiveAction: DataStreamsAction = { + type: 'dataStream', + metadata: { + totalBackingIndices: 2, + indicesRequiringUpgradeCount: 1, + indicesRequiringUpgrade: ['.ds-my-ds-000001'], + ignoredIndicesRequiringUpgrade: [], + ignoredIndicesRequiringUpgradeCount: 0, + reindexRequired: true, + }, +}; + +const createContext = ({ + status, + resolutionType, +}: { + status: DataStreamMigrationStatus; + resolutionType?: 'reindex' | 'readonly'; +}): MigrationStateContext => ({ + loadDataStreamMetadata: jest.fn, []>(), + initMigration: jest.fn(), + startReindex: jest.fn, []>(), + cancelReindex: jest.fn, []>(), + startReadonly: jest.fn, []>(), + cancelReadonly: jest.fn, []>(), + migrationState: { + loadingState: LoadingState.Success, + status, + resolutionType, + taskPercComplete: null, + errorMessage: null, + meta: null, + hasRequiredPrivileges: true, + }, +}); + +describe('DataStreamReindexActionsCell', () => { + const mockOpenFlyout = jest.fn(); + const mockOpenModal = jest.fn(); + + beforeEach(() => { + mockUseDataStreamMigrationContext.mockReset(); + mockOpenFlyout.mockClear(); + mockOpenModal.mockClear(); + }); + + it('displays both read-only and reindex actions when both are valid', () => { + mockUseDataStreamMigrationContext.mockReturnValue( + createContext({ + status: DataStreamMigrationStatus.notStarted, + }) + ); + + renderWithI18n( + + ); + + expect(screen.getByTestId('deprecation-dataStream-reindex')).toBeInTheDocument(); + expect(screen.getByTestId('deprecation-dataStream-readonly')).toBeInTheDocument(); + }); + + it('only displays reindex action if read-only is excluded', () => { + const correctiveAction: DataStreamsAction = { + ...baseCorrectiveAction, + metadata: { ...baseCorrectiveAction.metadata, excludedActions: ['readOnly'] }, + }; + + mockUseDataStreamMigrationContext.mockReturnValue( + createContext({ + status: DataStreamMigrationStatus.notStarted, + }) + ); + + renderWithI18n( + + ); + + expect(screen.getByTestId('deprecation-dataStream-reindex')).toBeInTheDocument(); + expect(screen.queryByTestId('deprecation-dataStream-readonly')).toBeNull(); + }); + + it('only displays read-only action if reindex is excluded', () => { + const correctiveAction: DataStreamsAction = { + ...baseCorrectiveAction, + metadata: { ...baseCorrectiveAction.metadata, excludedActions: ['reindex'] }, + }; + + mockUseDataStreamMigrationContext.mockReturnValue( + createContext({ + status: DataStreamMigrationStatus.notStarted, + }) + ); + + renderWithI18n( + + ); + + expect(screen.queryByTestId('deprecation-dataStream-reindex')).toBeNull(); + expect(screen.getByTestId('deprecation-dataStream-readonly')).toBeInTheDocument(); + }); + + it('only displays reindex action when reindexing is in progress', () => { + mockUseDataStreamMigrationContext.mockReturnValue( + createContext({ + status: DataStreamMigrationStatus.inProgress, + resolutionType: 'reindex', + }) + ); + + renderWithI18n( + + ); + + expect(screen.getByTestId('deprecation-dataStream-reindex')).toBeInTheDocument(); + expect(screen.queryByTestId('deprecation-dataStream-readonly')).toBeNull(); + }); + + it('only displays read-only action when setting read-only is in progress', () => { + mockUseDataStreamMigrationContext.mockReturnValue( + createContext({ + status: DataStreamMigrationStatus.inProgress, + resolutionType: 'readonly', + }) + ); + + renderWithI18n( + + ); + + expect(screen.queryByTestId('deprecation-dataStream-reindex')).toBeNull(); + expect(screen.getByTestId('deprecation-dataStream-readonly')).toBeInTheDocument(); + }); + + it('calls open handlers on click', () => { + mockUseDataStreamMigrationContext.mockReturnValue( + createContext({ + status: DataStreamMigrationStatus.notStarted, + }) + ); + + renderWithI18n( + + ); + + fireEvent.click(screen.getByTestId('deprecation-dataStream-reindex')); + expect(mockOpenFlyout).toHaveBeenCalledTimes(1); + + fireEvent.click(screen.getByTestId('deprecation-dataStream-readonly')); + expect(mockOpenModal).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/container.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/container.test.tsx new file mode 100644 index 0000000000000..5d4a10d63cee2 --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/container.test.tsx @@ -0,0 +1,131 @@ +/* + * 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 '@testing-library/jest-dom'; +import { screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { EnrichedDeprecationInfo } from '../../../../../../../common/types'; +import { DataStreamMigrationStatus } from '../../../../../../../common/types'; +import { LoadingState } from '../../../../types'; +import type { MigrationState } from '../use_migration_state'; +import { DataStreamReindexFlyout } from './container'; + +jest.mock('../../../../../app_context', () => { + const actual = jest.requireActual('../../../../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + services: { + api: { + useLoadNodeDiskSpace: () => ({ data: [] }), + }, + core: { + docLinks: { + links: { upgradeAssistant: { dataStreamReindex: 'https://example.invalid' } }, + }, + }, + }, + }), + }; +}); + +jest.mock('../use_migration_step', () => ({ + useMigrationStep: () => ['confirm', jest.fn()] as const, +})); + +jest.mock('./steps/confirm', () => ({ + ConfirmMigrationReindexFlyoutStep: ({ + lastIndexCreationDateFormatted, + }: { + lastIndexCreationDateFormatted: string; + }) =>
{lastIndexCreationDateFormatted}
, + ConfirmMigrationReadonlyFlyoutStep: () =>
, +})); + +jest.mock('./steps/checklist', () => ({ + ChecklistFlyoutStep: () =>
, +})); + +jest.mock('./steps/completed', () => ({ + MigrationCompletedFlyoutStep: () =>
, +})); + +jest.mock('../../../common/initializing_step', () => ({ + InitializingStep: () =>
, +})); + +const mockDeprecation: EnrichedDeprecationInfo = { + type: 'data_streams', + level: 'warning', + resolveDuringUpgrade: false, + url: 'doc_url', + message: 'Data stream needs to be reindexed or set to read-only', + index: 'my-data-stream', + correctiveAction: { + type: 'dataStream', + metadata: { + totalBackingIndices: 2, + indicesRequiringUpgradeCount: 1, + indicesRequiringUpgrade: ['.ds-my-data-stream-000001'], + ignoredIndicesRequiringUpgrade: [], + ignoredIndicesRequiringUpgradeCount: 0, + reindexRequired: true, + }, + }, +}; + +const createMigrationState = (): MigrationState => ({ + loadingState: LoadingState.Success, + status: DataStreamMigrationStatus.notStarted, + taskPercComplete: null, + errorMessage: null, + resolutionType: 'reindex', + meta: { + dataStreamName: 'my-data-stream', + documentationUrl: 'https://example.invalid/docs', + lastIndexRequiringUpgradeCreationDate: 1700000000000, + indicesRequiringUpgradeCount: 1, + indicesRequiringUpgrade: ['.ds-my-data-stream-000001'], + allIndices: ['.ds-my-data-stream-000001', '.ds-my-data-stream-000002'], + allIndicesCount: 2, + indicesRequiringUpgradeDocsCount: 10, + indicesRequiringUpgradeDocsSize: 1024, + }, +}); + +describe('DataStreamReindexFlyout', () => { + it('renders formatted metadata in the flyout header', () => { + const migrationState = createMigrationState(); + + renderWithI18n( + , []>()} + migrationState={migrationState} + initMigration={jest.fn()} + startReindex={jest.fn, []>()} + cancelReindex={jest.fn, []>()} + startReadonly={jest.fn, []>()} + cancelReadonly={jest.fn, []>()} + /> + ); + + expect(screen.getByTestId('flyoutTitle')).toHaveTextContent('my-data-stream'); + const lastIndexCreationDateFormatted = screen.getByTestId('confirmMigrationStep').textContent; + expect(lastIndexCreationDateFormatted).not.toBeNull(); + expect(screen.getByTestId('dataStreamLastIndexCreationDate')).toHaveTextContent( + lastIndexCreationDateFormatted! + ); + expect(screen.getByTestId('dataStreamSize')).toHaveTextContent(/1\s?KB/); + expect(screen.getByTestId('dataStreamDocumentCount')).toHaveTextContent('10'); + expect(screen.getByTestId('confirmMigrationStep')).toHaveTextContent(/\b20\d{2}\b/); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/checklist/checklist_reindex_step.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/checklist/checklist_reindex_step.test.tsx new file mode 100644 index 0000000000000..a8035bdc8f746 --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/checklist/checklist_reindex_step.test.tsx @@ -0,0 +1,124 @@ +/* + * 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 '@testing-library/jest-dom'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import { DataStreamMigrationStatus } from '../../../../../../../../../common/types'; +import { LoadingState } from '../../../../../../types'; +import type { MigrationState } from '../../../use_migration_state'; +import { ChecklistFlyoutStep } from './checklist_reindex_step'; + +jest.mock('../../../../../../../app_context', () => { + const actual = jest.requireActual('../../../../../../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + services: { + api: { + useLoadNodeDiskSpace: () => ({ data: [] }), + }, + }, + }), + }; +}); + +jest.mock('../../../../../common/nodes_low_disk_space', () => ({ + NodesLowSpaceCallOut: () =>
, +})); + +jest.mock('./callouts', () => ({ + FetchFailedCallout: ({ hasFetchFailed }: { hasFetchFailed: boolean }) => ( +
{String(hasFetchFailed)}
+ ), + NoPrivilegesCallout: () =>
, +})); + +jest.mock('./progress', () => ({ + MigrationProgress: () =>
, +})); + +const createMigrationState = (overrides: Partial): MigrationState => ({ + loadingState: LoadingState.Success, + status: DataStreamMigrationStatus.notStarted, + taskPercComplete: null, + errorMessage: null, + meta: null, + hasRequiredPrivileges: true, + resolutionType: 'reindex', + taskStatus: undefined, + cancelLoadingState: undefined, + migrationWarnings: undefined, + ...overrides, +}); + +const renderChecklistFlyout = ({ + migrationState, + executeAction = jest.fn(), + cancelAction = jest.fn(), + dataStreamName = 'my-ds', +}: { + migrationState: MigrationState; + executeAction?: () => void; + cancelAction?: () => void; + dataStreamName?: string; +}) => { + renderWithI18n( + + ); +}; + +describe('ChecklistFlyoutStep (data streams)', () => { + it('renders fetch-failed callout and hides main action button on fetchFailed', () => { + renderChecklistFlyout({ + migrationState: createMigrationState({ + status: DataStreamMigrationStatus.fetchFailed, + errorMessage: 'fetch failed', + }), + }); + + expect(screen.getByTestId('dataStreamMigrationChecklistFlyout')).toBeInTheDocument(); + expect(screen.getByTestId('fetchFailedCallout')).toHaveTextContent('true'); + expect(screen.queryByTestId('startDataStreamMigrationButton')).toBeNull(); + }); + + it('shows try-again button on failed status', () => { + renderChecklistFlyout({ + migrationState: createMigrationState({ + status: DataStreamMigrationStatus.failed, + errorMessage: 'failed', + }), + }); + + expect(screen.getByTestId('fetchFailedCallout')).toHaveTextContent('false'); + expect(screen.getByTestId('startDataStreamMigrationButton')).toHaveTextContent('Try again'); + }); + + it('shows cancel button when migration is in progress', () => { + const cancelAction = jest.fn(); + + renderChecklistFlyout({ + migrationState: createMigrationState({ + status: DataStreamMigrationStatus.inProgress, + }), + cancelAction, + }); + + expect(screen.getByTestId('startDataStreamMigrationButton')).toBeDisabled(); + fireEvent.click(screen.getByTestId('cancelDataStreamMigrationButton')); + expect(cancelAction).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/checklist/progress.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/checklist/progress.test.tsx new file mode 100644 index 0000000000000..8fcbedabc5d2b --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/checklist/progress.test.tsx @@ -0,0 +1,83 @@ +/* + * 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 '@testing-library/jest-dom'; +import { screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { DataStreamProgressDetails } from '../../../../../../../../../common/types'; +import { DataStreamMigrationStatus } from '../../../../../../../../../common/types'; +import { LoadingState } from '../../../../../../types'; +import type { MigrationState } from '../../../use_migration_state'; +import { MigrationProgress } from './progress'; + +jest.mock('./progress_title', () => ({ + MigrateDocumentsStepTitle: () => , +})); + +describe('MigrationProgress', () => { + it('renders the per-status counts for reindex resolution type', () => { + const taskStatus: DataStreamProgressDetails = { + startTimeMs: 1700000000000, + successCount: 2, + errorsCount: 1, + inProgressCount: 3, + pendingCount: 4, + }; + + const migrationState: MigrationState = { + loadingState: LoadingState.Success, + status: DataStreamMigrationStatus.inProgress, + taskPercComplete: 50, + errorMessage: null, + resolutionType: 'reindex', + meta: null, + taskStatus, + }; + + renderWithI18n(); + + expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent('logs-foo'); + expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent( + 'Reindexing logs-foo in progress…' + ); + expect(screen.getByText('1 Index failed to get reindexed.')).toBeInTheDocument(); + expect(screen.getByText('2 Indices successfully reindexed.')).toBeInTheDocument(); + expect(screen.getByText('3 Indices currently getting reindexed.')).toBeInTheDocument(); + expect(screen.getByText('4 Indices waiting to start.')).toBeInTheDocument(); + }); + + it('renders the per-status counts for readonly resolution type', () => { + const taskStatus: DataStreamProgressDetails = { + startTimeMs: 1700000000000, + successCount: 1, + errorsCount: 0, + inProgressCount: 0, + pendingCount: 1, + }; + + const migrationState: MigrationState = { + loadingState: LoadingState.Success, + status: DataStreamMigrationStatus.inProgress, + taskPercComplete: 20, + errorMessage: null, + resolutionType: 'readonly', + meta: null, + taskStatus, + }; + + renderWithI18n(); + + expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent( + 'Setting to read-only logs-bar in progress…' + ); + expect(screen.getByText('1 Index successfully set to read-only.')).toBeInTheDocument(); + expect(screen.getByText('0 Indices currently getting set to read-only.')).toBeInTheDocument(); + expect(screen.getByText('1 Index waiting to start.')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/confirm/readonly_confirm_step.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/confirm/readonly_confirm_step.test.tsx new file mode 100644 index 0000000000000..fda7c94c74e59 --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/confirm/readonly_confirm_step.test.tsx @@ -0,0 +1,139 @@ +/* + * 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 '@testing-library/jest-dom'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { WarningCheckboxProps } from './warnings'; +import { ConfirmMigrationReadonlyFlyoutStep } from './readonly_confirm_step'; +import { + createWarnings, + mockLoadNodeDiskSpace, + mockMeta, +} from './test_utils/confirm_step_test_scaffold'; + +jest.mock('../../../../../../../app_context', () => { + const actual = jest.requireActual('../../../../../../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + services: { + api: { + useLoadNodeDiskSpace: () => mockLoadNodeDiskSpace(), + }, + core: { + docLinks: { + links: { + upgradeAssistant: { + dataStreamReindex: 'https://example.invalid/reindex-docs', + }, + }, + }, + }, + }, + }), + }; +}); + +jest.mock('./warnings', () => ({ + IncompatibleDataInDataStreamWarningCheckbox: ({ + isChecked, + onChange, + id, + }: WarningCheckboxProps) => ( + + ), + AffectExistingSetupsWarningCheckbox: ({ isChecked, onChange, id }: WarningCheckboxProps) => ( + + ), +})); + +jest.mock('../../../../../common/nodes_low_disk_space', () => ({ + NodesLowSpaceCallOut: () =>
, +})); + +const mockWarnings = createWarnings('readonly'); + +describe('ConfirmMigrationReadonlyFlyoutStep', () => { + beforeEach(() => { + mockLoadNodeDiskSpace.mockReset(); + mockLoadNodeDiskSpace.mockReturnValue({ data: [] }); + }); + + it('blocks start until all warning checkboxes are checked', () => { + const startAction = jest.fn(); + + renderWithI18n( + + ); + + expect(screen.getByTestId('readonlyDataStreamModalTitle')).toHaveTextContent( + 'Set data stream to read-only' + ); + expect(screen.getByTestId('dataStreamMigrationWarningsCallout')).toBeInTheDocument(); + expect(screen.getByTestId('readOnlyDsWarningCallout')).toBeInTheDocument(); + + const startButton = screen.getByTestId('startActionButton'); + expect(startButton).toBeDisabled(); + + const checkboxes = screen.getAllByTestId(/^migrationWarning-\d+$/); + expect(checkboxes).toHaveLength(2); + checkboxes.forEach((checkboxEl) => fireEvent.click(checkboxEl)); + + expect(startButton).not.toBeDisabled(); + fireEvent.click(startButton); + expect(startAction).toHaveBeenCalled(); + }); + + it('renders nodes low disk space callout when nodes are present', () => { + mockLoadNodeDiskSpace.mockReturnValue({ + data: [ + { + nodeName: 'node-1', + availableBytes: 1024, + lowDiskSpace: true, + shards: ['shard-1'], + }, + ], + }); + + renderWithI18n( + + ); + + expect(screen.getByTestId('nodesLowDiskSpaceCallout')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/confirm/reindex_confirm_step.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/confirm/reindex_confirm_step.test.tsx new file mode 100644 index 0000000000000..a861e325794cf --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/confirm/reindex_confirm_step.test.tsx @@ -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 React from 'react'; +import '@testing-library/jest-dom'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { WarningCheckboxProps } from './warnings'; +import { ConfirmMigrationReindexFlyoutStep } from './reindex_confirm_step'; +import { + createWarnings, + mockLoadNodeDiskSpace, + mockMeta, +} from './test_utils/confirm_step_test_scaffold'; + +jest.mock('../../../../../../../app_context', () => { + const actual = jest.requireActual('../../../../../../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + services: { + api: { + useLoadNodeDiskSpace: () => mockLoadNodeDiskSpace(), + }, + core: { + docLinks: { + links: { + upgradeAssistant: { + dataStreamReindex: 'https://example.invalid/reindex-docs', + }, + }, + }, + }, + }, + }), + }; +}); + +jest.mock('./warnings', () => ({ + IncompatibleDataInDataStreamWarningCheckbox: ({ + isChecked, + onChange, + id, + }: WarningCheckboxProps) => ( + + ), + AffectExistingSetupsWarningCheckbox: ({ isChecked, onChange, id }: WarningCheckboxProps) => ( + + ), +})); + +jest.mock('../../../../../common/nodes_low_disk_space', () => ({ + NodesLowSpaceCallOut: () =>
, +})); + +const mockWarnings = createWarnings('reindex'); + +describe('ConfirmMigrationReindexFlyoutStep', () => { + beforeEach(() => { + mockLoadNodeDiskSpace.mockReset(); + mockLoadNodeDiskSpace.mockReturnValue({ data: [] }); + }); + + it('blocks start until all warning checkboxes are checked', () => { + const startAction = jest.fn(); + + renderWithI18n( + + ); + + expect(screen.getByTestId('dataStreamMigrationWarningsCallout')).toBeInTheDocument(); + expect(screen.getByTestId('reindexDsWarningCallout')).toBeInTheDocument(); + + const startButton = screen.getByTestId('startActionButton'); + expect(startButton).toBeDisabled(); + + const checkboxes = screen.getAllByTestId(/^migrationWarning-\d+$/); + expect(checkboxes).toHaveLength(2); + checkboxes.forEach((checkboxEl) => fireEvent.click(checkboxEl)); + + expect(startButton).not.toBeDisabled(); + fireEvent.click(startButton); + expect(startAction).toHaveBeenCalled(); + }); + + it('renders nodes low disk space callout when nodes are present', () => { + mockLoadNodeDiskSpace.mockReturnValue({ + data: [ + { + nodeName: 'node-1', + availableBytes: 1024, + lowDiskSpace: true, + shards: ['shard-1'], + }, + ], + }); + + renderWithI18n( + + ); + + expect(screen.getByTestId('nodesLowDiskSpaceCallout')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/confirm/test_utils/confirm_step_test_scaffold.ts b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/confirm/test_utils/confirm_step_test_scaffold.ts new file mode 100644 index 0000000000000..65302cf33d22f --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/flyout/steps/confirm/test_utils/confirm_step_test_scaffold.ts @@ -0,0 +1,42 @@ +/* + * 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 type { + DataStreamMetadata, + DataStreamMigrationWarning, +} from '../../../../../../../../../../common/types'; + +export const mockLoadNodeDiskSpace = jest.fn< + { + data: Array<{ + nodeName: string; + availableBytes: number; + lowDiskSpace: boolean; + shards: string[]; + }>; + }, + [] +>(); + +export const mockMeta: DataStreamMetadata = { + dataStreamName: 'my-data-stream', + documentationUrl: 'https://example.invalid/meta-docs', + lastIndexRequiringUpgradeCreationDate: 1700000000000, + allIndices: ['.ds-my-data-stream-000001', '.ds-my-data-stream-000002'], + allIndicesCount: 2, + indicesRequiringUpgradeCount: 1, + indicesRequiringUpgrade: ['.ds-my-data-stream-000001'], + indicesRequiringUpgradeDocsCount: 10, + indicesRequiringUpgradeDocsSize: 1024, +}; + +export const createWarnings = ( + resolutionType: 'readonly' | 'reindex' +): DataStreamMigrationWarning[] => [ + { warningType: 'incompatibleDataStream', meta: {}, resolutionType }, + { warningType: 'affectExistingSetups', meta: {}, resolutionType }, +]; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/resolution_table_cell.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/resolution_table_cell.test.tsx new file mode 100644 index 0000000000000..bd5c17fe2f9a1 --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/data_streams/resolution_table_cell.test.tsx @@ -0,0 +1,81 @@ +/* + * 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 '@testing-library/jest-dom'; +import { screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { DataStreamsAction } from '../../../../../../common/types'; +import { DataStreamMigrationStatus } from '../../../../../../common/types'; +import type { MigrationStateContext } from './context'; +import { DataStreamReindexResolutionCell } from './resolution_table_cell'; +import { LoadingState } from '../../../types'; + +const mockUseDataStreamMigrationContext = jest.fn(); + +jest.mock('./context', () => ({ + useDataStreamMigrationContext: () => mockUseDataStreamMigrationContext(), +})); + +const baseCorrectiveAction: DataStreamsAction = { + type: 'dataStream', + metadata: { + totalBackingIndices: 2, + indicesRequiringUpgradeCount: 1, + indicesRequiringUpgrade: ['.ds-my-ds-000001'], + ignoredIndicesRequiringUpgrade: [], + ignoredIndicesRequiringUpgradeCount: 0, + reindexRequired: true, + }, +}; + +describe('DataStreamReindexResolutionCell', () => { + beforeEach(() => { + mockUseDataStreamMigrationContext.mockReset(); + }); + + const makeDefaultMigrationContextMock = (): MigrationStateContext => ({ + loadDataStreamMetadata: jest.fn, []>(), + initMigration: jest.fn(), + startReindex: jest.fn, []>(), + cancelReindex: jest.fn, []>(), + startReadonly: jest.fn, []>(), + cancelReadonly: jest.fn, []>(), + migrationState: { + loadingState: LoadingState.Success, + status: DataStreamMigrationStatus.notStarted, + taskPercComplete: null, + errorMessage: null, + meta: null, + }, + }); + + it('recommends set to read-only by default', () => { + mockUseDataStreamMigrationContext.mockReturnValue(makeDefaultMigrationContextMock()); + + renderWithI18n(); + + expect(screen.getByText('Recommended: set to read-only')).toBeInTheDocument(); + }); + + it('recommends reindex when read-only is excluded', () => { + const correctiveAction: DataStreamsAction = { + ...baseCorrectiveAction, + metadata: { + ...baseCorrectiveAction.metadata, + excludedActions: ['readOnly'], + }, + }; + + mockUseDataStreamMigrationContext.mockReturnValue(makeDefaultMigrationContextMock()); + + renderWithI18n(); + + expect(screen.getByText('Recommended: reindex')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/flyout.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/flyout.test.tsx new file mode 100644 index 0000000000000..3203be91cdefc --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/flyout.test.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 '@testing-library/jest-dom'; +import { screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import { mockDefaultDeprecation } from '../../__fixtures__/es_deprecations'; +import { DefaultDeprecationFlyout } from './flyout'; + +describe('DefaultDeprecationFlyout', () => { + it('renders deprecation details', () => { + renderWithI18n( + + ); + + expect(screen.getByTestId('flyoutTitle')).toHaveTextContent(mockDefaultDeprecation.message); + expect(screen.getByTestId('documentationLink')).toHaveAttribute( + 'href', + mockDefaultDeprecation.url + ); + expect(screen.getByTestId('flyoutDescription')).toHaveTextContent( + String(mockDefaultDeprecation.index) + ); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/table_row.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/table_row.test.tsx new file mode 100644 index 0000000000000..bc52c147f764b --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/default/table_row.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import '@testing-library/jest-dom'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { DeprecationTableColumns } from '../../../types'; +import { mockDefaultDeprecation } from '../../__fixtures__/es_deprecations'; +import { DefaultTableRow } from './table_row'; + +const mockAddContent = jest.fn(); +const mockRemoveContent = jest.fn(); + +jest.mock('../../../../../shared_imports', () => { + const actual = jest.requireActual('../../../../../shared_imports'); + + return { + ...actual, + GlobalFlyout: { + ...actual.GlobalFlyout, + useGlobalFlyout: () => ({ + addContent: (...args: Parameters) => mockAddContent(...args), + removeContent: (...args: Parameters) => + mockRemoveContent(...args), + }), + }, + }; +}); + +describe('DefaultTableRow', () => { + const rowFieldNames: DeprecationTableColumns[] = ['message', 'actions']; + + beforeEach(() => { + mockAddContent.mockClear(); + mockRemoveContent.mockClear(); + }); + + it('opens flyout when action icon is clicked', async () => { + renderWithI18n( + + + + +
+ ); + + fireEvent.click(screen.getByTestId('deprecation-default')); + + await waitFor(() => { + expect(mockAddContent).toHaveBeenCalled(); + }); + + expect(mockAddContent.mock.calls[0][0].id).toBe('deprecationDetails'); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/flyout.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/flyout.test.tsx new file mode 100644 index 0000000000000..35b0da93bfbcc --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/flyout.test.tsx @@ -0,0 +1,117 @@ +/* + * 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 '@testing-library/jest-dom'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { ResponseError } from '../../../../../../common/types'; +import { mockIndexSettingDeprecation } from '../../__fixtures__/es_deprecations'; +import { RemoveIndexSettingsFlyout } from './flyout'; + +jest.mock('../../../../lib/ui_metric', () => { + const actual = jest.requireActual('../../../../lib/ui_metric'); + + return { + ...actual, + uiMetricService: { + ...actual.uiMetricService, + trackUiMetric: jest.fn(), + }, + }; +}); + +describe('RemoveIndexSettingsFlyout', () => { + const closeFlyout = jest.fn(); + const removeIndexSettings = jest.fn, [index: string, settings: string[]]>(); + + beforeEach(() => { + closeFlyout.mockClear(); + removeIndexSettings.mockClear(); + }); + + it('renders deprecation details and prompt', () => { + renderWithI18n( + + ); + + expect(screen.getByTestId('flyoutTitle')).toHaveTextContent( + mockIndexSettingDeprecation.message + ); + expect(screen.getByTestId('documentationLink')).toHaveAttribute( + 'href', + mockIndexSettingDeprecation.url + ); + expect(screen.getByTestId('removeSettingsPrompt')).toBeInTheDocument(); + expect(screen.getByTestId('deleteSettingsButton')).toHaveTextContent( + 'Remove deprecated settings' + ); + }); + + it('calls removeIndexSettings with index and settings', () => { + renderWithI18n( + + ); + + fireEvent.click(screen.getByTestId('deleteSettingsButton')); + expect(removeIndexSettings).toHaveBeenCalledWith('my_index', [ + 'index.routing.allocation.include._tier', + ]); + }); + + it('hides prompt and action button when resolved', () => { + renderWithI18n( + + ); + + expect(screen.queryByTestId('removeSettingsPrompt')).toBeNull(); + expect(screen.queryByTestId('deleteSettingsButton')).toBeNull(); + expect(screen.getByTestId('resolvedDeprecationBadge')).toBeInTheDocument(); + }); + + it('shows error callout and changes button label on failure', () => { + const error: ResponseError = { + statusCode: 500, + message: 'Remove index settings error', + }; + + renderWithI18n( + + ); + + expect(screen.getByTestId('deleteSettingsError')).toHaveTextContent( + 'Error deleting index settings' + ); + expect(screen.getByTestId('deleteSettingsError')).toHaveTextContent( + 'Remove index settings error' + ); + expect(screen.getByTestId('deleteSettingsButton')).toHaveTextContent( + 'Retry removing deprecated settings' + ); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.test.tsx new file mode 100644 index 0000000000000..e87fcb74ffed4 --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.test.tsx @@ -0,0 +1,160 @@ +/* + * 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 '@testing-library/jest-dom'; +import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { ResponseError } from '../../../../../../common/types'; +import type { DeprecationTableColumns } from '../../../types'; +import { mockIndexSettingDeprecation } from '../../__fixtures__/es_deprecations'; +import { IndexSettingsTableRow } from './table_row'; + +type UpdateIndexSettings = ( + index: string, + settings: string[] +) => Promise<{ error: ResponseError | null }>; + +const mockUpdateIndexSettings = jest.fn< + ReturnType, + Parameters +>(); + +const mockAddContent = jest.fn< + void, + [ + { + id: string; + Component: React.ComponentType; + props: { + removeIndexSettings: (index: string, settings: string[]) => Promise; + }; + } + ] +>(); +const mockRemoveContent = jest.fn(); + +jest.mock('../../../../app_context', () => { + const actual = jest.requireActual('../../../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + services: { + api: { + updateIndexSettings: (...args: Parameters) => + mockUpdateIndexSettings(...args), + }, + }, + }), + }; +}); + +jest.mock('../../../../../shared_imports', () => { + const actual = jest.requireActual('../../../../../shared_imports'); + + return { + ...actual, + GlobalFlyout: { + ...actual.GlobalFlyout, + useGlobalFlyout: () => ({ + addContent: (...args: Parameters) => mockAddContent(...args), + removeContent: (...args: Parameters) => + mockRemoveContent(...args), + }), + }, + }; +}); + +describe('IndexSettingsTableRow', () => { + const rowFieldNames: DeprecationTableColumns[] = ['correctiveAction', 'actions']; + + beforeEach(() => { + mockUpdateIndexSettings.mockReset(); + mockAddContent.mockClear(); + mockRemoveContent.mockClear(); + }); + + const renderRow = () => + renderWithI18n( + + + + +
+ ); + + it('opens flyout when action link is clicked', async () => { + renderRow(); + + fireEvent.click(screen.getByTestId('deprecation-indexSetting')); + + await waitFor(() => { + expect(mockAddContent).toHaveBeenCalled(); + }); + + expect(mockAddContent.mock.calls[0][0].id).toBe('indexSettingsFlyout'); + }); + + it('updates resolution cell on successful settings removal', async () => { + mockUpdateIndexSettings.mockResolvedValue({ error: null }); + renderRow(); + + fireEvent.click(screen.getByTestId('deprecation-indexSetting')); + + await waitFor(() => { + expect(mockAddContent).toHaveBeenCalled(); + }); + + const { removeIndexSettings } = mockAddContent.mock.calls[0][0].props; + await act(async () => { + await removeIndexSettings('my_index', ['index.routing.allocation.include._tier']); + }); + + expect(mockUpdateIndexSettings).toHaveBeenCalledWith('my_index', [ + 'index.routing.allocation.include._tier', + ]); + expect(mockRemoveContent).toHaveBeenCalledWith('indexSettingsFlyout'); + + const resolutionCell = within( + screen.getByTestId('indexSettingsTableCell-correctiveAction') + ).getByTestId('indexSettingsResolutionStatusCell'); + await waitFor(() => { + expect(resolutionCell).toHaveTextContent('Deprecated settings removed'); + }); + }); + + it('updates resolution cell on failed settings removal', async () => { + const error: ResponseError = { statusCode: 500, message: 'Remove index settings error' }; + mockUpdateIndexSettings.mockResolvedValue({ error }); + renderRow(); + + fireEvent.click(screen.getByTestId('deprecation-indexSetting')); + await waitFor(() => { + expect(mockAddContent).toHaveBeenCalled(); + }); + + const { removeIndexSettings } = mockAddContent.mock.calls[0][0].props; + await act(async () => { + await removeIndexSettings('my_index', ['index.routing.allocation.include._tier']); + }); + + expect(mockRemoveContent).not.toHaveBeenCalled(); + + const resolutionCell = within( + screen.getByTestId('indexSettingsTableCell-correctiveAction') + ).getByTestId('indexSettingsResolutionStatusCell'); + await waitFor(() => { + expect(resolutionCell).toHaveTextContent('Settings removal failed'); + }); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.tsx index 2ae389c32df42..9177c7342560f 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.tsx +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/index_settings/table_row.tsx @@ -58,7 +58,9 @@ export const IndexSettingsTableRow: React.FunctionComponent = ({ statusType: error ? 'error' : 'complete', details: error ?? undefined, }); - closeFlyout(); + if (!error) { + closeFlyout(); + } }, [api, closeFlyout] ); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/actions_table_cell.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/actions_table_cell.test.tsx new file mode 100644 index 0000000000000..875b64351d74b --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/actions_table_cell.test.tsx @@ -0,0 +1,289 @@ +/* + * 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 '@testing-library/jest-dom'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { ReindexStatus } from '@kbn/upgrade-assistant-pkg-common'; + +import type { + EnrichedDeprecationInfo, + ReindexAction, + UnfreezeAction, +} from '../../../../../../common/types'; +import type { IndexStateContext } from './context'; +import { ReindexActionCell } from './actions_table_cell'; +import { + createIndexContext, + createReindexState, + createUpdateIndexState, +} from '../test_utils/helpers'; + +const mockUseIndexContext = jest.fn(); + +jest.mock('./context', () => ({ + useIndexContext: () => mockUseIndexContext(), +})); + +const baseDeprecation = { + level: 'critical', + resolveDuringUpgrade: false, + type: 'index_settings', + message: 'Index created before 7.0', + details: 'deprecation details', + url: 'doc_url', + index: 'test-index', +} satisfies Omit; + +const reindexDeprecation = { + ...baseDeprecation, + correctiveAction: { + type: 'reindex', + metadata: { + isClosedIndex: false, + isFrozenIndex: false, + isInDataStream: false, + }, + } satisfies ReindexAction, +} satisfies EnrichedDeprecationInfo; + +const unfreezeDeprecation = { + ...baseDeprecation, + correctiveAction: { + type: 'unfreeze', + metadata: { + isClosedIndex: false, + isFrozenIndex: true, + isInDataStream: false, + }, + } satisfies UnfreezeAction, +} satisfies EnrichedDeprecationInfo; + +describe('ReindexActionCell', () => { + const mockOpenFlyout = jest.fn(); + const mockOpenModal = jest.fn(); + const mockSetSelectedResolutionType = jest.fn(); + + beforeEach(() => { + mockUseIndexContext.mockReset(); + mockOpenFlyout.mockClear(); + mockOpenModal.mockClear(); + mockSetSelectedResolutionType.mockClear(); + }); + + it('displays reindex and unfreeze actions for frozen indices', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: unfreezeDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n( + + ); + + expect(screen.getByTestId('deprecation-unfreeze-reindex')).toBeInTheDocument(); + expect(screen.getByTestId('deprecation-unfreeze-unfreeze')).toBeInTheDocument(); + }); + + it('only displays reindex action if reindex is in progress (frozen indices)', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: unfreezeDeprecation, + reindexState: createReindexState({ status: ReindexStatus.inProgress }), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n( + + ); + + expect(screen.getByTestId('deprecation-unfreeze-reindex')).toBeInTheDocument(); + expect(screen.queryByTestId('deprecation-unfreeze-unfreeze')).toBeNull(); + }); + + it('only displays unfreeze action if unfreezing is in progress (frozen indices)', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: unfreezeDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState({ status: 'inProgress' }), + }) + ); + + renderWithI18n( + + ); + + expect(screen.queryByTestId('deprecation-unfreeze-reindex')).toBeNull(); + expect(screen.getByTestId('deprecation-unfreeze-unfreeze')).toBeInTheDocument(); + }); + + it('displays reindex and read-only actions when both are valid', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: reindexDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n( + + ); + + expect(screen.getByTestId('deprecation-reindex-reindex')).toBeInTheDocument(); + expect(screen.getByTestId('deprecation-reindex-readonly')).toBeInTheDocument(); + }); + + it('only displays read-only action if reindexing is excluded', () => { + const excludedReindexDeprecation: EnrichedDeprecationInfo = { + ...reindexDeprecation, + correctiveAction: { + ...(reindexDeprecation.correctiveAction as ReindexAction), + excludedActions: ['reindex'], + }, + }; + + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: excludedReindexDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n( + + ); + + expect(screen.queryByTestId('deprecation-reindex-reindex')).toBeNull(); + expect(screen.getByTestId('deprecation-reindex-readonly')).toBeInTheDocument(); + }); + + it('only displays read-only action if index is a follower index', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: reindexDeprecation, + reindexState: createReindexState({ + meta: { ...createReindexState().meta, isFollowerIndex: true }, + }), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n( + + ); + + expect(screen.queryByTestId('deprecation-reindex-reindex')).toBeNull(); + expect(screen.getByTestId('deprecation-reindex-readonly')).toBeInTheDocument(); + }); + + it('only displays reindex action if read-only is excluded', () => { + const excludedReadOnlyDeprecation: EnrichedDeprecationInfo = { + ...reindexDeprecation, + correctiveAction: { + ...(reindexDeprecation.correctiveAction as ReindexAction), + excludedActions: ['readOnly'], + }, + }; + + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: excludedReadOnlyDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n( + + ); + + expect(screen.getByTestId('deprecation-reindex-reindex')).toBeInTheDocument(); + expect(screen.queryByTestId('deprecation-reindex-readonly')).toBeNull(); + }); + + it('only displays read-only action when readonly update is in progress', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: reindexDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState({ status: 'inProgress' }), + }) + ); + + renderWithI18n( + + ); + + expect(screen.queryByTestId('deprecation-reindex-reindex')).toBeNull(); + expect(screen.getByTestId('deprecation-reindex-readonly')).toBeInTheDocument(); + }); + + it('calls open handlers and sets resolution type on click', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: reindexDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n( + + ); + + fireEvent.click(screen.getByTestId('deprecation-reindex-reindex')); + expect(mockOpenFlyout).toHaveBeenCalledTimes(1); + + fireEvent.click(screen.getByTestId('deprecation-reindex-readonly')); + expect(mockOpenModal).toHaveBeenCalledTimes(1); + expect(mockSetSelectedResolutionType).toHaveBeenCalledWith('readonly'); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/reindex/progress.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/reindex/progress.test.tsx index 12686fb677181..d01281e004c21 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/reindex/progress.test.tsx +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/reindex/progress.test.tsx @@ -13,32 +13,28 @@ import { renderWithI18n } from '@kbn/test-jest-helpers'; import { ReindexStep } from '@kbn/reindex-service-plugin/common'; import { ReindexStatus } from '@kbn/upgrade-assistant-pkg-common'; import { LoadingState } from '../../../../../../types'; -import type { ReindexState } from '../../../use_reindex'; import { ReindexProgress } from './progress'; +import { createReindexState } from '../../../../test_utils/helpers'; describe('ReindexProgress', () => { it('renders', () => { renderWithI18n( ); @@ -61,25 +57,22 @@ describe('ReindexProgress', () => { it('displays errors in the step that failed', () => { renderWithI18n( ); @@ -97,4 +90,110 @@ describe('ReindexProgress', () => { expect(stepContent!).toHaveTextContent('There was an error'); expect(stepContent!).toHaveTextContent('This is an error that happened on alias switch'); }); + + it('has started but not yet reindexing documents', () => { + renderWithI18n( + + ); + + expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent( + 'Reindexing in progress… 5%' + ); + expect(screen.queryByTestId('cancelReindexingDocumentsButton')).toBeNull(); + }); + + it('has started reindexing documents', () => { + renderWithI18n( + + ); + + expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent( + 'Reindexing in progress… 30%' + ); + expect(screen.getByTestId('cancelReindexingDocumentsButton')).toBeInTheDocument(); + }); + + it('has completed reindexing documents', () => { + renderWithI18n( + + ); + + expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent( + 'Reindexing in progress… 90%' + ); + expect(screen.queryByTestId('cancelReindexingDocumentsButton')).toBeNull(); + }); + + it('has completed', () => { + renderWithI18n( + + ); + + expect(screen.getByTestId('reindexChecklistTitle')).toHaveTextContent('Reindexing process'); + expect(screen.queryByTestId('cancelReindexingDocumentsButton')).toBeNull(); + }); }); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/reindex/reindex_step.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/reindex/reindex_step.test.tsx index cffd70208198a..3bd18edf5c472 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/reindex/reindex_step.test.tsx +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/reindex/reindex_step.test.tsx @@ -17,9 +17,11 @@ import { ReindexFlyoutStep } from './reindex_step'; import { renderWithI18n } from '@kbn/test-jest-helpers'; jest.mock('../../../../../../../app_context', () => { + const actual = jest.requireActual('../../../../../../../app_context'); const { docLinksServiceMock } = jest.requireActual('@kbn/core-doc-links-browser-mocks'); return { + ...actual, useAppContext: () => { return { services: { diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/warning/warning_step.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/warning/warning_step.test.tsx index 9bdcd2543f27d..ff68789c85162 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/warning/warning_step.test.tsx +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/warning/warning_step.test.tsx @@ -23,9 +23,11 @@ import { renderWithI18n } from '@kbn/test-jest-helpers'; const kibanaVersion = new SemVer('8.0.0'); jest.mock('../../../../../../../app_context', () => { + const actual = jest.requireActual('../../../../../../../app_context'); const { docLinksServiceMock } = jest.requireActual('@kbn/core-doc-links-browser-mocks'); return { + ...actual, useAppContext: () => { return { services: { diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/warning/warning_step_modal.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/warning/warning_step_modal.test.tsx index 0c86348835909..c0e57ede88d6c 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/warning/warning_step_modal.test.tsx +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/flyout/steps/warning/warning_step_modal.test.tsx @@ -9,53 +9,83 @@ import React from 'react'; import { screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import type { EnrichedDeprecationInfo } from '../../../../../../../../../common/types'; -import type { IndexWarning, IndexWarningType } from '@kbn/reindex-service-plugin/common'; +import type { IndexWarning } from '@kbn/reindex-service-plugin/common'; import { ReindexStatus } from '@kbn/upgrade-assistant-pkg-common'; +import { LoadingState } from '../../../../../../types'; import { renderWithI18n } from '@kbn/test-jest-helpers'; import { WarningModalStep } from './warning_step_modal'; // Mocks -jest.mock('../../../../../../../app_context', () => ({ - useAppContext: () => ({ - services: { - api: { - useLoadNodeDiskSpace: () => ({ data: [] }), - }, - core: { - docLinks: { links: {} }, +jest.mock('../../../../../../../app_context', () => { + const actual = jest.requireActual('../../../../../../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + services: { + api: { + useLoadNodeDiskSpace: () => ({ data: [] }), + }, + core: { + docLinks: { links: {} }, + }, }, - }, - }), -})); + }), + }; +}); jest.mock('./warning_step_checkbox', () => ({ - DeprecatedSettingWarningCheckbox: ({ isChecked, onChange, id }: any) => ( - + DeprecatedSettingWarningCheckbox: ({ + isChecked, + onChange, + id, + }: { + isChecked: boolean; + onChange: (event: React.ChangeEvent) => void; + id: string; + }) => ( + ), - ReplaceIndexWithAliasWarningCheckbox: ({ isChecked, onChange, id }: any) => ( - + ReplaceIndexWithAliasWarningCheckbox: ({ + isChecked, + onChange, + id, + }: { + isChecked: boolean; + onChange: (event: React.ChangeEvent) => void; + id: string; + }) => ( + ), - MakeIndexReadonlyWarningCheckbox: ({ isChecked, onChange, id }: any) => ( - + MakeIndexReadonlyWarningCheckbox: ({ + isChecked, + onChange, + id, + }: { + isChecked: boolean; + onChange: (event: React.ChangeEvent) => void; + id: string; + }) => ( + ), })); jest.mock('../callouts', () => ({ - FollowerIndexCallout: () =>
, - ESTransformsTargetCallout: () =>
, - MlAnomalyCallout: () =>
, - FetchFailedCallOut: ({ errorMessage }: any) => ( -
{errorMessage}
+ FollowerIndexCallout: () =>
, + ESTransformsTargetCallout: () =>
, + MlAnomalyCallout: () =>
, + FetchFailedCallOut: ({ errorMessage }: { errorMessage: string }) => ( +
{errorMessage}
), })); jest.mock('../../../../../common/nodes_low_disk_space', () => ({ - NodesLowSpaceCallOut: () =>
, + NodesLowSpaceCallOut: () =>
, })); -// Define a local mock ReindexState type for testing const mockReindexState = { status: ReindexStatus.inProgress, + loadingState: LoadingState.Success, meta: { indexName: 'test-index', reindexName: 'test-index-reindex', @@ -66,26 +96,25 @@ const mockReindexState = { isClosedIndex: false, isFollowerIndex: false, }, - errorMessage: '', - loadingState: 'idle' as any, // type assertion workaround for tests + errorMessage: null, reindexTaskPercComplete: null, // valid value for number | null }; const mockDeprecation: EnrichedDeprecationInfo = { - type: 'index' as any, + type: 'index_settings', level: 'warning', resolveDuringUpgrade: false, - url: '', + url: 'doc_url', message: 'Deprecation message', correctiveAction: undefined, }; const mockWarnings: IndexWarning[] = [ - { warningType: 'indexSetting' as IndexWarningType, meta: {}, flow: 'readonly' }, - { warningType: 'replaceIndexWithAlias' as IndexWarningType, meta: {}, flow: 'readonly' }, + { warningType: 'indexSetting', meta: {}, flow: 'readonly' }, + { warningType: 'replaceIndexWithAlias', meta: {}, flow: 'readonly' }, ]; -const baseProps = { +const getBaseProps = () => ({ closeModal: jest.fn(), confirm: jest.fn(), meta: { @@ -101,69 +130,60 @@ const baseProps = { deprecation: mockDeprecation, reindexState: mockReindexState, flow: 'readonly' as const, -}; +}); describe('WarningModalStep', () => { it('renders read-only modal and disables continue until all checkboxes are checked', () => { - const { container } = renderWithI18n(); + const props = getBaseProps(); + renderWithI18n(); expect(screen.getByText(/Set index to read-only/i)).toBeInTheDocument(); expect(screen.getByText(/Old indices can maintain compatibility/i)).toBeInTheDocument(); const continueBtn = screen.getByRole('button', { name: /Set to read-only/i }); expect(continueBtn).toBeDisabled(); // Check all checkboxes - const checkboxes = container.querySelectorAll( - 'input[type="checkbox"][data-testid^="reindexWarning-"]' - ); - checkboxes.forEach((checkbox) => { - fireEvent.click(checkbox); + const checkboxes = screen.getAllByTestId(/^reindexWarning-\d+$/); + checkboxes.forEach((checkboxEl) => { + fireEvent.click(checkboxEl); }); expect(continueBtn).not.toBeDisabled(); }); it('calls confirm when continue is clicked in read-only flow', () => { - const { container } = renderWithI18n(); - const checkboxes = container.querySelectorAll( - 'input[type="checkbox"][data-testid^="reindexWarning-"]' - ); - checkboxes.forEach((checkbox) => { - fireEvent.click(checkbox); + const props = getBaseProps(); + renderWithI18n(); + const checkboxes = screen.getAllByTestId(/^reindexWarning-\d+$/); + checkboxes.forEach((checkboxEl) => { + fireEvent.click(checkboxEl); }); const continueBtn = screen.getByRole('button', { name: /Set to read-only/i }); fireEvent.click(continueBtn); - expect(baseProps.confirm).toHaveBeenCalled(); + expect(props.confirm).toHaveBeenCalledTimes(1); }); it('renders unfreeze modal with correct title and button', () => { + const props = getBaseProps(); renderWithI18n( - + ); expect(screen.getByTestId('updateIndexModalTitle')).toHaveTextContent(/Unfreeze index/i); expect(screen.getByRole('button', { name: /Unfreeze index/i })).toBeInTheDocument(); }); it('calls confirm when continue is clicked in unfreeze flow', () => { + const props = getBaseProps(); renderWithI18n( - + ); const continueBtn = screen.getByRole('button', { name: /Unfreeze index/i }); fireEvent.click(continueBtn); - expect(baseProps.confirm).toHaveBeenCalled(); + expect(props.confirm).toHaveBeenCalledTimes(1); }); it('calls closeModal when cancel is clicked', () => { - renderWithI18n(); + const props = getBaseProps(); + renderWithI18n(); const cancelBtn = screen.getByRole('button', { name: /Cancel/i }); fireEvent.click(cancelBtn); - expect(baseProps.closeModal).toHaveBeenCalled(); + expect(props.closeModal).toHaveBeenCalledTimes(1); }); }); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/resolution_table_cell.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/resolution_table_cell.test.tsx new file mode 100644 index 0000000000000..e8d7af3f18f6a --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/indices/resolution_table_cell.test.tsx @@ -0,0 +1,205 @@ +/* + * 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 '@testing-library/jest-dom'; +import { screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { ReindexStatus } from '@kbn/upgrade-assistant-pkg-common'; + +import type { + EnrichedDeprecationInfo, + ReindexAction, + UnfreezeAction, +} from '../../../../../../common/types'; +import type { IndexStateContext } from './context'; +import { ReindexResolutionCell } from './resolution_table_cell'; +import { + createIndexContext, + createReindexState, + createUpdateIndexState, +} from '../test_utils/helpers'; + +const mockUseIndexContext = jest.fn(); + +jest.mock('./context', () => ({ + useIndexContext: () => mockUseIndexContext(), +})); + +const baseDeprecation = { + level: 'critical', + resolveDuringUpgrade: false, + type: 'index_settings', + message: 'Index created before 7.0', + details: 'deprecation details', + url: 'doc_url', + index: 'test-index', +} satisfies Omit; + +const reindexDeprecation = { + ...baseDeprecation, + correctiveAction: { + type: 'reindex', + metadata: { + isClosedIndex: false, + isFrozenIndex: false, + isInDataStream: false, + }, + } satisfies ReindexAction, +} satisfies EnrichedDeprecationInfo; + +const unfreezeDeprecation = { + ...baseDeprecation, + correctiveAction: { + type: 'unfreeze', + metadata: { + isClosedIndex: false, + isFrozenIndex: true, + isInDataStream: false, + }, + } satisfies UnfreezeAction, +} satisfies EnrichedDeprecationInfo; + +describe('ReindexResolutionCell', () => { + beforeEach(() => { + mockUseIndexContext.mockReset(); + }); + + it('recommends reindexing by default', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: reindexDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n(); + + expect(screen.getByText('Recommended: reindex')).toBeInTheDocument(); + }); + + it('recommends unfreeze for frozen indices', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: unfreezeDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n(); + + expect(screen.getByText('Recommended: unfreeze')).toBeInTheDocument(); + }); + + it('recommends set to read-only for follower indices', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: reindexDeprecation, + reindexState: createReindexState({ + meta: { ...createReindexState().meta, isFollowerIndex: true }, + }), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n(); + + expect(screen.getByText('Recommended: set to read-only')).toBeInTheDocument(); + }); + + it('recommends set to read-only for large indices', () => { + const largeIndexDeprecation: EnrichedDeprecationInfo = { + ...reindexDeprecation, + correctiveAction: { + ...(reindexDeprecation.correctiveAction as ReindexAction), + indexSizeInBytes: 1073741825, + }, + }; + + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: largeIndexDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n(); + + expect(screen.getByText('Recommended: set to read-only')).toBeInTheDocument(); + }); + + it('recommends reindexing if index is already read-only', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: reindexDeprecation, + reindexState: createReindexState({ + meta: { ...createReindexState().meta, isReadonly: true }, + }), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n(); + + expect(screen.getByText('Recommended: reindex')).toBeInTheDocument(); + }); + + it('recommends set to read-only if reindexing is excluded', () => { + const excludedReindexDeprecation: EnrichedDeprecationInfo = { + ...reindexDeprecation, + correctiveAction: { + ...(reindexDeprecation.correctiveAction as ReindexAction), + excludedActions: ['reindex'], + }, + }; + + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: excludedReindexDeprecation, + reindexState: createReindexState(), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n(); + + expect(screen.getByText('Recommended: set to read-only')).toBeInTheDocument(); + }); + + it('recommends manual fix if follower index is already read-only', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: reindexDeprecation, + reindexState: createReindexState({ + meta: { ...createReindexState().meta, isFollowerIndex: true, isReadonly: true }, + }), + updateIndexState: createUpdateIndexState(), + }) + ); + + renderWithI18n(); + + expect(screen.getByText('Resolve manually')).toBeInTheDocument(); + }); + + it('shows success state when readonly update completed after reindex failure', () => { + mockUseIndexContext.mockReturnValue( + createIndexContext({ + deprecation: reindexDeprecation, + reindexState: createReindexState({ status: ReindexStatus.failed }), + updateIndexState: createUpdateIndexState({ status: 'complete' }), + }) + ); + + renderWithI18n(); + + expect(screen.getByText('Index is set to read-only')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/flyout.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/flyout.test.tsx new file mode 100644 index 0000000000000..294c5e67289fb --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/ml_snapshots/flyout.test.tsx @@ -0,0 +1,190 @@ +/* + * 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 '@testing-library/jest-dom'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { ResponseError } from '../../../../../../common/types'; +import { + mockMlDeprecation, + MOCK_JOB_ID, + MOCK_SNAPSHOT_ID, +} from '../../__fixtures__/es_deprecations'; +import type { SnapshotState } from './use_snapshot_state'; +import { FixSnapshotsFlyout } from './flyout'; + +jest.mock('../../../../app_context', () => { + const actual = jest.requireActual('../../../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + services: { + core: { + docLinks: { + links: { + ml: { + setUpgradeMode: 'https://example.invalid/ml-upgrade-mode', + }, + }, + }, + }, + }, + }), + }; +}); + +jest.mock('../../../../lib/ui_metric', () => { + const actual = jest.requireActual('../../../../lib/ui_metric'); + + return { + ...actual, + uiMetricService: { + ...actual.uiMetricService, + trackUiMetric: jest.fn(), + }, + }; +}); + +describe('FixSnapshotsFlyout', () => { + const closeFlyout = jest.fn(); + const upgradeSnapshot = jest.fn(); + const deleteSnapshot = jest.fn(); + + const renderFlyout = ({ + snapshotState, + mlUpgradeModeEnabled, + }: { + snapshotState: SnapshotState; + mlUpgradeModeEnabled: boolean; + }) => + renderWithI18n( + + ); + + beforeEach(() => { + closeFlyout.mockClear(); + upgradeSnapshot.mockClear(); + deleteSnapshot.mockClear(); + }); + + it('renders flyout details and actions', () => { + const snapshotState: SnapshotState = { + jobId: MOCK_JOB_ID, + snapshotId: MOCK_SNAPSHOT_ID, + status: 'idle', + error: undefined, + }; + + renderFlyout({ snapshotState, mlUpgradeModeEnabled: false }); + + expect(screen.getByTestId('flyoutTitle')).toHaveTextContent('Upgrade or delete model snapshot'); + expect(screen.getByTestId('documentationLink')).toHaveAttribute('href', mockMlDeprecation.url); + expect(screen.getByTestId('upgradeSnapshotButton')).toHaveTextContent('Upgrade'); + expect(screen.getByTestId('deleteSnapshotButton')).toHaveTextContent('Delete'); + }); + + it('shows upgrade mode enabled callout and hides actions', () => { + const snapshotState: SnapshotState = { + jobId: MOCK_JOB_ID, + snapshotId: MOCK_SNAPSHOT_ID, + status: 'idle', + error: undefined, + }; + + renderFlyout({ snapshotState, mlUpgradeModeEnabled: true }); + + expect(screen.getByTestId('mlUpgradeModeEnabledError')).toBeInTheDocument(); + expect(screen.getByTestId('setUpgradeModeDocsLink')).toHaveAttribute( + 'href', + 'https://example.invalid/ml-upgrade-mode' + ); + expect(screen.queryByTestId('upgradeSnapshotButton')).toBeNull(); + expect(screen.queryByTestId('deleteSnapshotButton')).toBeNull(); + }); + + it('shows error callout and changes action labels on upgrade failure', () => { + const error: ResponseError = { + statusCode: 500, + message: 'Upgrade snapshot error', + }; + const snapshotState: SnapshotState = { + jobId: MOCK_JOB_ID, + snapshotId: MOCK_SNAPSHOT_ID, + status: 'error', + action: 'upgrade', + error, + }; + + renderFlyout({ snapshotState, mlUpgradeModeEnabled: false }); + + expect(screen.getByTestId('resolveSnapshotError')).toHaveTextContent( + 'Error upgrading snapshot' + ); + expect(screen.getByTestId('resolveSnapshotError')).toHaveTextContent('Upgrade snapshot error'); + expect(screen.getByTestId('upgradeSnapshotButton')).toHaveTextContent('Retry upgrade'); + }); + + it('shows error callout and changes action labels on delete failure', () => { + const error: ResponseError = { + statusCode: 500, + message: 'Delete snapshot error', + }; + const snapshotState: SnapshotState = { + jobId: MOCK_JOB_ID, + snapshotId: MOCK_SNAPSHOT_ID, + status: 'error', + action: 'delete', + error, + }; + + renderFlyout({ snapshotState, mlUpgradeModeEnabled: false }); + + expect(screen.getByTestId('resolveSnapshotError')).toHaveTextContent('Error deleting snapshot'); + expect(screen.getByTestId('resolveSnapshotError')).toHaveTextContent('Delete snapshot error'); + expect(screen.getByTestId('deleteSnapshotButton')).toHaveTextContent('Retry delete'); + }); + + it('calls upgradeSnapshot and closes flyout', () => { + const snapshotState: SnapshotState = { + jobId: MOCK_JOB_ID, + snapshotId: MOCK_SNAPSHOT_ID, + status: 'idle', + error: undefined, + }; + + renderFlyout({ snapshotState, mlUpgradeModeEnabled: false }); + + fireEvent.click(screen.getByTestId('upgradeSnapshotButton')); + expect(upgradeSnapshot).toHaveBeenCalled(); + expect(closeFlyout).toHaveBeenCalled(); + }); + + it('calls deleteSnapshot and closes flyout', () => { + const snapshotState: SnapshotState = { + jobId: MOCK_JOB_ID, + snapshotId: MOCK_SNAPSHOT_ID, + status: 'idle', + error: undefined, + }; + + renderFlyout({ snapshotState, mlUpgradeModeEnabled: false }); + + fireEvent.click(screen.getByTestId('deleteSnapshotButton')); + expect(deleteSnapshot).toHaveBeenCalled(); + expect(closeFlyout).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/test_utils/helpers.ts b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/test_utils/helpers.ts new file mode 100644 index 0000000000000..8f4f7fe60ffce --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/test_utils/helpers.ts @@ -0,0 +1,59 @@ +/* + * 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 type { EnrichedDeprecationInfo } from '../../../../../../common/types'; +import type { IndexStateContext } from '../indices/context'; +import type { ReindexState } from '../indices/use_reindex'; +import type { UpdateIndexState } from '../indices/use_update_index'; +import { LoadingState } from '../../../types'; + +export const createReindexState = (overrides?: Partial): ReindexState => ({ + loadingState: LoadingState.Success, + errorMessage: null, + reindexTaskPercComplete: null, + status: undefined, + lastCompletedStep: undefined, + cancelLoadingState: undefined, + reindexWarnings: undefined, + hasRequiredPrivileges: true, + meta: { + indexName: 'test-index', + reindexName: 'test-index-reindexed', + aliases: [], + isFrozen: false, + isReadonly: false, + isInDataStream: false, + isClosedIndex: false, + isFollowerIndex: false, + }, + ...overrides, +}); + +export const createUpdateIndexState = ( + overrides?: Partial +): UpdateIndexState => ({ + failedBefore: false, + status: 'incomplete', + ...overrides, +}); + +export const createIndexContext = ({ + deprecation, + reindexState, + updateIndexState, +}: { + deprecation: EnrichedDeprecationInfo; + reindexState: ReindexState; + updateIndexState: UpdateIndexState; +}): IndexStateContext => ({ + deprecation, + reindexState, + updateIndexState, + startReindex: jest.fn, []>(), + cancelReindex: jest.fn, []>(), + updateIndex: jest.fn, []>(), +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.test.tsx new file mode 100644 index 0000000000000..5810c537d6c9b --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.test.tsx @@ -0,0 +1,233 @@ +/* + * 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 '@testing-library/jest-dom'; +import { screen } from '@testing-library/react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { createMemoryHistory } from 'history'; +import { Router } from '@kbn/shared-ux-router'; + +import type { ResponseError } from '../../../../common/types'; +import { EsDeprecations } from './es_deprecations'; +import { mockEsDeprecations } from './__fixtures__/es_deprecations'; + +const mockBreadcrumbsSetBreadcrumbs = jest.fn(); + +const mockUseLoadEsDeprecations = jest.fn(); +const mockUseLoadRemoteClusters = jest.fn(); + +jest.mock('../../app_context', () => { + const actual = jest.requireActual('../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + plugins: { + share: { + url: { + locators: { + get: () => ({ + useUrl: () => '/app/management/data/remote_clusters', + }), + }, + }, + }, + }, + services: { + api: { + useLoadEsDeprecations: () => mockUseLoadEsDeprecations(), + useLoadRemoteClusters: () => mockUseLoadRemoteClusters(), + }, + breadcrumbs: { + setBreadcrumbs: mockBreadcrumbsSetBreadcrumbs, + }, + core: { + docLinks: { + links: { + upgradeAssistant: { + batchReindex: 'https://example.invalid/batch-reindex', + }, + }, + }, + }, + }, + }), + }; +}); + +jest.mock('./es_deprecations_table', () => ({ + EsDeprecationsTable: () =>
, +})); + +describe('EsDeprecations', () => { + const renderPage = () => { + const history = createMemoryHistory(); + return renderWithI18n( + + + + ); + }; + + beforeEach(() => { + mockBreadcrumbsSetBreadcrumbs.mockClear(); + mockUseLoadEsDeprecations.mockReset(); + mockUseLoadRemoteClusters.mockReset(); + }); + + it('shows loading state', () => { + mockUseLoadEsDeprecations.mockReturnValue({ + data: undefined, + isLoading: true, + error: undefined, + resendRequest: jest.fn(), + }); + mockUseLoadRemoteClusters.mockReturnValue({ data: [] }); + + renderPage(); + + expect(screen.getByText('Loading deprecation issues…')).toBeInTheDocument(); + }); + + it('shows no deprecations prompt', async () => { + mockUseLoadEsDeprecations.mockReturnValue({ + data: { ...mockEsDeprecations, migrationsDeprecations: [], totalCriticalDeprecations: 0 }, + isLoading: false, + error: undefined, + resendRequest: jest.fn(), + }); + mockUseLoadRemoteClusters.mockReturnValue({ data: [] }); + + renderPage(); + + expect(await screen.findByTestId('noDeprecationsPrompt')).toBeInTheDocument(); + }); + + it('renders remote clusters callout', async () => { + mockUseLoadEsDeprecations.mockReturnValue({ + data: mockEsDeprecations, + isLoading: false, + error: undefined, + resendRequest: jest.fn(), + }); + mockUseLoadRemoteClusters.mockReturnValue({ data: ['test_remote_cluster'] }); + + renderPage(); + + expect(await screen.findByTestId('remoteClustersWarningCallout')).toBeInTheDocument(); + expect(screen.getByTestId('remoteClustersLink')).toHaveAttribute( + 'href', + '/app/management/data/remote_clusters' + ); + }); + + it('shows critical and warning deprecations count', async () => { + mockUseLoadEsDeprecations.mockReturnValue({ + data: mockEsDeprecations, + isLoading: false, + error: undefined, + resendRequest: jest.fn(), + }); + mockUseLoadRemoteClusters.mockReturnValue({ data: [] }); + + renderPage(); + + expect(await screen.findByTestId('criticalDeprecationsCount')).toHaveTextContent('Critical: 2'); + expect(screen.getByTestId('warningDeprecationsCount')).toHaveTextContent('Warning: 3'); + }); + + it('handles 403', async () => { + const error: ResponseError = { + statusCode: 403, + message: 'Forbidden', + }; + + mockUseLoadEsDeprecations.mockReturnValue({ + data: undefined, + isLoading: false, + error, + resendRequest: jest.fn(), + }); + mockUseLoadRemoteClusters.mockReturnValue({ data: [] }); + + renderPage(); + + expect(await screen.findByTestId('deprecationsPageLoadingError')).toHaveTextContent( + 'You are not authorized to view Elasticsearch deprecation issues.' + ); + }); + + it('shows upgraded message when all nodes have been upgraded', async () => { + const error: ResponseError = { + statusCode: 426, + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + allNodesUpgraded: true, + }, + }; + + mockUseLoadEsDeprecations.mockReturnValue({ + data: undefined, + isLoading: false, + error, + resendRequest: jest.fn(), + }); + mockUseLoadRemoteClusters.mockReturnValue({ data: [] }); + + renderPage(); + + expect(await screen.findByTestId('deprecationsPageLoadingError')).toHaveTextContent( + 'All Elasticsearch nodes have been upgraded.' + ); + }); + + it('shows partially upgraded warning when nodes are running different versions', async () => { + const error: ResponseError = { + statusCode: 426, + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + allNodesUpgraded: false, + }, + }; + + mockUseLoadEsDeprecations.mockReturnValue({ + data: undefined, + isLoading: false, + error, + resendRequest: jest.fn(), + }); + mockUseLoadRemoteClusters.mockReturnValue({ data: [] }); + + renderPage(); + + expect(await screen.findByTestId('deprecationsPageLoadingError')).toHaveTextContent( + /Upgrade Kibana to the same version as your Elasticsearch cluster/i + ); + }); + + it('handles generic error', async () => { + const error: ResponseError = { + statusCode: 500, + message: 'Internal server error', + }; + + mockUseLoadEsDeprecations.mockReturnValue({ + data: undefined, + isLoading: false, + error, + resendRequest: jest.fn(), + }); + mockUseLoadRemoteClusters.mockReturnValue({ data: [] }); + + renderPage(); + + expect(await screen.findByTestId('deprecationsPageLoadingError')).toHaveTextContent( + 'Could not retrieve Elasticsearch deprecation issues.' + ); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations_table.test.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations_table.test.tsx new file mode 100644 index 0000000000000..182a0d53c0448 --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations_table.test.tsx @@ -0,0 +1,243 @@ +/* + * 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 '@testing-library/jest-dom'; +import { fireEvent, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; + +import type { EnrichedDeprecationInfo } from '../../../../common/types'; +import { createEsDeprecations } from './__fixtures__/es_deprecations'; + +jest.mock('../../app_context', () => { + const actual = jest.requireActual('../../app_context'); + + return { + ...actual, + useAppContext: () => ({ + kibanaVersionInfo: { + currentMajor: 8, + currentMinor: 0, + currentPatch: 0, + }, + services: { + api: { + useLoadMlUpgradeMode: () => ({ data: { mlUpgradeModeEnabled: false } }), + }, + }, + }), + }; +}); + +jest.mock('./deprecation_types', () => { + const TestRow = ({ + deprecation, + index, + }: { + deprecation: EnrichedDeprecationInfo; + index: number; + }) => ( + + {deprecation.message} + + ); + + return { + MlSnapshotsTableRow: TestRow, + DefaultTableRow: TestRow, + IndexSettingsTableRow: TestRow, + IndexTableRow: TestRow, + ClusterSettingsTableRow: TestRow, + HealthIndicatorTableRow: TestRow, + DataStreamTableRow: TestRow, + }; +}); + +describe('EsDeprecationsTable', () => { + const reloadMock = jest.fn(); + + const renderTable = async (deprecations: EnrichedDeprecationInfo[]) => { + const { EsDeprecationsTable } = await import('./es_deprecations_table'); + renderWithI18n(); + }; + + const getPaginationItemsCount = () => { + return within(screen.getByTestId('esDeprecationsPagination')).getAllByTestId( + /^pagination-button-\d+$/ + ).length; + }; + + const openStatusFilter = async (user: ReturnType) => { + await user.click( + within(screen.getByTestId('searchBarContainer')).getByRole('button', { name: /Status/i }) + ); + }; + + const openTypeFilter = async (user: ReturnType) => { + await user.click( + within(screen.getByTestId('searchBarContainer')).getByRole('button', { name: /Type/i }) + ); + }; + + const clickFilterOption = async (user: ReturnType, label: string) => { + await user.click(await screen.findByText(label)); + }; + + it('calls reload when refresh is clicked', async () => { + const user = userEvent.setup(); + const { migrationsDeprecations } = createEsDeprecations(1); + reloadMock.mockClear(); + + await renderTable(migrationsDeprecations); + + await user.click(screen.getByTestId('refreshButton')); + expect(reloadMock).toHaveBeenCalledTimes(1); + }); + + it('shows the correct number of pages and deprecations per page', async () => { + const user = userEvent.setup(); + const { migrationsDeprecations } = createEsDeprecations(20); + + await renderTable(migrationsDeprecations); + + expect(getPaginationItemsCount()).toEqual(Math.ceil(migrationsDeprecations.length / 50)); + expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength(50); + + await user.click(screen.getByTestId('pagination-button-1')); + + await waitFor(() => { + expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( + migrationsDeprecations.length - 50 + ); + }); + }); + + it('allows the number of viewable rows to change', async () => { + const user = userEvent.setup(); + const { migrationsDeprecations } = createEsDeprecations(20); + + await renderTable(migrationsDeprecations); + + await user.click(screen.getByTestId('tablePaginationPopoverButton')); + const rowsPerPageButton = await screen.findByTestId('tablePagination-100-rows'); + await user.click(rowsPerPageButton); + + await waitFor(() => { + expect(getPaginationItemsCount()).toEqual(Math.ceil(migrationsDeprecations.length / 100)); + expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( + migrationsDeprecations.length + ); + }); + }); + + it('updates pagination when filters change', async () => { + const user = userEvent.setup(); + const { migrationsDeprecations } = createEsDeprecations(20); + const criticalDeprecations = migrationsDeprecations.filter((d) => d.level === 'critical'); + + await renderTable(migrationsDeprecations); + + await openStatusFilter(user); + await clickFilterOption(user, 'Critical'); + + await waitFor(() => { + expect(getPaginationItemsCount()).toEqual(Math.ceil(criticalDeprecations.length / 50)); + expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( + Math.min(criticalDeprecations.length, 50) + ); + }); + }); + + it('updates pagination when type filter changes', async () => { + const user = userEvent.setup(); + const { migrationsDeprecations } = createEsDeprecations(20); + const mlDeprecations = migrationsDeprecations.filter((d) => d.type === 'ml_settings'); + + await renderTable(migrationsDeprecations); + + await openTypeFilter(user); + await clickFilterOption(user, 'Machine Learning'); + + await waitFor(() => { + expect(getPaginationItemsCount()).toEqual(Math.ceil(mlDeprecations.length / 50)); + expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( + Math.min(mlDeprecations.length, 50) + ); + }); + }); + + it('updates pagination on search', async () => { + const { migrationsDeprecations } = createEsDeprecations(20); + const reindexDeprecations = migrationsDeprecations.filter( + (d) => d.correctiveAction?.type === 'reindex' + ); + + await renderTable(migrationsDeprecations); + + const input = within(screen.getByTestId('searchBarContainer')).getByRole('searchbox'); + fireEvent.change(input, { target: { value: 'Index created before 7.0' } }); + fireEvent.keyUp(input, { target: { value: 'Index created before 7.0' } }); + + await waitFor(() => { + expect(getPaginationItemsCount()).toEqual(Math.ceil(reindexDeprecations.length / 50)); + expect(screen.getAllByTestId('deprecationTableRow')).toHaveLength( + Math.min(reindexDeprecations.length, 50) + ); + }); + }); + + it('maintains correct row state across pagination', async () => { + const user = userEvent.setup(); + const { migrationsDeprecations } = createEsDeprecations(20); + + await renderTable(migrationsDeprecations); + + const getFirstRowMessageCellText = () => { + const row = screen.getAllByTestId('deprecationTableRow')[0]; + return within(row).getByTestId(/-message$/).textContent; + }; + + expect(getPaginationItemsCount()).toBeGreaterThan(1); + + const firstDeprecationMessagePage1 = getFirstRowMessageCellText(); + + await user.click(screen.getByTestId('pagination-button-1')); + await waitFor(() => { + expect(getFirstRowMessageCellText()).not.toEqual(firstDeprecationMessagePage1); + }); + + await user.click(screen.getByTestId('pagination-button-0')); + await waitFor(() => { + expect(getFirstRowMessageCellText()).toEqual(firstDeprecationMessagePage1); + }); + }); + + it('shows error for invalid search queries', async () => { + const { migrationsDeprecations } = createEsDeprecations(1); + await renderTable(migrationsDeprecations); + + const input = within(screen.getByTestId('searchBarContainer')).getByRole('searchbox'); + fireEvent.change(input, { target: { value: '%' } }); + fireEvent.keyUp(input, { target: { value: '%' } }); + + expect(await screen.findByTestId('invalidSearchQueryMessage')).toBeInTheDocument(); + }); + + it('shows message when search query does not return results', async () => { + const { migrationsDeprecations } = createEsDeprecations(1); + await renderTable(migrationsDeprecations); + + const input = within(screen.getByTestId('searchBarContainer')).getByRole('searchbox'); + fireEvent.change(input, { target: { value: 'foobarbaz' } }); + fireEvent.keyUp(input, { target: { value: 'foobarbaz' } }); + + expect(await screen.findByTestId('noDeprecationsRow')).toHaveTextContent( + 'No Elasticsearch deprecation issues found' + ); + }); +});