diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts index d0c93d74f31f4..924dce1a0be9f 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts @@ -37,6 +37,20 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setLoadDeprecationLogsCountResponse = ( + response?: { count: number }, + error?: ResponseError + ) => { + const status = error ? error.statusCode || 400 : 200; + const body = error ? error : response; + + server.respondWith('GET', `${API_BASE_PATH}/deprecation_logging/count`, [ + status, + { 'Content-Type': 'application/json' }, + JSON.stringify(body), + ]); + }; + const setUpdateDeprecationLoggingResponse = ( response?: DeprecationLoggingStatus, error?: ResponseError @@ -102,6 +116,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { setUpgradeMlSnapshotResponse, setDeleteMlSnapshotResponse, setUpgradeMlSnapshotStatusResponse, + setLoadDeprecationLogsCountResponse, }; }; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_logs_step/fix_logs_step.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_logs_step/fix_logs_step.test.tsx index 67d18a5a2e8f7..c19f06f03ecc2 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_logs_step/fix_logs_step.test.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/fix_logs_step/fix_logs_step.test.tsx @@ -103,14 +103,29 @@ describe('Overview - Fix deprecation logs step', () => { expect(exists('fetchLoggingError')).toBe(true); }); + + test('It doesnt show external links and deprecations count when toggle is disabled', async () => { + httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ + isDeprecationLogIndexingEnabled: false, + isDeprecationLoggingEnabled: false, + }); + + await act(async () => { + testBed = await setupOverviewPage(); + }); + + const { exists, component } = testBed; + + component.update(); + + expect(exists('externalLinksTitle')).toBe(false); + expect(exists('deprecationsCountTitle')).toBe(false); + }); }); describe('Step 2 - Analyze logs', () => { beforeEach(async () => { - httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ - isDeprecationLogIndexingEnabled: true, - isDeprecationLoggingEnabled: true, - }); + httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(getLoggingResponse(true)); }); test('Has a link to see logs in observability app', async () => { @@ -151,4 +166,97 @@ describe('Overview - Fix deprecation logs step', () => { expect(find('viewDiscoverLogs').props().href).toBe('/discover/logs'); }); }); + + describe('Step 3 - Resolve log issues', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(getLoggingResponse(true)); + }); + + test('With deprecation warnings', async () => { + httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ + count: 10, + }); + + await act(async () => { + testBed = await setupOverviewPage(); + }); + + const { find, exists, component } = testBed; + + component.update(); + + expect(exists('hasWarningsCallout')).toBe(true); + expect(find('hasWarningsCallout').text()).toContain('10'); + }); + + test('No deprecation warnings', async () => { + httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ + count: 0, + }); + + await act(async () => { + testBed = await setupOverviewPage(); + }); + + const { find, exists, component } = testBed; + + component.update(); + + expect(exists('noWarningsCallout')).toBe(true); + expect(find('noWarningsCallout').text()).toContain('No deprecation warnings'); + }); + + test('Handles errors and can retry', async () => { + const error = { + statusCode: 500, + error: 'Internal server error', + message: 'Internal server error', + }; + + httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse(undefined, error); + + await act(async () => { + testBed = await setupOverviewPage(); + }); + + const { exists, actions, component } = testBed; + + component.update(); + + expect(exists('errorCallout')).toBe(true); + + httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ + count: 0, + }); + + await actions.clickRetryButton(); + + expect(exists('noWarningsCallout')).toBe(true); + }); + + test('Allows user to reset last stored date', async () => { + httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ + count: 10, + }); + + await act(async () => { + testBed = await setupOverviewPage(); + }); + + const { exists, actions, component } = testBed; + + component.update(); + + expect(exists('hasWarningsCallout')).toBe(true); + expect(exists('resetLastStoredDate')).toBe(true); + + httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({ + count: 0, + }); + + await actions.clickResetButton(); + + expect(exists('noWarningsCallout')).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts index 3ab7f18bbefb8..1457af010af5b 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts @@ -37,8 +37,30 @@ const createActions = (testBed: TestBed) => { component.update(); }; + const clickRetryButton = async () => { + const { find, component } = testBed; + + await act(async () => { + find('retryButton').simulate('click'); + }); + + component.update(); + }; + + const clickResetButton = async () => { + const { find, component } = testBed; + + await act(async () => { + find('resetLastStoredDate').simulate('click'); + }); + + component.update(); + }; + return { clickDeprecationToggle, + clickRetryButton, + clickResetButton, }; }; diff --git a/x-pack/plugins/upgrade_assistant/common/constants.ts b/x-pack/plugins/upgrade_assistant/common/constants.ts index 04a07fbee0d35..d3b1efd31d373 100644 --- a/x-pack/plugins/upgrade_assistant/common/constants.ts +++ b/x-pack/plugins/upgrade_assistant/common/constants.ts @@ -30,4 +30,7 @@ export const API_BASE_PATH = '/api/upgrade_assistant'; export const DEPRECATION_WARNING_UPPER_LIMIT = 999999; export const DEPRECATION_LOGS_SOURCE_ID = 'deprecation_logs'; +export const DEPRECATION_LOGS_INDEX = '.logs-deprecation.elasticsearch-default'; export const DEPRECATION_LOGS_INDEX_PATTERN = '.logs-deprecation.elasticsearch-default'; + +export const DEPRECATION_LOGS_COUNT_POLL_INTERVAL_MS = 60000; diff --git a/x-pack/plugins/upgrade_assistant/kibana.json b/x-pack/plugins/upgrade_assistant/kibana.json index a250ecd55f1a9..948459ca7e95f 100644 --- a/x-pack/plugins/upgrade_assistant/kibana.json +++ b/x-pack/plugins/upgrade_assistant/kibana.json @@ -10,5 +10,5 @@ "configPath": ["xpack", "upgrade_assistant"], "requiredPlugins": ["management", "discover", "data", "licensing", "features", "infra", "share"], "optionalPlugins": ["usageCollection", "cloud"], - "requiredBundles": ["esUiShared", "kibanaReact"] + "requiredBundles": ["esUiShared", "kibanaReact", "kibanaUtils"] } diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx new file mode 100644 index 0000000000000..5187e13d210bd --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/deprecations_count_checkpoint.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent, useState } from 'react'; +import moment from 'moment-timezone'; +import { FormattedDate, FormattedTime, FormattedMessage } from '@kbn/i18n/react'; + +import { i18n } from '@kbn/i18n'; +import { EuiCallOut, EuiButton, EuiLoadingContent } from '@elastic/eui'; +import { useAppContext } from '../../../../app_context'; +import { Storage } from '../../../../../shared_imports'; + +const LS_SETTING_ID = 'kibana.upgradeAssistant.lastCheckpoint'; +const localStorage = new Storage(window.localStorage); + +const i18nTexts = { + calloutTitle: (warningsCount: number, previousCheck: string) => ( + + {' '} + + + ), + }} + /> + ), + calloutBody: i18n.translate('xpack.upgradeAssistant.overview.verifyChanges.calloutBody', { + defaultMessage: + 'Reset the counter after making changes and continue monitoring to verify that you are no longer using deprecated APIs.', + }), + loadingError: i18n.translate('xpack.upgradeAssistant.overview.verifyChanges.loadingError', { + defaultMessage: 'An error occurred while retrieving the count of deprecation logs', + }), + retryButton: i18n.translate('xpack.upgradeAssistant.overview.verifyChanges.retryButton', { + defaultMessage: 'Try again', + }), + resetCounterButton: i18n.translate( + 'xpack.upgradeAssistant.overview.verifyChanges.resetCounterButton', + { + defaultMessage: 'Reset counter', + } + ), +}; + +const getPreviousCheckpointDate = () => { + const storedValue = moment(localStorage.get(LS_SETTING_ID)); + + if (storedValue.isValid()) { + return storedValue.toISOString(); + } + + const now = moment().toISOString(); + localStorage.set(LS_SETTING_ID, now); + + return now; +}; + +export const DeprecationsCountCheckpoint: FunctionComponent = () => { + const { api } = useAppContext(); + const [previousCheck, setPreviousCheck] = useState(getPreviousCheckpointDate()); + const { data, error, isLoading, resendRequest, isInitialRequest } = api.getDeprecationLogsCount( + previousCheck + ); + + const warningsCount = data?.count || 0; + const calloutTint = warningsCount > 0 ? 'warning' : 'success'; + const calloutIcon = warningsCount > 0 ? 'alert' : 'check'; + const calloutTestId = warningsCount > 0 ? 'hasWarningsCallout' : 'noWarningsCallout'; + + const onResetClick = () => { + const now = moment().toISOString(); + + setPreviousCheck(now); + localStorage.set(LS_SETTING_ID, now); + }; + + if (isInitialRequest && isLoading) { + return ; + } + + if (error) { + return ( + +

+ {error.statusCode} - {error.message} +

+ + {i18nTexts.retryButton} + +
+ ); + } + + return ( + +

{i18nTexts.calloutBody}

+ + {i18nTexts.resetCounterButton} + +
+ ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/index.ts new file mode 100644 index 0000000000000..e32655f90b848 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/deprecations_count_checkpoint/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { DeprecationsCountCheckpoint } from './deprecations_count_checkpoint'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/fix_logs_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/fix_logs_step.tsx index 9c22a07ed771b..cc5c14a87f3da 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/fix_logs_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/fix_logs_step/fix_logs_step.tsx @@ -12,6 +12,7 @@ import { EuiText, EuiSpacer, EuiPanel, EuiCallOut } from '@elastic/eui'; import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; import { ExternalLinks } from './external_links'; +import { DeprecationsCountCheckpoint } from './deprecations_count_checkpoint'; import { useDeprecationLogging } from './use_deprecation_logging'; import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; @@ -25,6 +26,12 @@ const i18nTexts = { analyzeTitle: i18n.translate('xpack.upgradeAssistant.overview.analyzeTitle', { defaultMessage: 'Analyze deprecation logs', }), + deprecationsCountCheckpointTitle: i18n.translate( + 'xpack.upgradeAssistant.overview.deprecationsCountCheckpointTitle', + { + defaultMessage: 'Resolve deprecation issues and verify your changes', + } + ), onlyLogWritingEnabledTitle: i18n.translate( 'xpack.upgradeAssistant.overview.deprecationLogs.deprecationWarningTitle', { @@ -70,11 +77,18 @@ const FixLogsStep: FunctionComponent = () => { {state.isDeprecationLogIndexingEnabled && ( <> - +

{i18nTexts.analyzeTitle}

+ + + +

{i18nTexts.deprecationsCountCheckpointTitle}

+
+ + )} diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts index 78070c5717496..c4c1ba5e10d8a 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts @@ -7,7 +7,7 @@ import { HttpSetup } from 'src/core/public'; import { ESUpgradeStatus } from '../../../common/types'; -import { API_BASE_PATH } from '../../../common/constants'; +import { API_BASE_PATH, DEPRECATION_LOGS_COUNT_POLL_INTERVAL_MS } from '../../../common/constants'; import { UseRequestConfig, SendRequestConfig, @@ -82,6 +82,17 @@ export class ApiService { return result; } + public getDeprecationLogsCount(from: string) { + return this.useRequest<{ + count: number; + }>({ + path: `${API_BASE_PATH}/deprecation_logging/count`, + method: 'get', + query: { from }, + pollIntervalMs: DEPRECATION_LOGS_COUNT_POLL_INTERVAL_MS, + }); + } + public async updateIndexSettings(indexName: string, settings: string[]) { const result = await this.sendRequest({ path: `${API_BASE_PATH}/${indexName}/index_settings`, diff --git a/x-pack/plugins/upgrade_assistant/public/shared_imports.ts b/x-pack/plugins/upgrade_assistant/public/shared_imports.ts index fefa6fd60f806..e9c034117038a 100644 --- a/x-pack/plugins/upgrade_assistant/public/shared_imports.ts +++ b/x-pack/plugins/upgrade_assistant/public/shared_imports.ts @@ -18,6 +18,8 @@ export { GlobalFlyout, } from '../../../../src/plugins/es_ui_shared/public/'; +export { Storage } from '../../../../src/plugins/kibana_utils/public'; + export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; export { DataPublicPluginStart } from '../../../../src/plugins/data/public'; diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts index 870bd6b985661..a9a2671a6c713 100644 --- a/x-pack/plugins/upgrade_assistant/server/plugin.ts +++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts @@ -32,7 +32,7 @@ import { reindexOperationSavedObjectType, mlSavedObjectType, } from './saved_object_types'; -import { DEPRECATION_LOGS_SOURCE_ID, DEPRECATION_LOGS_INDEX_PATTERN } from '../common/constants'; +import { DEPRECATION_LOGS_SOURCE_ID, DEPRECATION_LOGS_INDEX } from '../common/constants'; import { RouteDependencies } from './types'; @@ -98,7 +98,7 @@ export class UpgradeAssistantServerPlugin implements Plugin { description: 'deprecation logs', logIndices: { type: 'index_name', - indexName: DEPRECATION_LOGS_INDEX_PATTERN, + indexName: DEPRECATION_LOGS_INDEX, }, logColumns: [ { timestampColumn: { id: 'timestampField' } }, diff --git a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/request.mock.ts b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/request.mock.ts index d3a36835d12be..c77f3a6661ebe 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/request.mock.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/request.mock.ts @@ -8,6 +8,7 @@ export const createRequestMock = (opts?: { headers?: any; params?: Record; + query?: Record; body?: Record; }) => { return Object.assign({ headers: {} }, opts || {}); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts index e146415b1ccfc..f1842f963aea5 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts @@ -61,7 +61,7 @@ describe('deprecation logging API', () => { it('returns an error if it throws', async () => { (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster - .getSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + .getSettings as jest.Mock).mockRejectedValue(new Error('scary error!')); await expect( routeDependencies.router.getHandler({ method: 'get', @@ -96,7 +96,7 @@ describe('deprecation logging API', () => { it('returns an error if it throws', async () => { (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster - .putSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + .putSettings as jest.Mock).mockRejectedValue(new Error('scary error!')); await expect( routeDependencies.router.getHandler({ method: 'put', @@ -105,4 +105,61 @@ describe('deprecation logging API', () => { ).rejects.toThrow('scary error!'); }); }); + + describe('GET /api/upgrade_assistant/deprecation_logging/count', () => { + const MOCK_FROM_DATE = '2021-08-23T07:32:34.782Z'; + + it('returns count of deprecations', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.indices + .exists as jest.Mock).mockResolvedValue({ + body: true, + }); + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser + .count as jest.Mock).mockResolvedValue({ + body: { count: 10 }, + }); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/deprecation_logging/count', + })( + routeHandlerContextMock, + createRequestMock({ query: { from: MOCK_FROM_DATE } }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ count: 10 }); + }); + + it('returns zero matches when deprecation logs index is not created', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.indices + .exists as jest.Mock).mockResolvedValue({ + body: false, + }); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/deprecation_logging/count', + })( + routeHandlerContextMock, + createRequestMock({ query: { from: MOCK_FROM_DATE } }), + kibanaResponseFactory + ); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ count: 0 }); + }); + + it('returns an error if it throws', async () => { + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.indices + .exists as jest.Mock).mockRejectedValue(new Error('scary error!')); + await expect( + routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/deprecation_logging/count', + })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory) + ).rejects.toThrow('scary error!'); + }); + }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts index fb2a5b559e5a9..a0dfe0d152ad5 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts @@ -14,6 +14,7 @@ import { } from '../lib/es_deprecation_logging_apis'; import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; import { RouteDependencies } from '../types'; +import { DEPRECATION_LOGS_INDEX } from '../../common/constants'; export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) { router.get( @@ -63,4 +64,49 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) } ) ); + + router.get( + { + path: `${API_BASE_PATH}/deprecation_logging/count`, + validate: { + query: schema.object({ + from: schema.string(), + }), + }, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + elasticsearch: { client }, + }, + }, + request, + response + ) => { + const { body: indexExists } = await client.asCurrentUser.indices.exists({ + index: DEPRECATION_LOGS_INDEX, + }); + + if (!indexExists) { + return response.ok({ body: { count: 0 } }); + } + + const { body } = await client.asCurrentUser.count({ + index: DEPRECATION_LOGS_INDEX, + body: { + query: { + range: { + '@timestamp': { + gte: request.query.from, + }, + }, + }, + }, + }); + + return response.ok({ body: { count: body.count } }); + } + ) + ); }