[Upgrade Assistant] Overview page redesign (#106521)

This commit is contained in:
Ignacio Rivas 2021-08-18 09:12:55 +02:00 committed by GitHub
parent fc1a2bbd1b
commit 1d98cb6512
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 1973 additions and 1123 deletions

View file

@ -198,6 +198,8 @@ export class DocLinksService {
transportSettings: `${ELASTICSEARCH_DOCS}modules-network.html#common-network-settings`,
typesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`,
deprecationLogging: `${ELASTICSEARCH_DOCS}logging.html#deprecation-logging`,
setupUpgrade: `${ELASTICSEARCH_DOCS}setup-upgrade.html`,
releaseHighlights: `${ELASTICSEARCH_DOCS}release-highlights.html`,
},
siem: {
guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`,

View file

@ -24797,7 +24797,6 @@
"xpack.upgradeAssistant.deprecationListSearchBar.placeholderAriaLabel": "フィルター",
"xpack.upgradeAssistant.deprecationListSearchBar.placeholderLabel": "フィルター",
"xpack.upgradeAssistant.deprecationListSearchBar.reloadButtonLabel": "再読み込み",
"xpack.upgradeAssistant.deprecationLoggingDescription.deprecationLoggingLink": "廃止予定ログ",
"xpack.upgradeAssistant.emptyPrompt.learnMoreDescription": "{nextMajor}への移行に関する詳細をご覧ください。",
"xpack.upgradeAssistant.emptyPrompt.title": "{uaVersion} アップグレードアシスタント",
"xpack.upgradeAssistant.emptyPrompt.upgradeAssistantDescription": "アップグレードアシスタントはクラスターの廃止予定の設定を特定し、アップグレード前に問題を解決できるようにします。Elastic {nextMajor}にアップグレードするときにここに戻って確認してください。",
@ -24819,9 +24818,7 @@
"xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsTitle": "重大",
"xpack.upgradeAssistant.esDeprecationStats.loadingText": "Elasticsearchの廃止統計情報を読み込んでいます...",
"xpack.upgradeAssistant.esDeprecationStats.statsTitle": "Elasticsearch",
"xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTitle": "廃止予定",
"xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTooltip": "このクラスターは{clusterCount}個の廃止予定のクラスター設定と{indexCount}個の廃止予定のインデックス設定を使用しています。",
"xpack.upgradeAssistant.esDeprecationStats.viewDeprecationsLinkText": "廃止予定を表示",
"xpack.upgradeAssistant.kibanaDeprecationErrors.loadingErrorDescription": "エラーについては、Kibanaサーバーログを確認してください。",
"xpack.upgradeAssistant.kibanaDeprecationErrors.loadingErrorTitle": "Kibana廃止予定を取得できませんでした",
"xpack.upgradeAssistant.kibanaDeprecationErrors.pluginErrorDescription": "エラーについては、Kibanaサーバーログを確認してください。",
@ -24845,23 +24842,12 @@
"xpack.upgradeAssistant.kibanaDeprecationStats.loadingErrorMessage": "Kibana廃止予定の取得中にエラーが発生しました。",
"xpack.upgradeAssistant.kibanaDeprecationStats.loadingText": "Kibana廃止予定統計情報を読み込んでいます…",
"xpack.upgradeAssistant.kibanaDeprecationStats.statsTitle": "Kibana",
"xpack.upgradeAssistant.kibanaDeprecationStats.totalDeprecationsLabel": "Kibanaには合計{totalDeprecations}個の廃止予定があります",
"xpack.upgradeAssistant.kibanaDeprecationStats.totalDeprecationsTitle": "廃止予定",
"xpack.upgradeAssistant.kibanaDeprecationStats.viewDeprecationsLinkText": "廃止予定を表示",
"xpack.upgradeAssistant.noDeprecationsPrompt.description": "構成は最新です。",
"xpack.upgradeAssistant.noDeprecationsPrompt.nextStepsDescription": "他のスタック廃止予定については、{overviewButton}を確認してください。",
"xpack.upgradeAssistant.noDeprecationsPrompt.overviewLinkText": "概要ページ",
"xpack.upgradeAssistant.noDeprecationsPrompt.title": "アップグレードする準備ができました。",
"xpack.upgradeAssistant.overview.deprecationLogging.loadingLabel": "ロギング状態を取得しています",
"xpack.upgradeAssistant.overview.deprecationLoggingDescription": "{deprecationLoggingLink}を有効にすると、Elastic {nextMajor}にアップグレードした後に使用できない廃止予定の機能を使用しているかどうかがわかります。",
"xpack.upgradeAssistant.overview.deprecationLoggingTitle": "廃止ログ",
"xpack.upgradeAssistant.overview.deprecationLogs.disableButtonLabel": "廃止ログを無効にする",
"xpack.upgradeAssistant.overview.deprecationLogs.disabledToastMessage": "廃止予定のアクションをログに出力しません。",
"xpack.upgradeAssistant.overview.deprecationLogs.disablingButtonLabel": "廃止ログを無効にしています",
"xpack.upgradeAssistant.overview.deprecationLogs.enableButtonLabel": "廃止ログを有効にする",
"xpack.upgradeAssistant.overview.deprecationLogs.enabledToastMessage": "廃止予定のアクションをログに出力します。",
"xpack.upgradeAssistant.overview.deprecationLogs.enablingButtonLabel": "廃止ログを有効にしています",
"xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorButtonLabel": "廃止ログがありません",
"xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorMessage": "ログ情報を取得できませんでした。",
"xpack.upgradeAssistant.overview.deprecationLogs.reloadButtonLabel": "再試行",
"xpack.upgradeAssistant.overview.deprecationLogs.updateErrorMessage": "ログ状態を更新できませんでした。",
@ -24869,9 +24855,6 @@
"xpack.upgradeAssistant.overview.pageDescription": "廃止予定の設定を特定し、構成を更新して、アップグレードの準備を行います。",
"xpack.upgradeAssistant.overview.pageTitle": "アップグレードアシスタント",
"xpack.upgradeAssistant.reindex.reindexPrivilegesErrorBatch": "「{indexName}」に再インデックスするための権限が不十分です。",
"xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.breackingChangesDocButtonLabel": "廃止と互換性を破る変更",
"xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.calloutDetail": "Elasticsearch {nextEsVersion} の {breakingChangesDocButton} の完全なリストは、最終の {currentEsVersion} マイナーリリースで確認できます。この警告は、リストがすべて解決されると消えます。",
"xpack.upgradeAssistant.tabs.incompleteCallout.calloutTitle": "リストの問題がすべて解決されていない可能性があります。",
"xpack.uptime.addDataButtonLabel": "データの追加",
"xpack.uptime.alerts.anomaly.criteriaExpression.ariaLabel": "選択したモニターの条件を表示する式。",
"xpack.uptime.alerts.anomaly.criteriaExpression.description": "監視するとき",

View file

@ -25350,7 +25350,6 @@
"xpack.upgradeAssistant.deprecationListSearchBar.placeholderAriaLabel": "筛选",
"xpack.upgradeAssistant.deprecationListSearchBar.placeholderLabel": "筛选",
"xpack.upgradeAssistant.deprecationListSearchBar.reloadButtonLabel": "重新加载",
"xpack.upgradeAssistant.deprecationLoggingDescription.deprecationLoggingLink": "弃用日志记录",
"xpack.upgradeAssistant.emptyPrompt.learnMoreDescription": "详细了解如何迁移到 {nextMajor}。",
"xpack.upgradeAssistant.emptyPrompt.title": "{uaVersion} 升级助手",
"xpack.upgradeAssistant.emptyPrompt.upgradeAssistantDescription": "升级助手识别集群中弃用的设置,帮助您在升级前解决问题。需要升级到 Elastic {nextMajor} 时,回到这里查看。",
@ -25372,9 +25371,7 @@
"xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsTitle": "紧急",
"xpack.upgradeAssistant.esDeprecationStats.loadingText": "正在加载 Elasticsearch 弃用统计……",
"xpack.upgradeAssistant.esDeprecationStats.statsTitle": "Elasticsearch",
"xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTitle": "弃用",
"xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTooltip": "此集群正在使用 {clusterCount} 个已弃用集群设置和 {indexCount} 个已弃用的索引设置",
"xpack.upgradeAssistant.esDeprecationStats.viewDeprecationsLinkText": "查看弃用",
"xpack.upgradeAssistant.kibanaDeprecationErrors.loadingErrorDescription": "请在 Kibana 服务器日志中查看错误。",
"xpack.upgradeAssistant.kibanaDeprecationErrors.loadingErrorTitle": "无法检索 Kibana 弃用",
"xpack.upgradeAssistant.kibanaDeprecationErrors.pluginErrorDescription": "请在 Kibana 服务器日志中查看错误。",
@ -25398,23 +25395,12 @@
"xpack.upgradeAssistant.kibanaDeprecationStats.loadingErrorMessage": "检索 Kibana 弃用时发生错误。",
"xpack.upgradeAssistant.kibanaDeprecationStats.loadingText": "正在加载 Kibana 弃用统计……",
"xpack.upgradeAssistant.kibanaDeprecationStats.statsTitle": "Kibana",
"xpack.upgradeAssistant.kibanaDeprecationStats.totalDeprecationsLabel": "Kibana 总共有 {totalDeprecations} 个弃用",
"xpack.upgradeAssistant.kibanaDeprecationStats.totalDeprecationsTitle": "弃用",
"xpack.upgradeAssistant.kibanaDeprecationStats.viewDeprecationsLinkText": "查看弃用",
"xpack.upgradeAssistant.noDeprecationsPrompt.description": "您的配置是最新的。",
"xpack.upgradeAssistant.noDeprecationsPrompt.nextStepsDescription": "查看{overviewButton}以了解其他 Stack 弃用。",
"xpack.upgradeAssistant.noDeprecationsPrompt.overviewLinkText": "“概览”页面",
"xpack.upgradeAssistant.noDeprecationsPrompt.title": "准备好升级!",
"xpack.upgradeAssistant.overview.deprecationLogging.loadingLabel": "正在检索日志记录状态",
"xpack.upgradeAssistant.overview.deprecationLoggingDescription": "启用{deprecationLoggingLink}以查看是否正在使用升级到 Elastic {nextMajor} 后将不再可用的已弃用功能。",
"xpack.upgradeAssistant.overview.deprecationLoggingTitle": "弃用日志",
"xpack.upgradeAssistant.overview.deprecationLogs.disableButtonLabel": "禁用弃用日志记录",
"xpack.upgradeAssistant.overview.deprecationLogs.disabledToastMessage": "不记录弃用的操作。",
"xpack.upgradeAssistant.overview.deprecationLogs.disablingButtonLabel": "正在禁用弃用日志记录",
"xpack.upgradeAssistant.overview.deprecationLogs.enableButtonLabel": "启用弃用日志记录",
"xpack.upgradeAssistant.overview.deprecationLogs.enabledToastMessage": "记录弃用的操作。",
"xpack.upgradeAssistant.overview.deprecationLogs.enablingButtonLabel": "正在启用弃用日志记录",
"xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorButtonLabel": "弃用日志记录不可用",
"xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorMessage": "无法检索日志记录信息。",
"xpack.upgradeAssistant.overview.deprecationLogs.reloadButtonLabel": "重试",
"xpack.upgradeAssistant.overview.deprecationLogs.updateErrorMessage": "无法更新日志记录状态。",
@ -25422,9 +25408,6 @@
"xpack.upgradeAssistant.overview.pageDescription": "通过识别弃用的设置并更新配置来准备升级。",
"xpack.upgradeAssistant.overview.pageTitle": "升级助手",
"xpack.upgradeAssistant.reindex.reindexPrivilegesErrorBatch": "您没有足够的权限重新索引“{indexName}”。",
"xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.breackingChangesDocButtonLabel": "弃用内容和重大更改",
"xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.calloutDetail": "Elasticsearch {nextEsVersion} 中的 {breakingChangesDocButton} 完整列表将在最终的 {currentEsVersion} 次要版本中提供。完成列表后,此警告将消失。",
"xpack.upgradeAssistant.tabs.incompleteCallout.calloutTitle": "问题列表可能不完整",
"xpack.uptime.addDataButtonLabel": "添加数据",
"xpack.uptime.alerts.anomaly.criteriaExpression.ariaLabel": "显示选定监测的条件的表达式。",
"xpack.uptime.alerts.anomaly.criteriaExpression.description": "当监测",

View file

@ -43,7 +43,10 @@ describe('Cluster tab', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ isEnabled: true });
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({
isDeprecationLogIndexingEnabled: true,
isDeprecationLoggingEnabled: true,
});
await act(async () => {
testBed = await setupClusterPage({ isReadOnlyMode: false });

View file

@ -7,7 +7,7 @@
import sinon, { SinonFakeServer } from 'sinon';
import { API_BASE_PATH } from '../../../common/constants';
import { ESUpgradeStatus } from '../../../common/types';
import { ESUpgradeStatus, DeprecationLoggingStatus } from '../../../common/types';
import { ResponseError } from '../../../public/application/lib/api';
// Register helpers to mock HTTP Requests
@ -24,7 +24,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
};
const setLoadDeprecationLoggingResponse = (
response?: { isEnabled: boolean },
response?: DeprecationLoggingStatus,
error?: ResponseError
) => {
const status = error ? error.statusCode || 400 : 200;
@ -38,7 +38,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
};
const setUpdateDeprecationLoggingResponse = (
response?: { isEnabled: boolean },
response?: DeprecationLoggingStatus,
error?: ResponseError
) => {
const status = error ? error.statusCode || 400 : 200;

View file

@ -7,7 +7,7 @@
import { act } from 'react-dom/test-utils';
import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest';
import { DeprecationsOverview } from '../../../public/application/components/overview';
import { Overview } from '../../../public/application/components/overview';
import { WithAppDependencies } from './setup_environment';
const testBedConfig: TestBedConfig = {
@ -31,7 +31,7 @@ const createActions = (testBed: TestBed) => {
const { find, component } = testBed;
await act(async () => {
find('upgradeAssistantDeprecationToggle').simulate('click');
find('deprecationLoggingToggle').simulate('click');
});
component.update();
@ -43,10 +43,7 @@ const createActions = (testBed: TestBed) => {
};
export const setup = async (overrides?: Record<string, unknown>): Promise<OverviewTestBed> => {
const initTestBed = registerTestBed(
WithAppDependencies(DeprecationsOverview, overrides),
testBedConfig
);
const initTestBed = registerTestBed(WithAppDependencies(Overview, overrides), testBedConfig);
const testBed = await initTestBed();
return {
@ -56,21 +53,27 @@ export const setup = async (overrides?: Record<string, unknown>): Promise<Overvi
};
export type OverviewTestSubjects =
| 'overviewPageContent'
| 'esStatsPanel'
| 'esStatsPanel.totalDeprecations'
| 'esStatsPanel.warningDeprecations'
| 'esStatsPanel.criticalDeprecations'
| 'kibanaStatsPanel'
| 'kibanaStatsPanel.totalDeprecations'
| 'kibanaStatsPanel.warningDeprecations'
| 'kibanaStatsPanel.criticalDeprecations'
| 'deprecationLoggingFormRow'
| 'requestErrorIconTip'
| 'esRequestErrorIconTip'
| 'kibanaRequestErrorIconTip'
| 'partiallyUpgradedErrorIconTip'
| 'upgradedErrorIconTip'
| 'unauthorizedErrorIconTip'
| 'upgradedPrompt'
| 'partiallyUpgradedPrompt'
| 'upgradeAssistantDeprecationToggle'
| 'deprecationLoggingToggle'
| 'updateLoggingError'
| 'fetchLoggingError'
| 'noDeprecationsLabel'
| 'viewObserveLogs'
| 'viewDiscoverLogs'
| 'upgradeSetupDocsLink'
| 'upgradeSetupCloudLink'
| 'deprecationWarningCallout'
| 'upgradeStatusError';

View file

@ -0,0 +1,30 @@
/*
* 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 { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks';
import { discoverPluginMock } from '../../../../../../src/plugins/discover/public/mocks';
import { applicationServiceMock } from '../../../../../../src/core/public/application/application_service.mock';
const discoverMock = discoverPluginMock.createStartContract();
export const servicesMock = {
data: dataPluginMock.createStartContract(),
application: applicationServiceMock.createStartContract(),
discover: {
...discoverMock,
locator: {
...discoverMock.locator,
getLocation: jest.fn(() =>
Promise.resolve({
app: '/discover',
path: 'logs',
state: {},
})
),
},
},
};

View file

@ -14,13 +14,16 @@ import {
deprecationsServiceMock,
docLinksServiceMock,
notificationServiceMock,
applicationServiceMock,
} from 'src/core/public/mocks';
import { HttpSetup } from 'src/core/public';
import { KibanaContextProvider } from '../../../public/shared_imports';
import { mockKibanaSemverVersion } from '../../../common/constants';
import { AppContextProvider } from '../../../public/application/app_context';
import { apiService } from '../../../public/application/lib/api';
import { breadcrumbService } from '../../../public/application/lib/breadcrumbs';
import { servicesMock } from './services_mock';
import { init as initHttpRequests } from './http_requests';
const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });
@ -39,18 +42,22 @@ export const WithAppDependencies = (Comp: any, overrides: Record<string, unknown
prevMajor: mockKibanaSemverVersion.major - 1,
nextMajor: mockKibanaSemverVersion.major + 1,
},
isReadOnlyMode: false,
notifications: notificationServiceMock.createStartContract(),
isReadOnlyMode: false,
api: apiService,
breadcrumbs: breadcrumbService,
getUrlForApp: () => '',
getUrlForApp: applicationServiceMock.createStartContract().getUrlForApp,
deprecations: deprecationsServiceMock.createStartContract(),
};
const { servicesOverrides, ...contextOverrides } = overrides;
return (
<AppContextProvider value={{ ...contextValue, ...overrides }}>
<Comp {...props} />
</AppContextProvider>
<KibanaContextProvider services={{ ...servicesMock, ...(servicesOverrides as {}) }}>
<AppContextProvider value={{ ...contextValue, ...contextOverrides }}>
<Comp {...props} />
</AppContextProvider>
</KibanaContextProvider>
);
};

View file

@ -39,7 +39,10 @@ describe('Indices tab', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ isEnabled: true });
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({
isDeprecationLogIndexingEnabled: true,
isDeprecationLoggingEnabled: true,
});
await act(async () => {
testBed = await setupIndicesPage({ isReadOnlyMode: false });

View file

@ -1,270 +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 { DomainDeprecationDetails } from 'kibana/public';
import { act } from 'react-dom/test-utils';
import { deprecationsServiceMock } from 'src/core/public/mocks';
import { ESUpgradeStatus } from '../../common/types';
import { OverviewTestBed, setupOverviewPage, setupEnvironment } from './helpers';
describe('Overview page', () => {
let testBed: OverviewTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
beforeEach(async () => {
const esDeprecationsMockResponse: ESUpgradeStatus = {
totalCriticalDeprecations: 1,
cluster: [
{
level: 'critical',
message: 'Index Lifecycle Management poll interval is set too low',
url:
'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html#ilm-poll-interval-limit',
details:
'The Index Lifecycle Management poll interval setting [indices.lifecycle.poll_interval] is currently set to [500ms], but must be 1s or greater',
},
],
indices: [
{
level: 'warning',
message: 'translog retention settings are ignored',
url:
'https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html',
details:
'translog retention settings [index.translog.retention.size] and [index.translog.retention.age] are ignored because translog is no longer used in peer recoveries with soft-deletes enabled (default in 7.0 or later)',
index: 'settings',
},
],
};
const kibanaDeprecationsMockResponse: DomainDeprecationDetails[] = [
{
correctiveActions: { manualSteps: ['test-step'] },
domainId: 'xpack.spaces',
level: 'critical',
message:
'Disabling the Spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)',
},
];
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ isEnabled: true });
await act(async () => {
const deprecationService = deprecationsServiceMock.createStartContract();
deprecationService.getAllDeprecations = jest
.fn()
.mockReturnValue(kibanaDeprecationsMockResponse);
testBed = await setupOverviewPage({
deprecations: deprecationService,
});
});
const { component } = testBed;
component.update();
});
afterAll(() => {
server.restore();
});
test('renders the overview page', () => {
const { exists, find } = testBed;
expect(exists('overviewPageContent')).toBe(true);
// Verify ES stats
expect(exists('esStatsPanel')).toBe(true);
expect(find('esStatsPanel.totalDeprecations').text()).toContain('2');
expect(find('esStatsPanel.criticalDeprecations').text()).toContain('1');
// Verify Kibana stats
expect(exists('kibanaStatsPanel')).toBe(true);
expect(find('kibanaStatsPanel.totalDeprecations').text()).toContain('1');
expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain('1');
});
describe('Deprecation logging', () => {
test('toggles deprecation logging', async () => {
const { find, actions } = testBed;
httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse({ isEnabled: false });
expect(find('upgradeAssistantDeprecationToggle').text()).toEqual(
'Disable deprecation logging'
);
await actions.clickDeprecationToggle();
const latestRequest = server.requests[server.requests.length - 1];
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ isEnabled: false });
expect(find('upgradeAssistantDeprecationToggle').text()).toEqual(
'Enable deprecation logging'
);
});
test('handles network error when updating logging state', async () => {
const error = {
statusCode: 500,
error: 'Internal server error',
message: 'Internal server error',
};
const { actions, find, exists } = testBed;
httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse(undefined, error);
expect(find('upgradeAssistantDeprecationToggle').text()).toEqual(
'Disable deprecation logging'
);
await actions.clickDeprecationToggle();
// Logging state should not change since there was an error
expect(find('upgradeAssistantDeprecationToggle').text()).toEqual(
'Disable deprecation logging'
);
expect(exists('updateLoggingError')).toBe(true);
});
test('handles network error when fetching logging state', async () => {
const error = {
statusCode: 500,
error: 'Internal server error',
message: 'Internal server error',
};
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(undefined, error);
await act(async () => {
testBed = await setupOverviewPage();
});
const { component, exists, find } = testBed;
component.update();
expect(find('upgradeAssistantDeprecationToggle').text()).toEqual(
'Deprecation logging unavailable'
);
expect(exists('fetchLoggingError')).toBe(true);
});
});
describe('Error handling', () => {
describe('Kibana deprecations', () => {
test('handles network failure', async () => {
await act(async () => {
const deprecationService = deprecationsServiceMock.createStartContract();
deprecationService.getAllDeprecations = jest
.fn()
.mockRejectedValue(new Error('Internal Server Error'));
testBed = await setupOverviewPage({
deprecations: deprecationService,
});
});
const { component, exists } = testBed;
component.update();
expect(exists('requestErrorIconTip')).toBe(true);
});
});
describe('Elasticsearch deprecations', () => {
test('handles network failure', async () => {
const error = {
statusCode: 500,
error: 'Internal server error',
message: 'Internal server error',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupOverviewPage();
});
const { component, exists } = testBed;
component.update();
expect(exists('requestErrorIconTip')).toBe(true);
});
test('handles unauthorized error', async () => {
const error = {
statusCode: 403,
error: 'Forbidden',
message: 'Forbidden',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupOverviewPage();
});
const { component, exists } = testBed;
component.update();
expect(exists('unauthorizedErrorIconTip')).toBe(true);
});
test('handles partially upgraded error', 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 act(async () => {
testBed = await setupOverviewPage({ isReadOnlyMode: false });
});
const { component, exists } = testBed;
component.update();
expect(exists('partiallyUpgradedErrorIconTip')).toBe(true);
});
test('handles upgrade error', async () => {
const error = {
statusCode: 426,
error: 'Upgrade required',
message: 'There are some nodes running a different version of Elasticsearch',
attributes: {
allNodesUpgraded: true,
},
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupOverviewPage({ isReadOnlyMode: false });
});
const { component, exists } = testBed;
component.update();
expect(exists('upgradedErrorIconTip')).toBe(true);
});
});
});
});

View file

@ -0,0 +1,153 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { act } from 'react-dom/test-utils';
import { OverviewTestBed, setupOverviewPage, setupEnvironment } from '../../helpers';
import { DeprecationLoggingStatus } from '../../../../common/types';
import { DEPRECATION_LOGS_SOURCE_ID } from '../../../../common/constants';
const getLoggingResponse = (toggle: boolean): DeprecationLoggingStatus => ({
isDeprecationLogIndexingEnabled: toggle,
isDeprecationLoggingEnabled: toggle,
});
describe('Overview - Fix deprecation logs step', () => {
let testBed: OverviewTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
beforeEach(async () => {
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(getLoggingResponse(true));
testBed = await setupOverviewPage();
const { component } = testBed;
component.update();
});
afterAll(() => {
server.restore();
});
describe('Step 1 - Toggle log writing and collecting', () => {
test('toggles deprecation logging', async () => {
const { find, actions } = testBed;
httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse({
isDeprecationLogIndexingEnabled: false,
isDeprecationLoggingEnabled: false,
});
expect(find('deprecationLoggingToggle').props()['aria-checked']).toBe(true);
await actions.clickDeprecationToggle();
const latestRequest = server.requests[server.requests.length - 1];
expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toEqual({ isEnabled: false });
expect(find('deprecationLoggingToggle').props()['aria-checked']).toBe(false);
});
test('shows callout when only loggerDeprecation is enabled', async () => {
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({
isDeprecationLogIndexingEnabled: false,
isDeprecationLoggingEnabled: true,
});
await act(async () => {
testBed = await setupOverviewPage();
});
const { exists, component } = testBed;
component.update();
expect(exists('deprecationWarningCallout')).toBe(true);
});
test('handles network error when updating logging state', async () => {
const error = {
statusCode: 500,
error: 'Internal server error',
message: 'Internal server error',
};
const { actions, exists } = testBed;
httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse(undefined, error);
await actions.clickDeprecationToggle();
expect(exists('updateLoggingError')).toBe(true);
});
test('handles network error when fetching logging state', async () => {
const error = {
statusCode: 500,
error: 'Internal server error',
message: 'Internal server error',
};
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse(undefined, error);
await act(async () => {
testBed = await setupOverviewPage();
});
const { component, exists } = testBed;
component.update();
expect(exists('fetchLoggingError')).toBe(true);
});
});
describe('Step 2 - Analyze logs', () => {
beforeEach(async () => {
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({
isDeprecationLogIndexingEnabled: true,
isDeprecationLoggingEnabled: true,
});
});
test('Has a link to see logs in observability app', async () => {
await act(async () => {
testBed = await setupOverviewPage({
http: {
basePath: {
prepend: (url: string) => url,
},
},
});
});
const { component, exists, find } = testBed;
component.update();
expect(exists('viewObserveLogs')).toBe(true);
expect(find('viewObserveLogs').props().href).toBe(
`/app/logs/stream?sourceId=${DEPRECATION_LOGS_SOURCE_ID}`
);
});
test('Has a link to see logs in discover app', async () => {
await act(async () => {
testBed = await setupOverviewPage({
getUrlForApp: jest.fn((app, options) => {
return `${app}/${options.path}`;
}),
});
});
const { exists, component, find } = testBed;
component.update();
expect(exists('viewDiscoverLogs')).toBe(true);
expect(find('viewDiscoverLogs').props().href).toBe('/discover/logs');
});
});
});

View file

@ -0,0 +1,56 @@
/*
* 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 { DomainDeprecationDetails } from 'kibana/public';
import { ESUpgradeStatus } from '../../../../common/types';
export const esDeprecations: ESUpgradeStatus = {
totalCriticalDeprecations: 1,
cluster: [
{
level: 'critical',
message: 'Index Lifecycle Management poll interval is set too low',
url:
'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html#ilm-poll-interval-limit',
details:
'The Index Lifecycle Management poll interval setting [indices.lifecycle.poll_interval] is currently set to [500ms], but must be 1s or greater',
},
],
indices: [
{
level: 'warning',
message: 'translog retention settings are ignored',
url:
'https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html',
details:
'translog retention settings [index.translog.retention.size] and [index.translog.retention.age] are ignored because translog is no longer used in peer recoveries with soft-deletes enabled (default in 7.0 or later)',
index: 'settings',
},
],
};
export const esDeprecationsEmpty: ESUpgradeStatus = {
totalCriticalDeprecations: 0,
cluster: [],
indices: [],
};
export const kibanaDeprecations: DomainDeprecationDetails[] = [
{
correctiveActions: { manualSteps: ['test-step'] },
domainId: 'xpack.spaces',
level: 'critical',
message:
'Disabling the Spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)',
},
{
correctiveActions: { manualSteps: ['test-step'] },
domainId: 'xpack.spaces',
level: 'warning',
message: 'Sample warning deprecation',
},
];

View file

@ -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 { act } from 'react-dom/test-utils';
import { deprecationsServiceMock } from 'src/core/public/mocks';
import * as mockedResponses from './mocked_responses';
import { OverviewTestBed, setupOverviewPage, setupEnvironment } from '../../helpers';
describe('Overview - Fix deprecated settings step', () => {
let testBed: OverviewTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(mockedResponses.esDeprecations);
await act(async () => {
const deprecationService = deprecationsServiceMock.createStartContract();
deprecationService.getAllDeprecations = jest
.fn()
.mockReturnValue(mockedResponses.kibanaDeprecations);
testBed = await setupOverviewPage({
deprecations: deprecationService,
});
});
const { component } = testBed;
component.update();
});
afterAll(() => {
server.restore();
});
describe('ES deprecations', () => {
test('Shows deprecation warning and critical counts', () => {
const { exists, find } = testBed;
expect(exists('esStatsPanel')).toBe(true);
expect(find('esStatsPanel.warningDeprecations').text()).toContain('1');
expect(find('esStatsPanel.criticalDeprecations').text()).toContain('1');
});
test('Handles network failure', async () => {
const error = {
statusCode: 500,
error: 'Cant retrieve deprecations error',
message: 'Cant retrieve deprecations error',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupOverviewPage();
});
const { component, exists } = testBed;
component.update();
expect(exists('esRequestErrorIconTip')).toBe(true);
});
test('Hides deprecation counts if it doesnt have any', async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(mockedResponses.esDeprecationsEmpty);
await act(async () => {
testBed = await setupOverviewPage();
});
const { exists } = testBed;
expect(exists('noDeprecationsLabel')).toBe(true);
});
test('Stats panel navigates to deprecations list if clicked', () => {
const { component, exists, find } = testBed;
component.update();
expect(exists('esStatsPanel')).toBe(true);
expect(find('esStatsPanel').find('a').props().href).toBe('/es_deprecations/cluster');
});
describe('Renders ES errors', () => {
test('handles network failure', async () => {
const error = {
statusCode: 500,
error: 'Internal server error',
message: 'Internal server error',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupOverviewPage();
});
const { component, exists } = testBed;
component.update();
expect(exists('esRequestErrorIconTip')).toBe(true);
});
test('handles unauthorized error', async () => {
const error = {
statusCode: 403,
error: 'Forbidden',
message: 'Forbidden',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupOverviewPage();
});
const { component, exists } = testBed;
component.update();
expect(exists('unauthorizedErrorIconTip')).toBe(true);
});
test('handles partially upgraded error', 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 act(async () => {
testBed = await setupOverviewPage({ isReadOnlyMode: false });
});
const { component, exists } = testBed;
component.update();
expect(exists('partiallyUpgradedErrorIconTip')).toBe(true);
});
test('handles upgrade error', async () => {
const error = {
statusCode: 426,
error: 'Upgrade required',
message: 'There are some nodes running a different version of Elasticsearch',
attributes: {
allNodesUpgraded: true,
},
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupOverviewPage({ isReadOnlyMode: false });
});
const { component, exists } = testBed;
component.update();
expect(exists('upgradedErrorIconTip')).toBe(true);
});
});
});
describe('Kibana deprecations', () => {
test('Show deprecation warning and critical counts', () => {
const { exists, find } = testBed;
expect(exists('kibanaStatsPanel')).toBe(true);
expect(find('kibanaStatsPanel.warningDeprecations').text()).toContain('1');
expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain('1');
});
test('Handles network failure', async () => {
await act(async () => {
const deprecationService = deprecationsServiceMock.createStartContract();
deprecationService.getAllDeprecations = jest
.fn()
.mockRejectedValue(new Error('Internal Server Error'));
testBed = await setupOverviewPage({
deprecations: deprecationService,
});
});
const { component, exists } = testBed;
component.update();
expect(exists('kibanaRequestErrorIconTip')).toBe(true);
});
test('Hides deprecation count if it doesnt have any', async () => {
await act(async () => {
const deprecationService = deprecationsServiceMock.createStartContract();
deprecationService.getAllDeprecations = jest.fn().mockRejectedValue([]);
testBed = await setupOverviewPage({
deprecations: deprecationService,
});
});
const { exists } = testBed;
expect(exists('noDeprecationsLabel')).toBe(true);
expect(exists('kibanaStatsPanel.warningDeprecations')).toBe(false);
expect(exists('kibanaStatsPanel.criticalDeprecations')).toBe(false);
});
test('Stats panel navigates to deprecations list if clicked', () => {
const { component, exists, find } = testBed;
component.update();
expect(exists('kibanaStatsPanel')).toBe(true);
expect(find('kibanaStatsPanel').find('a').props().href).toBe('/kibana_deprecations');
});
});
});

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { act } from 'react-dom/test-utils';
import { OverviewTestBed, setupOverviewPage, setupEnvironment } from '../../helpers';
describe('Overview - Upgrade Step', () => {
let testBed: OverviewTestBed;
const { server } = setupEnvironment();
beforeEach(async () => {
testBed = await setupOverviewPage();
testBed.component.update();
});
afterAll(() => {
server.restore();
});
describe('Step 3 - Upgrade stack', () => {
test('Shows link to setup upgrade docs for on-prem installations', () => {
const { exists } = testBed;
expect(exists('upgradeSetupDocsLink')).toBe(true);
expect(exists('upgradeSetupCloudLink')).toBe(false);
});
test('Shows upgrade cta and link to docs for cloud installations', async () => {
await act(async () => {
testBed = await setupOverviewPage({
servicesOverrides: {
cloud: {
isCloudEnabled: true,
baseUrl: 'https://test.com',
cloudId: '1234',
},
},
});
});
const { component, exists, find } = testBed;
component.update();
expect(exists('upgradeSetupCloudLink')).toBe(true);
expect(exists('upgradeSetupDocsLink')).toBe(true);
expect(find('upgradeSetupCloudLink').props().href).toBe('https://test.com/deployments/1234');
});
});
});

View file

@ -27,3 +27,7 @@ export const indexSettingDeprecations = {
};
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_PATTERN = '.logs-deprecation.elasticsearch-default';

View file

@ -248,3 +248,8 @@ export interface MlOperation extends SavedObjectAttributes {
snapshotId: string;
jobId: string;
}
export interface DeprecationLoggingStatus {
isDeprecationLogIndexingEnabled: boolean;
isDeprecationLoggingEnabled: boolean;
}

View file

@ -8,7 +8,7 @@
"githubTeam": "kibana-stack-management"
},
"configPath": ["xpack", "upgrade_assistant"],
"requiredPlugins": ["management", "licensing", "features"],
"optionalPlugins": ["usageCollection"],
"requiredPlugins": ["management", "discover", "data", "licensing", "features", "infra"],
"optionalPlugins": ["usageCollection", "cloud"],
"requiredBundles": ["esUiShared", "kibanaReact"]
}

View file

@ -8,15 +8,22 @@
import React from 'react';
import { Router, Switch, Route, Redirect } from 'react-router-dom';
import { I18nStart, ScopedHistory } from 'src/core/public';
import { ApplicationStart } from 'kibana/public';
import { KibanaContextProvider } from '../shared_imports';
import { AppServicesContext } from '../types';
import { AppContextProvider, ContextValue, useAppContext } from './app_context';
import { ComingSoonPrompt } from './components/coming_soon_prompt';
import { EsDeprecationsContent } from './components/es_deprecations';
import { KibanaDeprecationsContent } from './components/kibana_deprecations';
import { DeprecationsOverview } from './components/overview';
import { Overview } from './components/overview';
import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public';
export interface AppDependencies extends ContextValue {
i18n: I18nStart;
history: ScopedHistory;
application: ApplicationStart;
services: AppServicesContext;
}
const App: React.FunctionComponent = () => {
@ -29,7 +36,7 @@ const App: React.FunctionComponent = () => {
return (
<Switch>
<Route exact path="/overview" component={DeprecationsOverview} />
<Route exact path="/overview" component={Overview} />
<Route exact path="/es_deprecations/:tabName" component={EsDeprecationsContent} />
<Route exact path="/kibana_deprecations" component={KibanaDeprecationsContent} />
<Redirect from="/" to="/overview" />
@ -45,12 +52,22 @@ export const AppWithRouter = ({ history }: { history: ScopedHistory }) => {
);
};
export const RootComponent = ({ i18n, history, ...contextValue }: AppDependencies) => {
export const RootComponent = ({
i18n,
history,
services,
application,
...contextValue
}: AppDependencies) => {
return (
<i18n.Context>
<AppContextProvider value={contextValue}>
<AppWithRouter history={history} />
</AppContextProvider>
</i18n.Context>
<RedirectAppLinks application={application}>
<i18n.Context>
<KibanaContextProvider services={services}>
<AppContextProvider value={contextValue}>
<AppWithRouter history={history} />
</AppContextProvider>
</KibanaContextProvider>
</i18n.Context>
</RedirectAppLinks>
);
};

View file

@ -1,60 +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 React from 'react';
import { EuiCallOut, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { useAppContext } from '../app_context';
export const LatestMinorBanner: React.FunctionComponent = () => {
const { docLinks, kibanaVersionInfo } = useAppContext();
const { ELASTIC_WEBSITE_URL } = docLinks;
const esDocBasePath = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference`;
const { currentMajor, nextMajor } = kibanaVersionInfo;
return (
<EuiCallOut
title={
<FormattedMessage
id="xpack.upgradeAssistant.tabs.incompleteCallout.calloutTitle"
defaultMessage="Issues list might be incomplete"
/>
}
color="warning"
iconType="help"
>
<p>
<FormattedMessage
id="xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.calloutDetail"
defaultMessage="The complete list of {breakingChangesDocButton} in Elasticsearch {nextEsVersion}
will be available in the final {currentEsVersion} minor release. When the list
is complete, this warning will go away."
values={{
breakingChangesDocButton: (
<EuiLink
href={`${esDocBasePath}/master/breaking-changes.html`} // Pointing to master here, as we want to direct users to breaking changes for the next major ES version
target="_blank"
external
>
<FormattedMessage
id="xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.breackingChangesDocButtonLabel"
defaultMessage="deprecations and breaking changes"
/>
</EuiLink>
),
nextEsVersion: `${nextMajor}.x`,
currentEsVersion: `${currentMajor}.x`,
}}
/>
</p>
</EuiCallOut>
);
};

View file

@ -1 +1,2 @@
@import 'steps';
@import 'review_logs_step/index';
@import 'fix_deprecation_logs_step/index';

View file

@ -1,6 +0,0 @@
.upgSteps {
.euiStep:last-child .euiStep__content {
padding-bottom: 0;
margin-bottom: 0;
}
}

View file

@ -1,201 +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 React, { useEffect, useState } from 'react';
import {
EuiButton,
EuiFlexItem,
EuiFlexGroup,
EuiText,
EuiTextColor,
EuiButtonEmpty,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useAppContext } from '../../app_context';
import { ResponseError } from '../../lib/api';
const i18nTexts = {
fetchErrorMessage: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorMessage',
{
defaultMessage: 'Could not retrieve logging information.',
}
),
reloadButtonLabel: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.reloadButtonLabel',
{
defaultMessage: 'Try again',
}
),
updateErrorMessage: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.updateErrorMessage',
{
defaultMessage: 'Could not update logging state.',
}
),
enabledMessage: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.enabledToastMessage',
{
defaultMessage: 'Log deprecated actions.',
}
),
disabledMessage: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.disabledToastMessage',
{
defaultMessage: 'Do not log deprecated actions.',
}
),
fetchButtonLabel: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogging.loadingLabel',
{
defaultMessage: 'Retrieving logging state',
}
),
enablingButtonLabel: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.enablingButtonLabel',
{
defaultMessage: 'Enabling deprecation logging',
}
),
disablingButtonLabel: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.disablingButtonLabel',
{
defaultMessage: 'Disabling deprecation logging',
}
),
enableButtonLabel: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.enableButtonLabel',
{
defaultMessage: 'Enable deprecation logging',
}
),
disableButtonLabel: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.disableButtonLabel',
{
defaultMessage: 'Disable deprecation logging',
}
),
fetchErrorButtonLabel: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorButtonLabel',
{
defaultMessage: 'Deprecation logging unavailable',
}
),
};
export const DeprecationLoggingToggle: React.FunctionComponent = () => {
const { api, notifications } = useAppContext();
const { data, error: fetchError, isLoading, resendRequest } = api.useLoadDeprecationLogging();
const [isEnabled, setIsEnabled] = useState<boolean | undefined>(undefined);
const [isUpdating, setIsUpdating] = useState(false);
const [updateError, setUpdateError] = useState<ResponseError | undefined>(undefined);
const getButtonLabel = () => {
if (isLoading) {
return i18nTexts.fetchButtonLabel;
}
if (isUpdating) {
return isEnabled ? i18nTexts.disablingButtonLabel : i18nTexts.enablingButtonLabel;
}
if (fetchError) {
return i18nTexts.fetchErrorButtonLabel;
}
if (isEnabled) {
return i18nTexts.disableButtonLabel;
}
return i18nTexts.enableButtonLabel;
};
useEffect(() => {
if (isLoading === false && data) {
setIsEnabled(data.isEnabled);
}
}, [data, isLoading]);
const toggleLogging = async () => {
const newIsEnabledValue = !isEnabled;
setIsUpdating(true);
const {
data: updatedLoggingState,
error: updateDeprecationError,
} = await api.updateDeprecationLogging({
isEnabled: newIsEnabledValue,
});
setIsUpdating(false);
if (updateDeprecationError) {
setUpdateError(updateDeprecationError);
} else if (updatedLoggingState) {
setIsEnabled(updatedLoggingState.isEnabled);
notifications.toasts.addSuccess(
updatedLoggingState.isEnabled ? i18nTexts.enabledMessage : i18nTexts.disabledMessage
);
}
};
return (
<EuiFlexGroup alignItems="baseline">
<EuiFlexItem grow={false}>
<EuiButton
data-test-subj="upgradeAssistantDeprecationToggle"
isLoading={isLoading || isUpdating}
onClick={toggleLogging}
color={isEnabled ? 'text' : 'primary'}
disabled={Boolean(fetchError)}
>
{getButtonLabel()}
</EuiButton>
</EuiFlexItem>
{fetchError && (
<EuiFlexItem>
<EuiText>
<p data-test-subj="fetchLoggingError">
<EuiTextColor color="danger">{i18nTexts.fetchErrorMessage}</EuiTextColor>
{fetchError.statusCode && fetchError.message && (
<>
{' '}
<EuiTextColor color="danger">{`${fetchError.statusCode}: ${fetchError.message}`}</EuiTextColor>
</>
)}{' '}
<EuiButtonEmpty iconType="refresh" onClick={resendRequest}>
{i18nTexts.reloadButtonLabel}
</EuiButtonEmpty>
</p>
</EuiText>
</EuiFlexItem>
)}
{updateError && (
<EuiFlexItem>
<EuiText>
<p data-test-subj="updateLoggingError">
<EuiTextColor color="danger">{i18nTexts.updateErrorMessage}</EuiTextColor>
{updateError.statusCode && updateError.message && (
<>
{' '}
<EuiTextColor color="danger">{`${updateError.statusCode}: ${updateError.message}`}</EuiTextColor>
</>
)}
</p>
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
);
};

View file

@ -1,166 +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 React, { FunctionComponent } from 'react';
import {
EuiLink,
EuiPanel,
EuiStat,
EuiTitle,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { RouteComponentProps } from 'react-router-dom';
import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public';
import { useAppContext } from '../../app_context';
import { EsStatsErrors } from './es_stats_error';
const i18nTexts = {
statsTitle: i18n.translate('xpack.upgradeAssistant.esDeprecationStats.statsTitle', {
defaultMessage: 'Elasticsearch',
}),
totalDeprecationsTitle: i18n.translate(
'xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTitle',
{
defaultMessage: 'Deprecations',
}
),
criticalDeprecationsTitle: i18n.translate(
'xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsTitle',
{
defaultMessage: 'Critical',
}
),
viewDeprecationsLink: i18n.translate(
'xpack.upgradeAssistant.esDeprecationStats.viewDeprecationsLinkText',
{
defaultMessage: 'View deprecations',
}
),
loadingText: i18n.translate('xpack.upgradeAssistant.esDeprecationStats.loadingText', {
defaultMessage: 'Loading Elasticsearch deprecation stats…',
}),
getCriticalDeprecationsMessage: (criticalDeprecations: number) =>
i18n.translate('xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsLabel', {
defaultMessage: 'This cluster has {criticalDeprecations} critical deprecations',
values: {
criticalDeprecations,
},
}),
getTotalDeprecationsTooltip: (clusterCount: number, indexCount: number) =>
i18n.translate('xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTooltip', {
defaultMessage:
'This cluster is using {clusterCount} deprecated cluster settings and {indexCount} deprecated index settings',
values: {
clusterCount,
indexCount,
},
}),
};
interface Props {
history: RouteComponentProps['history'];
}
export const ESDeprecationStats: FunctionComponent<Props> = ({ history }) => {
const { api } = useAppContext();
const { data: esDeprecations, isLoading, error } = api.useLoadUpgradeStatus();
const allDeprecations = esDeprecations?.cluster?.concat(esDeprecations?.indices) ?? [];
const criticalDeprecations = allDeprecations.filter(
(deprecation) => deprecation.level === 'critical'
);
return (
<EuiPanel data-test-subj="esStatsPanel" hasShadow={false} hasBorder={true}>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="baseline">
<EuiFlexItem>
<EuiTitle size="s">
<h2>{i18nTexts.statsTitle}</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink
{...reactRouterNavigate(history, '/es_deprecations/cluster')}
data-test-subj="esDeprecationsLink"
>
{i18nTexts.viewDeprecationsLink}
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem>
<EuiStat
data-test-subj="totalDeprecations"
title={error ? '--' : allDeprecations.length}
description={
<>
<span>{i18nTexts.totalDeprecationsTitle}</span>{' '}
<EuiIconTip
content={i18nTexts.getTotalDeprecationsTooltip(
esDeprecations?.cluster.length ?? 0,
esDeprecations?.indices.length ?? 0
)}
position="right"
iconProps={{
tabIndex: -1,
}}
/>
</>
}
isLoading={isLoading}
>
{error === null && (
<EuiScreenReaderOnly>
<p>
{isLoading
? i18nTexts.loadingText
: i18nTexts.getTotalDeprecationsTooltip(
esDeprecations?.cluster.length ?? 0,
esDeprecations?.indices.length ?? 0
)}
</p>
</EuiScreenReaderOnly>
)}
</EuiStat>
</EuiFlexItem>
<EuiFlexItem>
<EuiStat
data-test-subj="criticalDeprecations"
title={error ? '--' : criticalDeprecations.length}
description={i18nTexts.criticalDeprecationsTitle}
titleColor="danger"
isLoading={isLoading}
>
{error === null && (
<EuiScreenReaderOnly>
<p>
{isLoading
? i18nTexts.loadingText
: i18nTexts.getCriticalDeprecationsMessage(criticalDeprecations.length)}
</p>
</EuiScreenReaderOnly>
)}
{error && <EsStatsErrors error={error} />}
</EuiStat>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
};

View file

@ -0,0 +1 @@
@import 'deprecation_logging_toggle/deprecation_logging_toggle';

View file

@ -0,0 +1,7 @@
// When you have a flex item with an EuiSwitch and replace it with
// an EuiLoadingSpinner you end up with a slight 2px difference between
// them. With this selector we offset the difference so that the content
// of the page doesnt jump when toggling between states.
.upgToggleLoading > .upgLoadingItem {
margin: $euiSizeM / 2;
}

View file

@ -0,0 +1,154 @@
/*
* 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, { useState, FunctionComponent } from 'react';
import {
EuiSwitch,
EuiFlexItem,
EuiFlexGroup,
EuiText,
EuiPopover,
EuiLink,
EuiTextColor,
EuiButtonEmpty,
EuiLoadingSpinner,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { ResponseError } from '../../../../lib/api';
import { DeprecationLoggingPreviewProps } from '../../../types';
const i18nTexts = {
fetchErrorMessage: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorMessage',
{
defaultMessage: 'Could not retrieve logging information.',
}
),
reloadButtonLabel: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.reloadButtonLabel',
{
defaultMessage: 'Try again',
}
),
updateErrorMessage: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.updateErrorMessage',
{
defaultMessage: 'Could not update logging state.',
}
),
errorLabel: i18n.translate('xpack.upgradeAssistant.overview.deprecationLogs.errorLabel', {
defaultMessage: 'Error',
}),
buttonLabel: i18n.translate('xpack.upgradeAssistant.overview.deprecationLogs.buttonLabel', {
defaultMessage: 'Enable deprecation logging and indexing',
}),
loadingLogsLabel: i18n.translate('xpack.upgradeAssistant.overview.loadingLogsLabel', {
defaultMessage: 'Loading log collection state…',
}),
};
const ErrorDetailsLink = ({ error }: { error: ResponseError }) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const onButtonClick = () => setIsPopoverOpen(!isPopoverOpen);
const closePopover = () => setIsPopoverOpen(false);
if (!error.statusCode || !error.message) {
return null;
}
const button = (
<EuiLink color="danger" onClick={onButtonClick}>
{i18nTexts.errorLabel} {error.statusCode}
</EuiLink>
);
return (
<EuiPopover button={button} isOpen={isPopoverOpen} closePopover={closePopover}>
<EuiText style={{ width: 300 }}>
<p>{error.message}</p>
</EuiText>
</EuiPopover>
);
};
export const DeprecationLoggingToggle: FunctionComponent<DeprecationLoggingPreviewProps> = ({
isDeprecationLogIndexingEnabled,
isLoading,
isUpdating,
fetchError,
updateError,
resendRequest,
toggleLogging,
}) => {
if (isLoading) {
return (
<EuiFlexGroup gutterSize="s" alignItems="center" className="upgToggleLoading">
<EuiFlexItem grow={false} className="upgLoadingItem">
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
<EuiFlexItem grow={false}>{i18nTexts.loadingLogsLabel}</EuiFlexItem>
</EuiFlexGroup>
);
}
if (fetchError) {
return (
<EuiFlexGroup gutterSize="none" alignItems="center">
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="flexEnd" data-test-subj="fetchLoggingError">
<EuiFlexItem grow={false}>
<EuiTextColor color="danger">{i18nTexts.fetchErrorMessage}</EuiTextColor>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ErrorDetailsLink error={fetchError} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="refresh" onClick={resendRequest}>
{i18nTexts.reloadButtonLabel}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
);
}
return (
<EuiFlexGroup gutterSize="m" alignItems="center">
<EuiFlexItem grow={false}>
<EuiSwitch
data-test-subj="deprecationLoggingToggle"
label={i18nTexts.buttonLabel}
checked={!!isDeprecationLogIndexingEnabled}
onChange={toggleLogging}
disabled={Boolean(fetchError) || isUpdating}
/>
</EuiFlexItem>
{updateError && (
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="flexEnd" data-test-subj="updateLoggingError">
<EuiFlexItem grow={false}>
<EuiTextColor color="danger">{i18nTexts.updateErrorMessage}</EuiTextColor>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ErrorDetailsLink error={updateError} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
)}
{isUpdating && (
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
)}
</EuiFlexGroup>
);
};

View file

@ -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 { DeprecationLoggingToggle } from './deprecation_logging_toggle';

View file

@ -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, { FunctionComponent, useState, useEffect } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiLink, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiPanel, EuiText } from '@elastic/eui';
import { useAppContext } from '../../../app_context';
import { useKibana, DataPublicPluginStart } from '../../../../shared_imports';
import {
DEPRECATION_LOGS_INDEX_PATTERN,
DEPRECATION_LOGS_SOURCE_ID,
} from '../../../../../common/constants';
const getDeprecationIndexPatternId = async (dataService: DataPublicPluginStart) => {
const { indexPatterns: indexPatternService } = dataService;
const results = await indexPatternService.find(DEPRECATION_LOGS_INDEX_PATTERN);
// Since the find might return also results with wildcard matchers we need to find the
// index pattern that has an exact match with our title.
const deprecationIndexPattern = results.find(
(result) => result.title === DEPRECATION_LOGS_INDEX_PATTERN
);
if (deprecationIndexPattern) {
return deprecationIndexPattern.id;
} else {
const newIndexPattern = await indexPatternService.createAndSave({
title: DEPRECATION_LOGS_INDEX_PATTERN,
allowNoIndex: true,
});
return newIndexPattern.id;
}
};
const DiscoverAppLink: FunctionComponent = () => {
const { getUrlForApp } = useAppContext();
const { data: dataService, discover: discoverService } = useKibana().services;
const [discoveryUrl, setDiscoveryUrl] = useState<string | undefined>();
useEffect(() => {
const getDiscoveryUrl = async () => {
const indexPatternId = await getDeprecationIndexPatternId(dataService);
const appLocation = await discoverService?.locator?.getLocation({ indexPatternId });
const result = getUrlForApp(appLocation?.app as string, {
path: appLocation?.path,
});
setDiscoveryUrl(result);
};
getDiscoveryUrl();
}, [dataService, discoverService, getUrlForApp]);
return (
<EuiLink href={discoveryUrl} target="_blank" data-test-subj="viewDiscoverLogs">
<FormattedMessage
id="xpack.upgradeAssistant.overview.viewDiscoverResultsAction"
defaultMessage="Analyze logs in Discover"
/>
</EuiLink>
);
};
const ObservabilityAppLink: FunctionComponent = () => {
const { http } = useAppContext();
const logStreamUrl = http?.basePath?.prepend(
`/app/logs/stream?sourceId=${DEPRECATION_LOGS_SOURCE_ID}`
);
return (
<EuiLink href={logStreamUrl} target="_blank" data-test-subj="viewObserveLogs">
<FormattedMessage
id="xpack.upgradeAssistant.overview.viewObservabilityResultsAction"
defaultMessage="View deprecation logs in Observability"
/>
</EuiLink>
);
};
export const ExternalLinks: FunctionComponent = () => {
return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiPanel>
<EuiText size="s">
<p>
<FormattedMessage
id="xpack.upgradeAssistant.overview.observe.observabilityDescription"
defaultMessage="Get insight into which deprecated APIs are being used and what applications you need to update."
/>
</p>
</EuiText>
<EuiSpacer size="m" />
<ObservabilityAppLink />
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel>
<EuiText size="s">
<p>
<FormattedMessage
id="xpack.upgradeAssistant.overview.observe.discoveryDescription"
defaultMessage="Search and filter the deprecation logs to understand the types of changes you need to make."
/>
</p>
</EuiText>
<EuiSpacer size="m" />
<DiscoverAppLink />
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
};

View file

@ -0,0 +1,90 @@
/*
* 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 } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiText, EuiSpacer, EuiPanel, EuiCallOut } from '@elastic/eui';
import type { EuiStepProps } from '@elastic/eui/src/components/steps/step';
import { ExternalLinks } from './external_links';
import { useDeprecationLogging } from './use_deprecation_logging';
import { DeprecationLoggingToggle } from './deprecation_logging_toggle';
const i18nTexts = {
identifyStepTitle: i18n.translate('xpack.upgradeAssistant.overview.identifyStepTitle', {
defaultMessage: 'Identify deprecated API use and update your applications',
}),
toggleTitle: i18n.translate('xpack.upgradeAssistant.overview.toggleTitle', {
defaultMessage: 'Log Elasticsearch deprecation warnings',
}),
analyzeTitle: i18n.translate('xpack.upgradeAssistant.overview.analyzeTitle', {
defaultMessage: 'Analyze deprecation logs',
}),
onlyLogWritingEnabledTitle: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.deprecationWarningTitle',
{
defaultMessage: 'Your logs are being written to the logs directory',
}
),
onlyLogWritingEnabledBody: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.deprecationWarningBody',
{
defaultMessage:
'Go to your logs directory to view the deprecation logs or enable log collecting to see them in the UI.',
}
),
};
const DeprecationLogsPreview: FunctionComponent = () => {
const state = useDeprecationLogging();
return (
<>
<EuiText>
<h4>{i18nTexts.toggleTitle}</h4>
</EuiText>
<EuiSpacer size="m" />
<EuiPanel>
<DeprecationLoggingToggle {...state} />
</EuiPanel>
{state.onlyDeprecationLogWritingEnabled && (
<>
<EuiSpacer size="m" />
<EuiCallOut
title={i18nTexts.onlyLogWritingEnabledTitle}
color="warning"
iconType="help"
data-test-subj="deprecationWarningCallout"
>
<p>{i18nTexts.onlyLogWritingEnabledBody}</p>
</EuiCallOut>
</>
)}
{state.isDeprecationLogIndexingEnabled && (
<>
<EuiSpacer size="xl" />
<EuiText>
<h4>{i18nTexts.analyzeTitle}</h4>
</EuiText>
<EuiSpacer size="m" />
<ExternalLinks />
</>
)}
</>
);
};
export const getFixDeprecationLogsStep = (): EuiStepProps => {
return {
title: i18nTexts.identifyStepTitle,
status: 'incomplete',
children: <DeprecationLogsPreview />,
};
};

View file

@ -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 { getFixDeprecationLogsStep } from './fix_deprecation_logs_step';

View file

@ -0,0 +1,89 @@
/*
* 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 { useState, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { useAppContext } from '../../../app_context';
import { ResponseError } from '../../../lib/api';
import { DeprecationLoggingPreviewProps } from '../../types';
const i18nTexts = {
enabledMessage: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.enabledToastMessage',
{
defaultMessage: 'Deprecated API requests will be logged and indexed.',
}
),
disabledMessage: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLogs.disabledToastMessage',
{
defaultMessage: 'Deprecated API requests will not be logged.',
}
),
};
export const useDeprecationLogging = (): DeprecationLoggingPreviewProps => {
const { api, notifications } = useAppContext();
const { data, error: fetchError, isLoading, resendRequest } = api.useLoadDeprecationLogging();
const [isDeprecationLogIndexingEnabled, setIsDeprecationLogIndexingEnabled] = useState(false);
const [isUpdating, setIsUpdating] = useState(false);
const [onlyDeprecationLogWritingEnabled, setOnlyDeprecationLogWritingEnabled] = useState(false);
const [updateError, setUpdateError] = useState<ResponseError | undefined>();
useEffect(() => {
if (!isLoading && data) {
const {
isDeprecationLogIndexingEnabled: isIndexingEnabled,
isDeprecationLoggingEnabled,
} = data;
setIsDeprecationLogIndexingEnabled(isIndexingEnabled);
if (!isIndexingEnabled && isDeprecationLoggingEnabled) {
setOnlyDeprecationLogWritingEnabled(true);
}
}
}, [data, isLoading]);
const toggleLogging = async () => {
setIsUpdating(true);
const {
data: updatedLoggingState,
error: updateDeprecationError,
} = await api.updateDeprecationLogging({
isEnabled: !isDeprecationLogIndexingEnabled,
});
setIsUpdating(false);
setOnlyDeprecationLogWritingEnabled(false);
if (updateDeprecationError) {
setUpdateError(updateDeprecationError);
} else if (updatedLoggingState) {
setIsDeprecationLogIndexingEnabled(updatedLoggingState.isDeprecationLogIndexingEnabled);
notifications.toasts.addSuccess(
updatedLoggingState.isDeprecationLogIndexingEnabled
? i18nTexts.enabledMessage
: i18nTexts.disabledMessage
);
}
};
return {
isDeprecationLogIndexingEnabled,
isLoading,
isUpdating,
toggleLogging,
fetchError,
updateError,
resendRequest,
onlyDeprecationLogWritingEnabled,
};
};

View file

@ -5,4 +5,4 @@
* 2.0.
*/
export { DeprecationsOverview } from './overview';
export { Overview } from './overview';

View file

@ -1,194 +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 React, { FunctionComponent, useEffect, useState } from 'react';
import {
EuiLink,
EuiPanel,
EuiStat,
EuiTitle,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { RouteComponentProps } from 'react-router-dom';
import type { DomainDeprecationDetails } from 'kibana/public';
import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public';
import { useAppContext } from '../../app_context';
const i18nTexts = {
statsTitle: i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.statsTitle', {
defaultMessage: 'Kibana',
}),
totalDeprecationsTitle: i18n.translate(
'xpack.upgradeAssistant.kibanaDeprecationStats.totalDeprecationsTitle',
{
defaultMessage: 'Deprecations',
}
),
criticalDeprecationsTitle: i18n.translate(
'xpack.upgradeAssistant.kibanaDeprecationStats.criticalDeprecationsTitle',
{
defaultMessage: 'Critical',
}
),
viewDeprecationsLink: i18n.translate(
'xpack.upgradeAssistant.kibanaDeprecationStats.viewDeprecationsLinkText',
{
defaultMessage: 'View deprecations',
}
),
loadingError: i18n.translate(
'xpack.upgradeAssistant.kibanaDeprecationStats.loadingErrorMessage',
{
defaultMessage: 'An error occurred while retrieving Kibana deprecations.',
}
),
loadingText: i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.loadingText', {
defaultMessage: 'Loading Kibana deprecation stats…',
}),
getCriticalDeprecationsMessage: (criticalDeprecations: number) =>
i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.criticalDeprecationsLabel', {
defaultMessage: 'Kibana has {criticalDeprecations} critical deprecations',
values: {
criticalDeprecations,
},
}),
getTotalDeprecationsMessage: (totalDeprecations: number) =>
i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.totalDeprecationsLabel', {
defaultMessage: 'Kibana has {totalDeprecations} total deprecations',
values: {
totalDeprecations,
},
}),
};
interface Props {
history: RouteComponentProps['history'];
}
export const KibanaDeprecationStats: FunctionComponent<Props> = ({ history }) => {
const { deprecations } = useAppContext();
const [kibanaDeprecations, setKibanaDeprecations] = useState<
DomainDeprecationDetails[] | undefined
>(undefined);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | undefined>(undefined);
useEffect(() => {
async function getAllDeprecations() {
setIsLoading(true);
try {
const response = await deprecations.getAllDeprecations();
setKibanaDeprecations(response);
} catch (e) {
setError(e);
}
setIsLoading(false);
}
getAllDeprecations();
}, [deprecations]);
return (
<EuiPanel data-test-subj="kibanaStatsPanel" hasShadow={false} hasBorder={true}>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="baseline">
<EuiFlexItem>
<EuiTitle size="s">
<h2>{i18nTexts.statsTitle}</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink
{...reactRouterNavigate(history, '/kibana_deprecations')}
data-test-subj="kibanaDeprecationsLink"
>
{i18nTexts.viewDeprecationsLink}
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
<EuiFlexGroup>
<EuiFlexItem>
<EuiStat
data-test-subj="totalDeprecations"
title={error ? '--' : kibanaDeprecations?.length ?? '0'}
description={i18nTexts.totalDeprecationsTitle}
isLoading={isLoading}
>
{error === undefined && (
<EuiScreenReaderOnly>
<p>
{isLoading
? i18nTexts.loadingText
: i18nTexts.getTotalDeprecationsMessage(kibanaDeprecations?.length ?? 0)}
</p>
</EuiScreenReaderOnly>
)}
</EuiStat>
</EuiFlexItem>
<EuiFlexItem>
<EuiStat
data-test-subj="criticalDeprecations"
title={
kibanaDeprecations
? kibanaDeprecations.filter((deprecation) => deprecation.level === 'critical')
?.length ?? '0'
: '--'
}
description={i18nTexts.criticalDeprecationsTitle}
titleColor="danger"
isLoading={isLoading}
>
{error === undefined && (
<EuiScreenReaderOnly>
<p>
{isLoading
? i18nTexts.loadingText
: i18nTexts.getCriticalDeprecationsMessage(
kibanaDeprecations
? kibanaDeprecations.filter(
(deprecation) => deprecation.level === 'critical'
)?.length ?? 0
: 0
)}
</p>
</EuiScreenReaderOnly>
)}
{error && (
<>
<EuiSpacer size="s" />
<EuiIconTip
type="alert"
color="danger"
size="l"
content={i18nTexts.loadingError}
iconProps={{
'data-test-subj': 'requestErrorIconTip',
}}
/>
</>
)}
</EuiStat>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
};

View file

@ -8,71 +8,26 @@
import React, { FunctionComponent, useEffect } from 'react';
import {
EuiPageContentBody,
EuiSteps,
EuiText,
EuiPageHeader,
EuiButtonEmpty,
EuiFlexItem,
EuiFlexGroup,
EuiSpacer,
EuiTitle,
EuiLink,
EuiPageBody,
EuiPageContent,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { RouteComponentProps } from 'react-router-dom';
import { useAppContext } from '../../app_context';
import { LatestMinorBanner } from '../latest_minor_banner';
import { ESDeprecationStats } from './es_stats';
import { KibanaDeprecationStats } from './kibana_stats';
import { DeprecationLoggingToggle } from './deprecation_logging_toggle';
import { getReviewLogsStep } from './review_logs_step';
import { getFixDeprecationLogsStep } from './fix_deprecation_logs_step';
import { getUpgradeStep } from './upgrade_step';
const i18nTexts = {
pageTitle: i18n.translate('xpack.upgradeAssistant.overview.pageTitle', {
defaultMessage: 'Upgrade Assistant',
}),
pageDescription: i18n.translate('xpack.upgradeAssistant.overview.pageDescription', {
defaultMessage:
'Prepare to upgrade by identifying deprecated settings and updating your configuration.',
}),
docLink: i18n.translate('xpack.upgradeAssistant.overview.documentationLinkText', {
defaultMessage: 'Documentation',
}),
deprecationLoggingTitle: i18n.translate(
'xpack.upgradeAssistant.overview.deprecationLoggingTitle',
{
defaultMessage: 'Deprecation logs',
}
),
getDeprecationLoggingDescription: (nextMajor: string, href: string) => (
<FormattedMessage
id="xpack.upgradeAssistant.overview.deprecationLoggingDescription"
defaultMessage="Enable {deprecationLoggingLink} to see if you are using deprecated features that will not be available after you upgrade to Elastic {nextMajor}."
values={{
nextMajor,
deprecationLoggingLink: (
<EuiLink href={href} target="_blank">
{i18n.translate(
'xpack.upgradeAssistant.deprecationLoggingDescription.deprecationLoggingLink',
{
defaultMessage: 'deprecation logging',
}
)}
</EuiLink>
),
}}
/>
),
};
interface Props {
history: RouteComponentProps['history'];
}
export const DeprecationsOverview: FunctionComponent<Props> = ({ history }) => {
export const Overview: FunctionComponent = () => {
const { kibanaVersionInfo, breadcrumbs, docLinks, api } = useAppContext();
const { nextMajor } = kibanaVersionInfo;
const { currentMajor } = kibanaVersionInfo;
useEffect(() => {
async function sendTelemetryData() {
@ -89,68 +44,51 @@ export const DeprecationsOverview: FunctionComponent<Props> = ({ history }) => {
}, [breadcrumbs]);
return (
<div data-test-subj="overviewPageContent">
<EuiPageHeader
bottomBorder
pageTitle={i18nTexts.pageTitle}
description={i18nTexts.pageDescription}
rightSideItems={[
<EuiButtonEmpty
href={docLinks.links.upgradeAssistant}
target="_blank"
iconType="help"
data-test-subj="documentationLink"
>
{i18nTexts.docLink}
</EuiButtonEmpty>,
]}
/>
<EuiPageBody restrictWidth={true}>
<EuiPageContent horizontalPosition="center" color="transparent" paddingSize="none">
<EuiPageHeader
bottomBorder
pageTitle={i18n.translate('xpack.upgradeAssistant.overview.pageTitle', {
defaultMessage: 'Upgrade Assistant',
})}
description={i18n.translate('xpack.upgradeAssistant.overview.pageDescription', {
defaultMessage: 'Get ready for the next version of the Elastic Stack!',
})}
rightSideItems={[
<EuiButtonEmpty
href={docLinks.links.upgradeAssistant}
target="_blank"
iconType="help"
data-test-subj="documentationLink"
>
<FormattedMessage
id="xpack.upgradeAssistant.overview.documentationLinkText"
defaultMessage="Documentation"
/>
</EuiButtonEmpty>,
]}
>
<EuiText>
<EuiLink href={docLinks.links.elasticsearch.releaseHighlights} target="_blank">
<FormattedMessage
id="xpack.upgradeAssistant.overview.whatsNewLink"
defaultMessage="What's new in version {currentMajor}.0?"
values={{ currentMajor }}
/>
</EuiLink>
</EuiText>
</EuiPageHeader>
<EuiSpacer size="l" />
<EuiSpacer size="l" />
<EuiPageContentBody>
<>
{/* Remove this in last minor of the current major (e.g., 7.15) */}
<LatestMinorBanner />
<EuiSpacer size="xl" />
{/* Deprecation stats */}
<EuiFlexGroup>
<EuiFlexItem>
<ESDeprecationStats history={history} />
</EuiFlexItem>
<EuiFlexItem>
<KibanaDeprecationStats history={history} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer />
{/* Deprecation logging */}
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="s">
<h2>{i18nTexts.deprecationLoggingTitle}</h2>
</EuiTitle>
<EuiText>
<p>
{i18nTexts.getDeprecationLoggingDescription(
`${nextMajor}.x`,
docLinks.links.elasticsearch.deprecationLogging
)}
</p>
</EuiText>
<EuiSpacer size="m" />
<DeprecationLoggingToggle />
</EuiFlexItem>
</EuiFlexGroup>
</>
</EuiPageContentBody>
</div>
<EuiSteps
steps={[
getReviewLogsStep({ currentMajor }),
getFixDeprecationLogsStep(),
getUpgradeStep({ docLinks, currentMajor }),
]}
/>
</EuiPageContent>
</EuiPageBody>
);
};

View file

@ -0,0 +1,2 @@
@import 'stats_panel';
@import 'no_deprecations/no_deprecations';

View file

@ -0,0 +1,6 @@
// Used by both es_stats and kibana_stats panel for having the EuiPopover Icon
// for errors shown next to the title without having to resort to wrapping everything
// with EuiFlexGroups.
.upgWarningIcon {
margin-left: $euiSizeS;
}

View file

@ -0,0 +1,153 @@
/*
* 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 } from 'react';
import { useHistory } from 'react-router-dom';
import {
EuiStat,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
EuiCard,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public';
import { getDeprecationsUpperLimit } from '../../../../lib/utils';
import { useAppContext } from '../../../../app_context';
import { EsStatsErrors } from './es_stats_error';
import { NoDeprecations } from '../no_deprecations';
const i18nTexts = {
statsTitle: i18n.translate('xpack.upgradeAssistant.esDeprecationStats.statsTitle', {
defaultMessage: 'Elasticsearch',
}),
warningDeprecationsTitle: i18n.translate(
'xpack.upgradeAssistant.esDeprecationStats.warningDeprecationsTitle',
{
defaultMessage: 'Warning',
}
),
criticalDeprecationsTitle: i18n.translate(
'xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsTitle',
{
defaultMessage: 'Critical',
}
),
loadingText: i18n.translate('xpack.upgradeAssistant.esDeprecationStats.loadingText', {
defaultMessage: 'Loading Elasticsearch deprecation stats…',
}),
getCriticalDeprecationsMessage: (criticalDeprecations: number) =>
i18n.translate('xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsLabel', {
defaultMessage: 'This cluster has {criticalDeprecations} critical deprecations',
values: {
criticalDeprecations,
},
}),
getWarningDeprecationMessage: (clusterCount: number, indexCount: number) =>
i18n.translate('xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTooltip', {
defaultMessage:
'This cluster is using {clusterCount} deprecated cluster settings and {indexCount} deprecated index settings',
values: {
clusterCount,
indexCount,
},
}),
};
export const ESDeprecationStats: FunctionComponent = () => {
const history = useHistory();
const { api } = useAppContext();
const { data: esDeprecations, isLoading, error } = api.useLoadUpgradeStatus();
const allDeprecations = esDeprecations?.cluster?.concat(esDeprecations?.indices) ?? [];
const warningDeprecations = allDeprecations.filter(
(deprecation) => deprecation.level === 'warning'
);
const criticalDeprecations = allDeprecations.filter(
(deprecation) => deprecation.level === 'critical'
);
const hasWarnings = warningDeprecations.length > 0;
const hasCritical = criticalDeprecations.length > 0;
const hasNoDeprecations = !isLoading && !error && !hasWarnings && !hasCritical;
const shouldRenderStat = (forSection: boolean) => error || isLoading || forSection;
return (
<EuiCard
data-test-subj="esStatsPanel"
layout="horizontal"
title={
<>
{i18nTexts.statsTitle}
{error && <EsStatsErrors error={error} />}
</>
}
{...(!hasNoDeprecations && reactRouterNavigate(history, '/es_deprecations/cluster'))}
>
<EuiSpacer />
<EuiFlexGroup>
{hasNoDeprecations && (
<EuiFlexItem>
<NoDeprecations />
</EuiFlexItem>
)}
{shouldRenderStat(hasCritical) && (
<EuiFlexItem>
<EuiStat
data-test-subj="criticalDeprecations"
title={error ? '--' : getDeprecationsUpperLimit(criticalDeprecations.length)}
titleElement="span"
description={i18nTexts.criticalDeprecationsTitle}
titleColor="danger"
isLoading={isLoading}
>
{error === null && (
<EuiScreenReaderOnly>
<p>
{isLoading
? i18nTexts.loadingText
: i18nTexts.getCriticalDeprecationsMessage(criticalDeprecations.length)}
</p>
</EuiScreenReaderOnly>
)}
</EuiStat>
</EuiFlexItem>
)}
{shouldRenderStat(hasWarnings) && (
<EuiFlexItem>
<EuiStat
data-test-subj="warningDeprecations"
title={error ? '--' : getDeprecationsUpperLimit(warningDeprecations.length)}
titleElement="span"
description={i18nTexts.warningDeprecationsTitle}
isLoading={isLoading}
>
{!error && (
<EuiScreenReaderOnly>
<p>
{isLoading
? i18nTexts.loadingText
: i18nTexts.getWarningDeprecationMessage(
esDeprecations?.cluster.length ?? 0,
esDeprecations?.indices.length ?? 0
)}
</p>
</EuiScreenReaderOnly>
)}
</EuiStat>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiCard>
);
};

View file

@ -7,9 +7,9 @@
import React from 'react';
import { EuiIconTip, EuiSpacer } from '@elastic/eui';
import { ResponseError } from '../../lib/api';
import { getEsDeprecationError } from '../../lib/es_deprecation_errors';
import { EuiIconTip } from '@elastic/eui';
import { ResponseError } from '../../../../lib/api';
import { getEsDeprecationError } from '../../../../lib/es_deprecation_errors';
interface Props {
error: ResponseError;
@ -26,7 +26,7 @@ export const EsStatsErrors: React.FunctionComponent<Props> = ({ error }) => {
<EuiIconTip
type="alert"
color="danger"
size="l"
size="m"
content={message}
iconProps={{
'data-test-subj': 'unauthorizedErrorIconTip',
@ -39,7 +39,7 @@ export const EsStatsErrors: React.FunctionComponent<Props> = ({ error }) => {
<EuiIconTip
type="alert"
color="warning"
size="l"
size="m"
content={message}
iconProps={{
'data-test-subj': 'partiallyUpgradedErrorIconTip',
@ -52,7 +52,7 @@ export const EsStatsErrors: React.FunctionComponent<Props> = ({ error }) => {
<EuiIconTip
type="alert"
color="warning"
size="l"
size="m"
content={message}
iconProps={{
'data-test-subj': 'upgradedErrorIconTip',
@ -66,19 +66,14 @@ export const EsStatsErrors: React.FunctionComponent<Props> = ({ error }) => {
<EuiIconTip
type="alert"
color="danger"
size="l"
size="m"
content={message}
iconProps={{
'data-test-subj': 'requestErrorIconTip',
'data-test-subj': 'esRequestErrorIconTip',
}}
/>
);
}
return (
<>
<EuiSpacer size="s" />
{iconContent}
</>
);
return <span className="upgWarningIcon">{iconContent}</span>;
};

View file

@ -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 { ESDeprecationStats } from './es_stats';

View file

@ -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 { getReviewLogsStep } from './review_logs_step';

View file

@ -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 { KibanaDeprecationStats } from './kibana_stats';

View file

@ -0,0 +1,188 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { FunctionComponent, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import {
EuiCard,
EuiStat,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { DomainDeprecationDetails } from 'kibana/public';
import { reactRouterNavigate } from '../../../../../../../../../src/plugins/kibana_react/public';
import { getDeprecationsUpperLimit } from '../../../../lib/utils';
import { useAppContext } from '../../../../app_context';
import { NoDeprecations } from '../no_deprecations';
const i18nTexts = {
statsTitle: i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.statsTitle', {
defaultMessage: 'Kibana',
}),
warningDeprecationsTitle: i18n.translate(
'xpack.upgradeAssistant.kibanaDeprecationStats.warningDeprecationsTitle',
{
defaultMessage: 'Warning',
}
),
criticalDeprecationsTitle: i18n.translate(
'xpack.upgradeAssistant.kibanaDeprecationStats.criticalDeprecationsTitle',
{
defaultMessage: 'Critical',
}
),
loadingError: i18n.translate(
'xpack.upgradeAssistant.kibanaDeprecationStats.loadingErrorMessage',
{
defaultMessage: 'An error occurred while retrieving Kibana deprecations.',
}
),
loadingText: i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.loadingText', {
defaultMessage: 'Loading Kibana deprecation stats…',
}),
getCriticalDeprecationsMessage: (criticalDeprecations: number) =>
i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.criticalDeprecationsLabel', {
defaultMessage:
'Kibana has {criticalDeprecations} critical {criticalDeprecations, plural, one {deprecation} other {deprecations}}',
values: {
criticalDeprecations,
},
}),
getWarningDeprecationsMessage: (warningDeprecations: number) =>
i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.getWarningDeprecationsMessage', {
defaultMessage:
'Kibana has {warningDeprecations} warning {warningDeprecations, plural, one {deprecation} other {deprecations}}',
values: {
warningDeprecations,
},
}),
};
export const KibanaDeprecationStats: FunctionComponent = () => {
const history = useHistory();
const { deprecations } = useAppContext();
const [kibanaDeprecations, setKibanaDeprecations] = useState<
DomainDeprecationDetails[] | undefined
>(undefined);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<Error | undefined>(undefined);
useEffect(() => {
async function getAllDeprecations() {
setIsLoading(true);
try {
const response = await deprecations.getAllDeprecations();
setKibanaDeprecations(response);
} catch (e) {
setError(e);
}
setIsLoading(false);
}
getAllDeprecations();
}, [deprecations]);
const warningDeprecationsCount =
kibanaDeprecations?.filter((deprecation) => deprecation.level === 'warning')?.length ?? 0;
const criticalDeprecationsCount =
kibanaDeprecations?.filter((deprecation) => deprecation.level === 'critical')?.length ?? 0;
const hasCritical = criticalDeprecationsCount > 0;
const hasWarnings = warningDeprecationsCount > 0;
const hasNoDeprecations = !isLoading && !error && !hasWarnings && !hasCritical;
const shouldRenderStat = (forSection: boolean) => error || isLoading || forSection;
return (
<EuiCard
data-test-subj="kibanaStatsPanel"
layout="horizontal"
title={
<>
{i18nTexts.statsTitle}
{error && (
<EuiIconTip
type="alert"
color="danger"
size="m"
content={i18nTexts.loadingError}
anchorClassName="upgWarningIcon"
iconProps={{
'data-test-subj': 'kibanaRequestErrorIconTip',
}}
/>
)}
</>
}
{...(!hasNoDeprecations && reactRouterNavigate(history, '/kibana_deprecations'))}
>
<EuiSpacer />
<EuiFlexGroup>
{hasNoDeprecations && (
<EuiFlexItem>
<NoDeprecations />
</EuiFlexItem>
)}
{shouldRenderStat(hasCritical) && (
<EuiFlexItem>
<EuiStat
data-test-subj="criticalDeprecations"
title={
kibanaDeprecations ? getDeprecationsUpperLimit(criticalDeprecationsCount) : '--'
}
titleElement="span"
description={i18nTexts.criticalDeprecationsTitle}
titleColor="danger"
isLoading={isLoading}
>
{error === undefined && (
<EuiScreenReaderOnly>
<p>
{isLoading
? i18nTexts.loadingText
: i18nTexts.getCriticalDeprecationsMessage(criticalDeprecationsCount)}
</p>
</EuiScreenReaderOnly>
)}
</EuiStat>
</EuiFlexItem>
)}
{shouldRenderStat(hasWarnings) && (
<EuiFlexItem>
<EuiStat
data-test-subj="warningDeprecations"
title={error ? '--' : getDeprecationsUpperLimit(warningDeprecationsCount)}
titleElement="span"
description={i18nTexts.warningDeprecationsTitle}
isLoading={isLoading}
>
{!error && (
<EuiScreenReaderOnly>
<p>
{isLoading
? i18nTexts.loadingText
: i18nTexts.getWarningDeprecationsMessage(warningDeprecationsCount)}
</p>
</EuiScreenReaderOnly>
)}
</EuiStat>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiCard>
);
};

View file

@ -0,0 +1,3 @@
.upgRenderSuccessMessage {
margin-top: $euiSizeL;
}

View file

@ -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 { NoDeprecations } from './no_deprecations';

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { FunctionComponent } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
const i18nTexts = {
noDeprecationsText: i18n.translate(
'xpack.upgradeAssistant.esDeprecationStats.noDeprecationsText',
{
defaultMessage: 'No warnings. Good to go!',
}
),
};
export const NoDeprecations: FunctionComponent = () => {
return (
<EuiText color="success">
<EuiFlexGroup gutterSize="s" alignItems="center" className="upgRenderSuccessMessage">
<EuiFlexItem grow={false}>
<EuiIcon type="check" />
</EuiFlexItem>
<EuiFlexItem grow={false} data-test-subj="noDeprecationsLabel">
{i18nTexts.noDeprecationsText}
</EuiFlexItem>
</EuiFlexGroup>
</EuiText>
);
};

View file

@ -0,0 +1,53 @@
/*
* 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 { EuiText, EuiFlexItem, EuiFlexGroup, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import type { EuiStepProps } from '@elastic/eui/src/components/steps/step';
import { ESDeprecationStats } from './es_stats';
import { KibanaDeprecationStats } from './kibana_stats';
const i18nTexts = {
reviewStepTitle: i18n.translate('xpack.upgradeAssistant.overview.reviewStepTitle', {
defaultMessage: 'Review deprecated settings and resolve issues',
}),
};
export const getReviewLogsStep = ({ currentMajor }: { currentMajor: number }): EuiStepProps => {
return {
title: i18nTexts.reviewStepTitle,
status: 'incomplete',
children: (
<>
<EuiText>
<p>
<FormattedMessage
id="xpack.upgradeAssistant.overview.resolveStepDescription"
defaultMessage="Update your Elasticsearch and Kibana deployments to be compatible with {currentMajor}.0. Critical issues must be resolved before you upgrade."
values={{ currentMajor }}
/>
</p>
</EuiText>
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem>
<ESDeprecationStats />
</EuiFlexItem>
<EuiFlexItem>
<KibanaDeprecationStats />
</EuiFlexItem>
</EuiFlexGroup>
</>
),
};
};

View file

@ -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 { getUpgradeStep } from './upgrade_step';

View file

@ -0,0 +1,129 @@
/*
* 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 {
EuiText,
EuiFlexItem,
EuiFlexGroup,
EuiSpacer,
EuiButton,
EuiButtonEmpty,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { EuiStepProps } from '@elastic/eui/src/components/steps/step';
import type { DocLinksStart } from 'src/core/public';
import { useKibana } from '../../../../shared_imports';
const i18nTexts = {
upgradeStepTitle: (currentMajor: number) =>
i18n.translate('xpack.upgradeAssistant.overview.upgradeStepTitle', {
defaultMessage: 'Install {currentMajor}.0',
values: { currentMajor },
}),
upgradeStepDescription: i18n.translate('xpack.upgradeAssistant.overview.upgradeStepDescription', {
defaultMessage:
"Once you've resolved all critical issues and verified that your applications are ready, you can upgrade the Elastic Stack.",
}),
upgradeStepDescriptionForCloud: i18n.translate(
'xpack.upgradeAssistant.overview.upgradeStepDescriptionForCloud',
{
defaultMessage:
"Once you've resolved all critical issues and verified that your applications are ready, you can upgrade the Elastic Stack. Upgrade your deployment on Elastic Cloud.",
}
),
upgradeStepLink: i18n.translate('xpack.upgradeAssistant.overview.upgradeStepLink', {
defaultMessage: 'Learn more',
}),
upgradeStepCloudLink: i18n.translate('xpack.upgradeAssistant.overview.upgradeStepCloudLink', {
defaultMessage: 'Upgrade on Cloud',
}),
upgradeGuideLink: i18n.translate('xpack.upgradeAssistant.overview.upgradeGuideLink', {
defaultMessage: 'View upgrade guide',
}),
};
const UpgradeStep = ({ docLinks }: { docLinks: DocLinksStart }) => {
const { cloud } = useKibana().services;
const isCloudEnabled: boolean = Boolean(cloud?.isCloudEnabled);
const cloudDeploymentUrl: string = `${cloud?.baseUrl ?? ''}/deployments/${cloud?.cloudId ?? ''}`;
let callToAction;
if (isCloudEnabled) {
callToAction = (
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiButton
href={cloudDeploymentUrl}
target="_blank"
data-test-subj="upgradeSetupCloudLink"
iconSide="right"
iconType="popout"
>
{i18nTexts.upgradeStepCloudLink}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
href={docLinks.links.elasticsearch.setupUpgrade}
target="_blank"
data-test-subj="upgradeSetupDocsLink"
iconSide="right"
iconType="popout"
>
{i18nTexts.upgradeGuideLink}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
);
} else {
callToAction = (
<EuiButton
href={docLinks.links.elasticsearch.setupUpgrade}
target="_blank"
data-test-subj="upgradeSetupDocsLink"
iconSide="right"
iconType="popout"
>
{i18nTexts.upgradeStepLink}
</EuiButton>
);
}
return (
<>
<EuiText>
<p>
{isCloudEnabled
? i18nTexts.upgradeStepDescriptionForCloud
: i18nTexts.upgradeStepDescription}
</p>
</EuiText>
<EuiSpacer size="m" />
{callToAction}
</>
);
};
interface Props {
docLinks: DocLinksStart;
currentMajor: number;
}
export const getUpgradeStep = ({ docLinks, currentMajor }: Props): EuiStepProps => {
return {
title: i18nTexts.upgradeStepTitle(currentMajor),
status: 'incomplete',
children: <UpgradeStep docLinks={docLinks} />,
};
};

View file

@ -46,3 +46,14 @@ export enum TelemetryState {
}
export type EsTabs = 'cluster' | 'indices';
export interface DeprecationLoggingPreviewProps {
isDeprecationLogIndexingEnabled: boolean;
onlyDeprecationLogWritingEnabled: boolean;
isLoading: boolean;
isUpdating: boolean;
fetchError: ResponseError | null;
updateError: ResponseError | undefined;
resendRequest: () => void;
toggleLogging: () => void;
}

View file

@ -63,7 +63,10 @@ export class ApiService {
}
public useLoadDeprecationLogging() {
return this.useRequest<{ isEnabled: boolean }>({
return this.useRequest<{
isDeprecationLogIndexingEnabled: boolean;
isDeprecationLoggingEnabled: boolean;
}>({
path: `${API_BASE_PATH}/deprecation_logging`,
method: 'get',
});

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import { validateRegExpString } from './utils';
import { DEPRECATION_WARNING_UPPER_LIMIT } from '../../../common/constants';
import { validateRegExpString, getDeprecationsUpperLimit } from './utils';
describe('validRegExpString', () => {
it('correctly returns false for invalid strings', () => {
@ -20,3 +21,17 @@ describe('validRegExpString', () => {
expect(validateRegExpString('')).toBe('');
});
});
describe('getDeprecationsUpperLimit', () => {
it('correctly returns capped number if it goes above limit', () => {
expect(getDeprecationsUpperLimit(1000000)).toBe(`${DEPRECATION_WARNING_UPPER_LIMIT}+`);
expect(getDeprecationsUpperLimit(2000000)).toBe(`${DEPRECATION_WARNING_UPPER_LIMIT}+`);
});
it('correctly returns true for valid strings', () => {
expect(getDeprecationsUpperLimit(10)).toBe('10');
expect(getDeprecationsUpperLimit(DEPRECATION_WARNING_UPPER_LIMIT)).toBe(
DEPRECATION_WARNING_UPPER_LIMIT.toString()
);
});
});

View file

@ -8,6 +8,8 @@
import { pipe } from 'fp-ts/lib/pipeable';
import { tryCatch, fold } from 'fp-ts/lib/Either';
import { DEPRECATION_WARNING_UPPER_LIMIT } from '../../../common/constants';
export const validateRegExpString = (s: string) =>
pipe(
tryCatch(
@ -19,3 +21,16 @@ export const validateRegExpString = (s: string) =>
() => ''
)
);
/*
* There isnt much difference between having 1M or 1.1M deprecation warnings, the number is
* so big it beats the purpose of having a little preview of the count. With this we can also
* prevent the container of the value to grow due to the value being so large.
*/
export const getDeprecationsUpperLimit = (count: number) => {
if (count > DEPRECATION_WARNING_UPPER_LIMIT) {
return `${DEPRECATION_WARNING_UPPER_LIMIT}+`;
}
return count.toString();
};

View file

@ -11,12 +11,14 @@ import { renderApp } from './render_app';
import { KibanaVersionContext } from './app_context';
import { apiService } from './lib/api';
import { breadcrumbService } from './lib/breadcrumbs';
import { AppServicesContext } from '../types';
export async function mountManagementSection(
coreSetup: CoreSetup,
params: ManagementAppMountParams,
kibanaVersionInfo: KibanaVersionContext,
readonly: boolean
readonly: boolean,
services: AppServicesContext
) {
const [
{ i18n, docLinks, notifications, application, deprecations },
@ -41,5 +43,7 @@ export async function mountManagementSection(
breadcrumbs: breadcrumbService,
getUrlForApp: application.getUrlForApp,
deprecations,
application,
services,
});
}

View file

@ -9,17 +9,13 @@ import SemVer from 'semver/classes/semver';
import { i18n } from '@kbn/i18n';
import { Plugin, CoreSetup, PluginInitializerContext } from 'src/core/public';
import { ManagementSetup } from '../../../../src/plugins/management/public';
import { SetupDependencies, StartDependencies, AppServicesContext } from './types';
import { Config } from '../common/config';
interface Dependencies {
management: ManagementSetup;
}
export class UpgradeAssistantUIPlugin implements Plugin {
export class UpgradeAssistantUIPlugin
implements Plugin<void, void, SetupDependencies, StartDependencies> {
constructor(private ctx: PluginInitializerContext) {}
setup(coreSetup: CoreSetup, { management }: Dependencies) {
setup(coreSetup: CoreSetup<StartDependencies>, { management, cloud }: SetupDependencies) {
const { enabled, readonly } = this.ctx.config.get<Config>();
if (!enabled) {
@ -45,7 +41,8 @@ export class UpgradeAssistantUIPlugin implements Plugin {
title: pluginName,
order: 1,
async mount(params) {
const [coreStart] = await coreSetup.getStartServices();
const [coreStart, { discover, data }] = await coreSetup.getStartServices();
const services: AppServicesContext = { discover, data, cloud };
const {
chrome: { docTitle },
@ -58,7 +55,8 @@ export class UpgradeAssistantUIPlugin implements Plugin {
coreSetup,
params,
kibanaVersionInfo,
readonly
readonly,
services
);
return () => {

View file

@ -5,6 +5,9 @@
* 2.0.
*/
import { useKibana as _useKibana } from '../../../../src/plugins/kibana_react/public';
import { AppServicesContext } from './types';
export {
sendRequest,
SendRequestConfig,
@ -13,3 +16,9 @@ export {
UseRequestConfig,
SectionLoading,
} from '../../../../src/plugins/es_ui_shared/public/';
export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public';
export { DataPublicPluginStart } from '../../../../src/plugins/data/public';
export const useKibana = () => _useKibana<AppServicesContext>();

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { DiscoverStart } from 'src/plugins/discover/public';
import { ManagementSetup } from 'src/plugins/management/public';
import { DataPublicPluginStart } from 'src/plugins/data/public';
import { CloudSetup } from '../../cloud/public';
import { LicensingPluginStart } from '../../licensing/public';
export interface AppServicesContext {
cloud?: CloudSetup;
discover: DiscoverStart;
data: DataPublicPluginStart;
}
export interface SetupDependencies {
management: ManagementSetup;
cloud?: CloudSetup;
}
export interface StartDependencies {
licensing: LicensingPluginStart;
discover: DiscoverStart;
data: DataPublicPluginStart;
}

View file

@ -10,6 +10,7 @@ import {
getDeprecationLoggingStatus,
isDeprecationLoggingEnabled,
setDeprecationLogging,
isDeprecationLogIndexingEnabled,
} from './es_deprecation_logging_apis';
describe('getDeprecationLoggingStatus', () => {
@ -28,7 +29,16 @@ describe('setDeprecationLogging', () => {
const dataClient = elasticsearchServiceMock.createScopedClusterClient();
await setDeprecationLogging(dataClient, true);
expect(dataClient.asCurrentUser.cluster.putSettings).toHaveBeenCalledWith({
body: { transient: { 'logger.deprecation': 'WARN' } },
body: {
persistent: {
'logger.deprecation': 'WARN',
'cluster.deprecation_indexing.enabled': true,
},
transient: {
'logger.deprecation': 'WARN',
'cluster.deprecation_indexing.enabled': true,
},
},
});
});
});
@ -38,7 +48,16 @@ describe('setDeprecationLogging', () => {
const dataClient = elasticsearchServiceMock.createScopedClusterClient();
await setDeprecationLogging(dataClient, false);
expect(dataClient.asCurrentUser.cluster.putSettings).toHaveBeenCalledWith({
body: { transient: { 'logger.deprecation': 'ERROR' } },
body: {
persistent: {
'logger.deprecation': 'ERROR',
'cluster.deprecation_indexing.enabled': false,
},
transient: {
'logger.deprecation': 'ERROR',
'cluster.deprecation_indexing.enabled': false,
},
},
});
});
});
@ -84,3 +103,24 @@ describe('isDeprecationLoggingEnabled', () => {
).toBe(true);
});
});
describe('isDeprecationLogIndexingEnabled', () => {
it('allows transient to override persistent and default', () => {
expect(
isDeprecationLogIndexingEnabled({
default: { cluster: { deprecation_indexing: { enabled: 'false' } } },
persistent: { cluster: { deprecation_indexing: { enabled: 'false' } } },
transient: { cluster: { deprecation_indexing: { enabled: 'true' } } },
})
).toBe(true);
});
it('allows persistent to override default', () => {
expect(
isDeprecationLogIndexingEnabled({
default: { cluster: { deprecation_indexing: { enabled: 'false' } } },
persistent: { cluster: { deprecation_indexing: { enabled: 'true' } } },
})
).toBe(true);
});
});

View file

@ -7,10 +7,7 @@
import { get } from 'lodash';
import { IScopedClusterClient } from 'src/core/server';
interface DeprecationLoggingStatus {
isEnabled: boolean;
}
import { DeprecationLoggingStatus } from '../../common/types';
export async function getDeprecationLoggingStatus(
dataClient: IScopedClusterClient
@ -20,7 +17,8 @@ export async function getDeprecationLoggingStatus(
});
return {
isEnabled: isDeprecationLoggingEnabled(response),
isDeprecationLogIndexingEnabled: isDeprecationLogIndexingEnabled(response),
isDeprecationLoggingEnabled: isDeprecationLoggingEnabled(response),
};
}
@ -30,17 +28,38 @@ export async function setDeprecationLogging(
): Promise<DeprecationLoggingStatus> {
const { body: response } = await dataClient.asCurrentUser.cluster.putSettings({
body: {
persistent: {
'logger.deprecation': isEnabled ? 'WARN' : 'ERROR',
'cluster.deprecation_indexing.enabled': isEnabled,
},
/*
* If we only set the persistent setting, we can end up in a situation in which a user has
* set transient on/off. And when toggling and reloading the page the transient setting will
* have priority over it thus "overriding" whatever the user selected.
*/
transient: {
'logger.deprecation': isEnabled ? 'WARN' : 'ERROR',
'cluster.deprecation_indexing.enabled': isEnabled,
},
},
});
return {
isEnabled: isDeprecationLoggingEnabled(response),
isDeprecationLogIndexingEnabled: isEnabled,
isDeprecationLoggingEnabled: isDeprecationLoggingEnabled(response),
};
}
export function isDeprecationLogIndexingEnabled(settings: any) {
const clusterDeprecationLoggingEnabled = ['default', 'persistent', 'transient'].reduce(
(currentLogLevel, settingsTier) =>
get(settings, [settingsTier, 'cluster', 'deprecation_indexing', 'enabled'], currentLogLevel),
'false'
);
return clusterDeprecationLoggingEnabled === 'true';
}
export function isDeprecationLoggingEnabled(settings: any) {
const deprecationLogLevel = ['default', 'persistent', 'transient'].reduce(
(currentLogLevel, settingsTier) =>

View file

@ -31,6 +31,11 @@ describe('Upgrade Assistant Usage Collector', () => {
logger: {
deprecation: 'WARN',
},
cluster: {
deprecation_indexing: {
enabled: 'true',
},
},
},
},
});

View file

@ -20,7 +20,10 @@ import {
UpgradeAssistantTelemetrySavedObject,
UpgradeAssistantTelemetrySavedObjectAttributes,
} from '../../../common/types';
import { isDeprecationLoggingEnabled } from '../es_deprecation_logging_apis';
import {
isDeprecationLogIndexingEnabled,
isDeprecationLoggingEnabled,
} from '../es_deprecation_logging_apis';
async function getSavedObjectAttributesFromRepo(
savedObjectsRepository: ISavedObjectsRepository,
@ -45,7 +48,10 @@ async function getDeprecationLoggingStatusValue(esClient: ElasticsearchClient):
include_defaults: true,
});
return isDeprecationLoggingEnabled(loggerDeprecationCallResult);
return (
isDeprecationLogIndexingEnabled(loggerDeprecationCallResult) &&
isDeprecationLoggingEnabled(loggerDeprecationCallResult)
);
} catch (e) {
return false;
}

View file

@ -16,6 +16,7 @@ import {
SavedObjectsClient,
SavedObjectsServiceStart,
} from '../../../../src/core/server';
import { InfraPluginSetup } from '../../infra/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
import { LicensingPluginSetup } from '../../licensing/server';
@ -31,6 +32,7 @@ import {
reindexOperationSavedObjectType,
mlSavedObjectType,
} from './saved_object_types';
import { DEPRECATION_LOGS_SOURCE_ID, DEPRECATION_LOGS_INDEX_PATTERN } from '../common/constants';
import { RouteDependencies } from './types';
@ -38,6 +40,7 @@ interface PluginsSetup {
usageCollection: UsageCollectionSetup;
licensing: LicensingPluginSetup;
features: FeaturesPluginSetup;
infra: InfraPluginSetup;
}
export class UpgradeAssistantServerPlugin implements Plugin {
@ -66,8 +69,8 @@ export class UpgradeAssistantServerPlugin implements Plugin {
}
setup(
{ http, getStartServices, capabilities, savedObjects }: CoreSetup,
{ usageCollection, features, licensing }: PluginsSetup
{ http, getStartServices, savedObjects }: CoreSetup,
{ usageCollection, features, licensing, infra }: PluginsSetup
) {
this.licensing = licensing;
@ -88,6 +91,21 @@ export class UpgradeAssistantServerPlugin implements Plugin {
],
});
// We need to initialize the deprecation logs plugin so that we can
// navigate from this app to the observability app using a source_id.
infra.defineInternalSourceConfiguration(DEPRECATION_LOGS_SOURCE_ID, {
name: 'deprecationLogs',
description: 'deprecation logs',
logIndices: {
type: 'index_name',
indexName: DEPRECATION_LOGS_INDEX_PATTERN,
},
logColumns: [
{ timestampColumn: { id: 'timestampField' } },
{ messageColumn: { id: 'messageField' } },
],
});
const router = http.createRouter();
const dependencies: RouteDependencies = {

View file

@ -37,18 +37,26 @@ describe('deprecation logging API', () => {
});
describe('GET /api/upgrade_assistant/deprecation_logging', () => {
it('returns isEnabled', async () => {
it('returns that indexing and writing logs is enabled', async () => {
(routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
.getSettings as jest.Mock).mockResolvedValue({
body: { default: { logger: { deprecation: 'WARN' } } },
body: {
default: {
cluster: { deprecation_indexing: { enabled: 'true' } },
},
},
});
const resp = await routeDependencies.router.getHandler({
method: 'get',
pathPattern: '/api/upgrade_assistant/deprecation_logging',
})(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
expect(resp.status).toEqual(200);
expect(resp.payload).toEqual({ isEnabled: true });
expect(resp.payload).toEqual({
isDeprecationLogIndexingEnabled: true,
isDeprecationLoggingEnabled: true,
});
});
it('returns an error if it throws', async () => {
@ -64,17 +72,26 @@ describe('deprecation logging API', () => {
});
describe('PUT /api/upgrade_assistant/deprecation_logging', () => {
it('returns isEnabled', async () => {
it('returns that indexing and writing logs is enabled', async () => {
(routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
.putSettings as jest.Mock).mockResolvedValue({
body: { default: { logger: { deprecation: 'ERROR' } } },
body: {
default: {
logger: { deprecation: 'WARN' },
cluster: { deprecation_indexing: { enabled: 'true' } },
},
},
});
const resp = await routeDependencies.router.getHandler({
method: 'put',
pathPattern: '/api/upgrade_assistant/deprecation_logging',
})(routeHandlerContextMock, { body: { isEnabled: true } }, kibanaResponseFactory);
expect(resp.payload).toEqual({ isEnabled: false });
expect(resp.payload).toEqual({
isDeprecationLogIndexingEnabled: true,
isDeprecationLoggingEnabled: true,
});
});
it('returns an error if it throws', async () => {

View file

@ -22,5 +22,7 @@
{ "path": "../features/tsconfig.json" },
{ "path": "../licensing/tsconfig.json" },
{ "path": "../../../src/plugins/es_ui_shared/tsconfig.json" },
{ "path": "../infra/tsconfig.json" },
{ "path": "../cloud/tsconfig.json" },
]
}