[Upgrade Assistant] New ES deprecations page (#107053)

This commit is contained in:
Alison Goryachev 2021-08-20 08:54:50 -04:00 committed by GitHub
parent c635216a6c
commit fcd89703f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 3520 additions and 4177 deletions

View file

@ -5827,16 +5827,10 @@
},
"ui_open": {
"properties": {
"cluster": {
"elasticsearch": {
"type": "long",
"_meta": {
"description": "Number of times a user viewed the list of Elasticsearch cluster deprecations."
}
},
"indices": {
"type": "long",
"_meta": {
"description": "Number of times a user viewed the list of Elasticsearch index deprecations."
"description": "Number of times a user viewed the list of Elasticsearch deprecations."
}
},
"overview": {

View file

@ -7185,21 +7185,6 @@
"xpack.canvas.workpadTemplates.table.descriptionColumnTitle": "説明",
"xpack.canvas.workpadTemplates.table.nameColumnTitle": "テンプレート名",
"xpack.canvas.workpadTemplates.table.tagsColumnTitle": "タグ",
"expressionShape.functions.progress.args.barColorHelpText": "背景バーの色です。",
"expressionShape.functions.progress.args.barWeightHelpText": "背景バーの太さです。",
"expressionShape.functions.progress.args.fontHelpText": "ラベルの {CSS} フォントプロパティです。例:{FONT_FAMILY} または {FONT_WEIGHT}。",
"expressionShape.functions.progress.args.labelHelpText": "ラベルの表示・非表示を切り替えるには、{BOOLEAN_TRUE}または{BOOLEAN_FALSE}を使用します。また、ラベルとして表示する文字列を入力することもできます。",
"expressionShape.functions.progress.args.maxHelpText": "進捗エレメントの最高値です。",
"expressionShape.functions.progress.args.shapeHelpText": "{list} または {end} を選択します。",
"expressionShape.functions.progress.args.valueColorHelpText": "進捗バーの色です。",
"expressionShape.functions.progress.args.valueWeightHelpText": "進捗バーの太さです。",
"expressionShape.functions.progress.invalidMaxValueErrorMessage": "無効な {arg} 値:「{max, number}」。「{arg}」は 0 より大きい必要があります",
"expressionShape.functions.progress.invalidValueErrorMessage": "無効な値:「{value, number}」。値は 0 と {max, number} の間でなければなりません",
"expressionShape.functions.progressHelpText": "進捗エレメントを構成します。",
"expressionShape.renderer.progress.displayName": "進捗インジケーター",
"expressionShape.renderer.progress.helpDescription": "エレメントのパーセンテージを示す進捗インジケーターをレンダリングします",
"expressionShape.renderer.shape.displayName": "形状",
"expressionShape.renderer.shape.helpDescription": "基本的な図形をレンダリングします",
"expressionRepeatImage.error.repeatImage.missingMaxArgument": "{emptyImageArgument} を指定する場合は、{maxArgument} を設定する必要があります",
"expressionRepeatImage.functions.repeatImage.args.emptyImageHelpText": "この画像のエレメントについて、{CONTEXT}および{maxArg}パラメーターの差異を解消します。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。",
"expressionRepeatImage.functions.repeatImage.args.imageHelpText": "繰り返す画像です。画像アセットは{BASE64}データ{URL}として提供するか、部分式で渡します。",
@ -7221,6 +7206,8 @@
"expressionMetric.functions.metricHelpText": "ラベルの上に数字を表示します。",
"expressionMetric.renderer.metric.displayName": "メトリック",
"expressionMetric.renderer.metric.helpDescription": "ラベルの上に数字をレンダリングします",
"expressionShape.renderer.shape.displayName": "形状",
"expressionShape.renderer.shape.helpDescription": "基本的な図形をレンダリングします",
"expressionError.errorComponent.description": "表現が失敗し次のメッセージが返されました:",
"expressionError.errorComponent.title": "おっと!表現が失敗しました",
"expressionError.renderer.debug.displayName": "デバッグ",
@ -24636,24 +24623,13 @@
"xpack.upgradeAssistant.breadcrumb.kibanaDeprecationsLabel": "Kibanaの廃止予定",
"xpack.upgradeAssistant.breadcrumb.overviewLabel": "アップグレードアシスタント",
"xpack.upgradeAssistant.checkupTab.changeFiltersShowMoreLabel": "より多く表示させるにはフィルターを変更します。",
"xpack.upgradeAssistant.checkupTab.confirmationModal.removeButtonLabel": "削除",
"xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel": "重大",
"xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIndexLabel": "インデックス別",
"xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIssueLabel": "問題別",
"xpack.upgradeAssistant.checkupTab.deprecations.criticalActionTooltip": "アップグレード前にこの問題を解決してください。",
"xpack.upgradeAssistant.checkupTab.deprecations.criticalLabel": "重大",
"xpack.upgradeAssistant.checkupTab.deprecations.documentationButtonLabel": "ドキュメント",
"xpack.upgradeAssistant.checkupTab.deprecations.indexTable.detailsColumnLabel": "詳細",
"xpack.upgradeAssistant.checkupTab.deprecations.indexTable.indexColumnLabel": "インデックス",
"xpack.upgradeAssistant.checkupTab.deprecations.warningActionTooltip": "アップグレード前にこの問題を解決することをお勧めしますが、必須ではありません。",
"xpack.upgradeAssistant.checkupTab.deprecations.warningLabel": "警告",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.cancelButtonLabel": "キャンセル",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.description": "次の廃止予定のインデックス設定が検出されました。これらは削除される予定です。",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.errorNotificationText": "インデックス設定の削除エラー",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.successNotificationText": "インデックス設定が削除されました",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.title": "廃止予定の設定を'{indexName}'から削除しますか?",
"xpack.upgradeAssistant.checkupTab.indexSettings.doneButtonLabel": "完了",
"xpack.upgradeAssistant.checkupTab.indexSettings.fixButtonLabel": "修正",
"xpack.upgradeAssistant.checkupTab.noDeprecationsLabel": "説明がありません",
"xpack.upgradeAssistant.checkupTab.numDeprecationsShownLabel": "{total} 件中 {numShown} 件を表示中",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.cancelButtonLabel": "キャンセル",
@ -24681,7 +24657,6 @@
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.reindexingChecklist.resumeWatcherStepTitle": "Watcher を再開中",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.reindexingChecklist.stopWatcherStepTitle": "Watcher を停止中",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.reindexingChecklistTitle": "プロセスを再インデックス中",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.flyoutHeader": "{indexName} を再インデックス",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutDetails": "このインデックスは現在閉じています。アップグレードアシスタントが開き、再インデックスを実行してからインデックスを閉じます。 {reindexingMayTakeLongerEmph}。詳細については {docs} をご覧ください。",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutDetails.reindexingTakesLongerEmphasis": "再インデックスには通常よりも時間がかかることがあります",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutTitle": "インデックスが閉じました",
@ -24693,13 +24668,6 @@
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.warningsStep.destructiveCallout.calloutDetail": "続行する前に、インデックスをバックアップしてください。再インデックスを続行するには、各変更を承諾してください。",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.warningsStep.destructiveCallout.calloutTitle": "このインデックスには元に戻すことのできない破壊的な変更が含まれています",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.warningsStep.documentationLinkLabel": "ドキュメント",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.cancelledLabel": "キャンセル済み",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.doneLabel": "完了",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.failedLabel": "失敗",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.indexClosedToolTipDetails": "「{indexName}」は再インデックスが必要ですが現在閉じています。アップグレードアシスタントが開き、再インデックスを実行してからインデックスを閉じます。再インデックスには通常よりも時間がかかることがあります。",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.loadingLabel": "読み込み中…",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.pausedLabel": "一時停止中",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.reindexLabel": "再インデックス",
"xpack.upgradeAssistant.deprecationGroupItem.docLinkText": "ドキュメンテーションを表示",
"xpack.upgradeAssistant.deprecationGroupItem.fixButtonLabel": "修正する手順を表示",
"xpack.upgradeAssistant.deprecationGroupItem.resolveButtonLabel": "クイック解決",
@ -24717,13 +24685,6 @@
"xpack.upgradeAssistant.esDeprecationErrors.partiallyUpgradedWarningMessage": "Kibanaをご使用のElasticsearchクラスターと同じバージョンにアップグレードしてください。クラスターの1つ以上のードがKibanaとは異なるバージョンを実行しています。",
"xpack.upgradeAssistant.esDeprecationErrors.permissionsErrorMessage": "Elasticsearchの廃止予定を表示する権限がありません。",
"xpack.upgradeAssistant.esDeprecationErrors.upgradedWarningMessage": "構成は最新です。KibanaおよびすべてのElasticsearchードは同じバージョンを実行しています。",
"xpack.upgradeAssistant.esDeprecations.backupDataButtonLabel": "データをバックアップ",
"xpack.upgradeAssistant.esDeprecations.backupDataTooltipText": "変更を行う前にスナップショットを作成します。",
"xpack.upgradeAssistant.esDeprecations.clusterLabel": "クラスター",
"xpack.upgradeAssistant.esDeprecations.clusterTabLabel": "クラスター",
"xpack.upgradeAssistant.esDeprecations.docLinkText": "ドキュメント",
"xpack.upgradeAssistant.esDeprecations.indexLabel": "インデックス",
"xpack.upgradeAssistant.esDeprecations.indicesTabLabel": "インデックス",
"xpack.upgradeAssistant.esDeprecations.loadingText": "廃止予定を読み込んでいます...",
"xpack.upgradeAssistant.esDeprecations.pageDescription": "廃止予定のクラスターとインデックス設定をレビューします。アップグレード前に重要な問題を解決する必要があります。",
"xpack.upgradeAssistant.esDeprecations.pageTitle": "Elasticsearch",
@ -24731,7 +24692,6 @@
"xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsTitle": "重大",
"xpack.upgradeAssistant.esDeprecationStats.loadingText": "Elasticsearchの廃止統計情報を読み込んでいます...",
"xpack.upgradeAssistant.esDeprecationStats.statsTitle": "Elasticsearch",
"xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTooltip": "このクラスターは{clusterCount}個の廃止予定のクラスター設定と{indexCount}個の廃止予定のインデックス設定を使用しています。",
"xpack.upgradeAssistant.kibanaDeprecationErrors.loadingErrorDescription": "エラーについては、Kibanaサーバーログを確認してください。",
"xpack.upgradeAssistant.kibanaDeprecationErrors.loadingErrorTitle": "Kibana廃止予定を取得できませんでした",
"xpack.upgradeAssistant.kibanaDeprecationErrors.pluginErrorDescription": "エラーについては、Kibanaサーバーログを確認してください。",
@ -24755,10 +24715,8 @@
"xpack.upgradeAssistant.kibanaDeprecationStats.loadingErrorMessage": "Kibana廃止予定の取得中にエラーが発生しました。",
"xpack.upgradeAssistant.kibanaDeprecationStats.loadingText": "Kibana廃止予定統計情報を読み込んでいます…",
"xpack.upgradeAssistant.kibanaDeprecationStats.statsTitle": "Kibana",
"xpack.upgradeAssistant.noDeprecationsPrompt.description": "構成は最新です。",
"xpack.upgradeAssistant.noDeprecationsPrompt.nextStepsDescription": "他のスタック廃止予定については、{overviewButton}を確認してください。",
"xpack.upgradeAssistant.noDeprecationsPrompt.overviewLinkText": "概要ページ",
"xpack.upgradeAssistant.noDeprecationsPrompt.title": "アップグレードする準備ができました。",
"xpack.upgradeAssistant.overview.deprecationLogs.disabledToastMessage": "廃止予定のアクションをログに出力しません。",
"xpack.upgradeAssistant.overview.deprecationLogs.enabledToastMessage": "廃止予定のアクションをログに出力します。",
"xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorMessage": "ログ情報を取得できませんでした。",

View file

@ -25186,25 +25186,13 @@
"xpack.upgradeAssistant.breadcrumb.kibanaDeprecationsLabel": "Kibana 弃用",
"xpack.upgradeAssistant.breadcrumb.overviewLabel": "升级助手",
"xpack.upgradeAssistant.checkupTab.changeFiltersShowMoreLabel": "更改筛选以显示更多内容。",
"xpack.upgradeAssistant.checkupTab.confirmationModal.removeButtonLabel": "移除",
"xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel": "紧急",
"xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIndexLabel": "按索引",
"xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIssueLabel": "按问题",
"xpack.upgradeAssistant.checkupTab.deprecations.criticalActionTooltip": "请解决此问题后再升级。",
"xpack.upgradeAssistant.checkupTab.deprecations.criticalLabel": "紧急",
"xpack.upgradeAssistant.checkupTab.deprecations.documentationButtonLabel": "文档",
"xpack.upgradeAssistant.checkupTab.deprecations.indexTable.detailsColumnLabel": "详情",
"xpack.upgradeAssistant.checkupTab.deprecations.indexTable.indexColumnLabel": "索引",
"xpack.upgradeAssistant.checkupTab.deprecations.warningActionTooltip": "建议在升级之前先解决此问题,但这不是必需的。",
"xpack.upgradeAssistant.checkupTab.deprecations.warningLabel": "警告",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.cancelButtonLabel": "取消",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.description": "检测到并将移除以下弃用的索引设置:",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.errorNotificationText": "移除索引设置时出错",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.successNotificationText": "索引设置已移除",
"xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.title": "从“{indexName}”移除已弃用的设置?",
"xpack.upgradeAssistant.checkupTab.indexSettings.doneButtonLabel": "完成",
"xpack.upgradeAssistant.checkupTab.indexSettings.fixButtonLabel": "修复",
"xpack.upgradeAssistant.checkupTab.indicesBadgeLabel": "{numIndices, plural, other { 个索引}}",
"xpack.upgradeAssistant.checkupTab.noDeprecationsLabel": "无弃用内容",
"xpack.upgradeAssistant.checkupTab.numDeprecationsShownLabel": "显示 {numShown} 个,共 {total} 个",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.cancelButtonLabel": "取消",
@ -25232,7 +25220,6 @@
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.reindexingChecklist.resumeWatcherStepTitle": "正在恢复 Watcher",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.reindexingChecklist.stopWatcherStepTitle": "正在停止 Watcher",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.reindexingChecklistTitle": "重新索引过程",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.flyoutHeader": "重新索引 {indexName}",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutDetails": "此索引当前已关闭。升级助手将打开索引,重新索引,然后关闭索引。{reindexingMayTakeLongerEmph}。请参阅文档{docs}以了解更多信息。",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutDetails.reindexingTakesLongerEmphasis": "重新索引可能比通常花费更多的时间",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutTitle": "索引已关闭",
@ -25244,13 +25231,6 @@
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.warningsStep.destructiveCallout.calloutDetail": "继续前备份索引。要继续重新索引,请接受每个更改。",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.warningsStep.destructiveCallout.calloutTitle": "此索引需要无法恢复的破坏性更改",
"xpack.upgradeAssistant.checkupTab.reindexing.flyout.warningsStep.documentationLinkLabel": "文档",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.cancelledLabel": "已取消",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.doneLabel": "完成",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.failedLabel": "失败",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.indexClosedToolTipDetails": "“{indexName}”需要重新索引,但当前已关闭。升级助手将打开索引,重新索引,然后关闭索引。重新索引可能比通常花费更多的时间。",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.loadingLabel": "正在加载……",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.pausedLabel": "已暂停",
"xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.reindexLabel": "重新索引",
"xpack.upgradeAssistant.deprecationGroupItem.docLinkText": "查看文档",
"xpack.upgradeAssistant.deprecationGroupItem.fixButtonLabel": "显示修复步骤",
"xpack.upgradeAssistant.deprecationGroupItem.resolveButtonLabel": "快速解决",
@ -25268,13 +25248,6 @@
"xpack.upgradeAssistant.esDeprecationErrors.partiallyUpgradedWarningMessage": "将 Kibana 升级到与您的 Elasticsearch 集群相同的版本。集群中的一个或多个节点正在运行与 Kibana 不同的版本。",
"xpack.upgradeAssistant.esDeprecationErrors.permissionsErrorMessage": "您无权查看 Elasticsearch 弃用。",
"xpack.upgradeAssistant.esDeprecationErrors.upgradedWarningMessage": "您的配置是最新的。Kibana 和索引 Elasticsearch 节点正在运行相同的版本。",
"xpack.upgradeAssistant.esDeprecations.backupDataButtonLabel": "备份您的数据",
"xpack.upgradeAssistant.esDeprecations.backupDataTooltipText": "在进行任何更改之前拍取快照。",
"xpack.upgradeAssistant.esDeprecations.clusterLabel": "集群",
"xpack.upgradeAssistant.esDeprecations.clusterTabLabel": "集群",
"xpack.upgradeAssistant.esDeprecations.docLinkText": "文档",
"xpack.upgradeAssistant.esDeprecations.indexLabel": "索引",
"xpack.upgradeAssistant.esDeprecations.indicesTabLabel": "索引",
"xpack.upgradeAssistant.esDeprecations.loadingText": "正在加载弃用……",
"xpack.upgradeAssistant.esDeprecations.pageDescription": "查看已弃用的群集和索引设置。在升级之前必须解决任何紧急问题。",
"xpack.upgradeAssistant.esDeprecations.pageTitle": "Elasticsearch",
@ -25282,7 +25255,6 @@
"xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsTitle": "紧急",
"xpack.upgradeAssistant.esDeprecationStats.loadingText": "正在加载 Elasticsearch 弃用统计……",
"xpack.upgradeAssistant.esDeprecationStats.statsTitle": "Elasticsearch",
"xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTooltip": "此集群正在使用 {clusterCount} 个已弃用集群设置和 {indexCount} 个已弃用的索引设置",
"xpack.upgradeAssistant.kibanaDeprecationErrors.loadingErrorDescription": "请在 Kibana 服务器日志中查看错误。",
"xpack.upgradeAssistant.kibanaDeprecationErrors.loadingErrorTitle": "无法检索 Kibana 弃用",
"xpack.upgradeAssistant.kibanaDeprecationErrors.pluginErrorDescription": "请在 Kibana 服务器日志中查看错误。",
@ -25306,10 +25278,8 @@
"xpack.upgradeAssistant.kibanaDeprecationStats.loadingErrorMessage": "检索 Kibana 弃用时发生错误。",
"xpack.upgradeAssistant.kibanaDeprecationStats.loadingText": "正在加载 Kibana 弃用统计……",
"xpack.upgradeAssistant.kibanaDeprecationStats.statsTitle": "Kibana",
"xpack.upgradeAssistant.noDeprecationsPrompt.description": "您的配置是最新的。",
"xpack.upgradeAssistant.noDeprecationsPrompt.nextStepsDescription": "查看{overviewButton}以了解其他 Stack 弃用。",
"xpack.upgradeAssistant.noDeprecationsPrompt.overviewLinkText": "“概览”页面",
"xpack.upgradeAssistant.noDeprecationsPrompt.title": "准备好升级!",
"xpack.upgradeAssistant.overview.deprecationLogs.disabledToastMessage": "不记录弃用的操作。",
"xpack.upgradeAssistant.overview.deprecationLogs.enabledToastMessage": "记录弃用的操作。",
"xpack.upgradeAssistant.overview.deprecationLogs.fetchErrorMessage": "无法检索日志记录信息。",

View file

@ -1,363 +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 { act } from 'react-dom/test-utils';
import { MlAction, ESUpgradeStatus } from '../../common/types';
import { ClusterTestBed, setupClusterPage, setupEnvironment } from './helpers';
describe('Cluster tab', () => {
let testBed: ClusterTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
afterAll(() => {
server.restore();
});
describe('with deprecations', () => {
const snapshotId = '1';
const jobId = 'deprecation_check_job';
const esDeprecationsMockResponse: ESUpgradeStatus = {
totalCriticalDeprecations: 1,
cluster: [
{
level: 'critical',
message:
'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded',
details:
'model snapshot [%s] for job [%s] supports minimum version [%s] and needs to be at least [%s]',
url: 'doc_url',
correctiveAction: {
type: 'mlSnapshot',
snapshotId,
jobId,
},
},
],
indices: [],
};
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({
isDeprecationLogIndexingEnabled: true,
isDeprecationLoggingEnabled: true,
});
await act(async () => {
testBed = await setupClusterPage({ isReadOnlyMode: false });
});
const { actions, component } = testBed;
component.update();
// Navigate to the cluster tab
await act(async () => {
actions.clickTab('cluster');
});
component.update();
});
test('renders deprecations', () => {
const { exists } = testBed;
expect(exists('clusterTabContent')).toBe(true);
expect(exists('deprecationsContainer')).toBe(true);
});
describe('fix ml snapshots button', () => {
let flyout: Element | null;
beforeEach(async () => {
const { component, actions, exists, find } = testBed;
expect(exists('deprecationsContainer')).toBe(true);
// Open all deprecations
actions.clickExpandAll();
// The data-test-subj is derived from the deprecation message
const accordionTestSubj = `depgroup_${esDeprecationsMockResponse.cluster[0].message
.split(' ')
.join('_')}`;
await act(async () => {
find(`${accordionTestSubj}.fixMlSnapshotsButton`).simulate('click');
});
component.update();
// We need to read the document "body" as the flyout is added there and not inside
// the component DOM tree.
flyout = document.body.querySelector('[data-test-subj="fixSnapshotsFlyout"]');
expect(flyout).not.toBe(null);
expect(flyout!.textContent).toContain('Upgrade or delete model snapshot');
});
test('upgrades snapshots', async () => {
const { component } = testBed;
const upgradeButton: HTMLButtonElement | null = flyout!.querySelector(
'[data-test-subj="upgradeSnapshotButton"]'
);
httpRequestsMockHelpers.setUpgradeMlSnapshotResponse({
nodeId: 'my_node',
snapshotId,
jobId,
status: 'in_progress',
});
await act(async () => {
upgradeButton!.click();
});
component.update();
// First, we expect a POST request to upgrade the snapshot
const upgradeRequest = server.requests[server.requests.length - 2];
expect(upgradeRequest.method).toBe('POST');
expect(upgradeRequest.url).toBe('/api/upgrade_assistant/ml_snapshots');
// Next, we expect a GET request to check the status of the upgrade
const statusRequest = server.requests[server.requests.length - 1];
expect(statusRequest.method).toBe('GET');
expect(statusRequest.url).toBe(
`/api/upgrade_assistant/ml_snapshots/${jobId}/${snapshotId}`
);
});
test('handles upgrade failure', async () => {
const { component, find } = testBed;
const upgradeButton: HTMLButtonElement | null = flyout!.querySelector(
'[data-test-subj="upgradeSnapshotButton"]'
);
const error = {
statusCode: 500,
error: 'Upgrade snapshot error',
message: 'Upgrade snapshot error',
};
httpRequestsMockHelpers.setUpgradeMlSnapshotResponse(undefined, error);
await act(async () => {
upgradeButton!.click();
});
component.update();
const upgradeRequest = server.requests[server.requests.length - 1];
expect(upgradeRequest.method).toBe('POST');
expect(upgradeRequest.url).toBe('/api/upgrade_assistant/ml_snapshots');
const accordionTestSubj = `depgroup_${esDeprecationsMockResponse.cluster[0].message
.split(' ')
.join('_')}`;
expect(find(`${accordionTestSubj}.fixMlSnapshotsButton`).text()).toEqual('Failed');
});
test('deletes snapshots', async () => {
const { component } = testBed;
const deleteButton: HTMLButtonElement | null = flyout!.querySelector(
'[data-test-subj="deleteSnapshotButton"]'
);
httpRequestsMockHelpers.setDeleteMlSnapshotResponse({
acknowledged: true,
});
await act(async () => {
deleteButton!.click();
});
component.update();
const request = server.requests[server.requests.length - 1];
const mlDeprecation = esDeprecationsMockResponse.cluster[0];
expect(request.method).toBe('DELETE');
expect(request.url).toBe(
`/api/upgrade_assistant/ml_snapshots/${
(mlDeprecation.correctiveAction! as MlAction).jobId
}/${(mlDeprecation.correctiveAction! as MlAction).snapshotId}`
);
});
test('handles delete failure', async () => {
const { component, find } = testBed;
const deleteButton: HTMLButtonElement | null = flyout!.querySelector(
'[data-test-subj="deleteSnapshotButton"]'
);
const error = {
statusCode: 500,
error: 'Upgrade snapshot error',
message: 'Upgrade snapshot error',
};
httpRequestsMockHelpers.setDeleteMlSnapshotResponse(undefined, error);
await act(async () => {
deleteButton!.click();
});
component.update();
const request = server.requests[server.requests.length - 1];
const mlDeprecation = esDeprecationsMockResponse.cluster[0];
expect(request.method).toBe('DELETE');
expect(request.url).toBe(
`/api/upgrade_assistant/ml_snapshots/${
(mlDeprecation.correctiveAction! as MlAction).jobId
}/${(mlDeprecation.correctiveAction! as MlAction).snapshotId}`
);
const accordionTestSubj = `depgroup_${esDeprecationsMockResponse.cluster[0].message
.split(' ')
.join('_')}`;
expect(find(`${accordionTestSubj}.fixMlSnapshotsButton`).text()).toEqual('Failed');
});
});
});
describe('no deprecations', () => {
beforeEach(async () => {
const noDeprecationsResponse = {
totalCriticalDeprecations: 0,
cluster: [],
indices: [],
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(noDeprecationsResponse);
await act(async () => {
testBed = await setupClusterPage({ isReadOnlyMode: false });
});
const { component } = testBed;
component.update();
});
test('renders prompt', () => {
const { exists, find } = testBed;
expect(exists('noDeprecationsPrompt')).toBe(true);
expect(find('noDeprecationsPrompt').text()).toContain('Ready to upgrade!');
});
});
describe('error handling', () => {
test('handles 403', async () => {
const error = {
statusCode: 403,
error: 'Forbidden',
message: 'Forbidden',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupClusterPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('permissionsError')).toBe(true);
expect(find('permissionsError').text()).toContain(
'You are not authorized to view Elasticsearch deprecations.'
);
});
test('shows upgraded message when all nodes have been upgraded', async () => {
const error = {
statusCode: 426,
error: 'Upgrade required',
message: 'There are some nodes running a different version of Elasticsearch',
attributes: {
// This is marked true in the scenario where none of the nodes have the same major version of Kibana,
// and therefore we assume all have been upgraded
allNodesUpgraded: true,
},
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupClusterPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('upgradedCallout')).toBe(true);
expect(find('upgradedCallout').text()).toContain(
'Your configuration is up to date. Kibana and all Elasticsearch nodes are running the same version.'
);
});
test('shows partially upgrade error when nodes are running different versions', async () => {
const error = {
statusCode: 426,
error: 'Upgrade required',
message: 'There are some nodes running a different version of Elasticsearch',
attributes: {
allNodesUpgraded: false,
},
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupClusterPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('partiallyUpgradedWarning')).toBe(true);
expect(find('partiallyUpgradedWarning').text()).toContain(
'Upgrade Kibana to the same version as your Elasticsearch cluster. One or more nodes in the cluster is running a different version than Kibana.'
);
});
test('handles generic error', async () => {
const error = {
statusCode: 500,
error: 'Internal server error',
message: 'Internal server error',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupClusterPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('requestError')).toBe(true);
expect(find('requestError').text()).toContain(
'Could not retrieve Elasticsearch deprecations.'
);
});
});
});

View file

@ -0,0 +1,52 @@
/*
* 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 { ElasticsearchTestBed, setupElasticsearchPage, setupEnvironment } from '../helpers';
import { esDeprecationsMockResponse, MOCK_SNAPSHOT_ID, MOCK_JOB_ID } from './mocked_responses';
describe('Default deprecation flyout', () => {
let testBed: ElasticsearchTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
afterAll(() => {
server.restore();
});
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
status: 'idle',
});
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
testBed.component.update();
});
it('renders a flyout with deprecation details', async () => {
const multiFieldsDeprecation = esDeprecationsMockResponse.deprecations[2];
const { actions, find, exists } = testBed;
await actions.clickDefaultDeprecationAt(0);
expect(exists('defaultDeprecationDetails')).toBe(true);
expect(find('defaultDeprecationDetails.flyoutTitle').text()).toContain(
multiFieldsDeprecation.message
);
expect(find('defaultDeprecationDetails.flyoutDescription').text()).toContain(
multiFieldsDeprecation.index
);
});
});

View file

@ -0,0 +1,267 @@
/*
* 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 { API_BASE_PATH } from '../../../common/constants';
import type { MlAction } from '../../../common/types';
import { ElasticsearchTestBed, setupElasticsearchPage, setupEnvironment } from '../helpers';
import {
esDeprecationsMockResponse,
MOCK_SNAPSHOT_ID,
MOCK_JOB_ID,
createEsDeprecationsMockResponse,
} from './mocked_responses';
describe('Deprecations table', () => {
let testBed: ElasticsearchTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
afterAll(() => {
server.restore();
});
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
status: 'idle',
});
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
testBed.component.update();
});
it('renders deprecations', () => {
const { exists, find } = testBed;
// Verify container exists
expect(exists('esDeprecationsContent')).toBe(true);
// Verify all deprecations appear in the table
expect(find('deprecationTableRow').length).toEqual(
esDeprecationsMockResponse.deprecations.length
);
});
it('refreshes deprecation data', async () => {
const { actions } = testBed;
const totalRequests = server.requests.length;
await actions.clickRefreshButton();
const mlDeprecation = esDeprecationsMockResponse.deprecations[0];
const reindexDeprecation = esDeprecationsMockResponse.deprecations[3];
// Since upgradeStatusMockResponse includes ML and reindex actions (which require fetching status), there will be 3 requests made
expect(server.requests.length).toBe(totalRequests + 3);
expect(server.requests[server.requests.length - 3].url).toBe(
`${API_BASE_PATH}/es_deprecations`
);
expect(server.requests[server.requests.length - 2].url).toBe(
`${API_BASE_PATH}/ml_snapshots/${(mlDeprecation.correctiveAction as MlAction).jobId}/${
(mlDeprecation.correctiveAction as MlAction).snapshotId
}`
);
expect(server.requests[server.requests.length - 1].url).toBe(
`${API_BASE_PATH}/reindex/${reindexDeprecation.index}`
);
});
describe('search bar', () => {
it('filters results by "critical" status', async () => {
const { find, actions } = testBed;
await actions.clickCriticalFilterButton();
const criticalDeprecations = esDeprecationsMockResponse.deprecations.filter(
(deprecation) => deprecation.isCritical
);
expect(find('deprecationTableRow').length).toEqual(criticalDeprecations.length);
await actions.clickCriticalFilterButton();
expect(find('deprecationTableRow').length).toEqual(
esDeprecationsMockResponse.deprecations.length
);
});
it('filters results by type', async () => {
const { component, find, actions } = testBed;
await actions.clickTypeFilterDropdownAt(0);
// We need to read the document "body" as the filter dropdown options are added there and not inside
// the component DOM tree.
const clusterTypeFilterButton: HTMLButtonElement | null = document.body.querySelector(
'.euiFilterSelect__items .euiFilterSelectItem'
);
expect(clusterTypeFilterButton).not.toBeNull();
await act(async () => {
clusterTypeFilterButton!.click();
});
component.update();
const clusterDeprecations = esDeprecationsMockResponse.deprecations.filter(
(deprecation) => deprecation.type === 'cluster_settings'
);
expect(find('deprecationTableRow').length).toEqual(clusterDeprecations.length);
});
it('filters results by query string', async () => {
const { find, actions } = testBed;
const multiFieldsDeprecation = esDeprecationsMockResponse.deprecations[2];
await actions.setSearchInputValue(multiFieldsDeprecation.message);
expect(find('deprecationTableRow').length).toEqual(1);
expect(find('deprecationTableRow').at(0).text()).toContain(multiFieldsDeprecation.message);
});
it('shows error for invalid search queries', async () => {
const { find, exists, actions } = testBed;
await actions.setSearchInputValue('%');
expect(exists('invalidSearchQueryMessage')).toBe(true);
expect(find('invalidSearchQueryMessage').text()).toContain('Invalid search');
});
it('shows message when search query does not return results', async () => {
const { find, actions, exists } = testBed;
await actions.setSearchInputValue('foobarbaz');
expect(exists('noDeprecationsRow')).toBe(true);
expect(find('noDeprecationsRow').text()).toContain(
'No Elasticsearch deprecation issues found'
);
});
});
describe('pagination', () => {
const esDeprecationsMockResponseWithManyDeprecations = createEsDeprecationsMockResponse(20);
const { deprecations } = esDeprecationsMockResponseWithManyDeprecations;
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(
esDeprecationsMockResponseWithManyDeprecations
);
httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
status: 'idle',
});
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
testBed.component.update();
});
it('shows the correct number of pages and deprecations per page', async () => {
const { find, actions } = testBed;
expect(find('esDeprecationsPagination').find('.euiPagination__item').length).toEqual(
Math.round(deprecations.length / 50) // Default rows per page is 50
);
expect(find('deprecationTableRow').length).toEqual(50);
// Navigate to the next page
await actions.clickPaginationAt(1);
// On the second (last) page, we expect to see the remaining deprecations
expect(find('deprecationTableRow').length).toEqual(deprecations.length - 50);
});
it('allows the number of viewable rows to change', async () => {
const { find, actions, component } = testBed;
await actions.clickRowsPerPageDropdown();
// We need to read the document "body" as the rows-per-page dropdown options are added there and not inside
// the component DOM tree.
const rowsPerPageButton: HTMLButtonElement | null = document.body.querySelector(
'[data-test-subj="tablePagination-100-rows"]'
);
expect(rowsPerPageButton).not.toBeNull();
await act(async () => {
rowsPerPageButton!.click();
});
component.update();
expect(find('esDeprecationsPagination').find('.euiPagination__item').length).toEqual(
Math.round(deprecations.length / 100) // Rows per page is now 100
);
expect(find('deprecationTableRow').length).toEqual(deprecations.length);
});
it('updates pagination when filters change', async () => {
const { actions, find } = testBed;
const criticalDeprecations = deprecations.filter((deprecation) => deprecation.isCritical);
await actions.clickCriticalFilterButton();
// Only 40 critical deprecations, so only one page should show
expect(find('esDeprecationsPagination').find('.euiPagination__item').length).toEqual(1);
expect(find('deprecationTableRow').length).toEqual(criticalDeprecations.length);
});
it('updates pagination on search', async () => {
const { actions, find } = testBed;
const reindexDeprecations = deprecations.filter(
(deprecation) => deprecation.correctiveAction?.type === 'reindex'
);
await actions.setSearchInputValue('Index created before 7.0');
// Only 20 deprecations that match, so only one page should show
expect(find('esDeprecationsPagination').find('.euiPagination__item').length).toEqual(1);
expect(find('deprecationTableRow').length).toEqual(reindexDeprecations.length);
});
});
describe('no deprecations', () => {
beforeEach(async () => {
const noDeprecationsResponse = {
totalCriticalDeprecations: 0,
deprecations: [],
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(noDeprecationsResponse);
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
testBed.component.update();
});
test('renders prompt', () => {
const { exists, find } = testBed;
expect(exists('noDeprecationsPrompt')).toBe(true);
expect(find('noDeprecationsPrompt').text()).toContain(
'Your Elasticsearch configuration is up to date'
);
});
});
});

View file

@ -0,0 +1,115 @@
/*
* 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 { ElasticsearchTestBed, setupElasticsearchPage, setupEnvironment } from '../helpers';
describe('Error handling', () => {
let testBed: ElasticsearchTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
afterAll(() => {
server.restore();
});
it('handles 403', async () => {
const error = {
statusCode: 403,
error: 'Forbidden',
message: 'Forbidden',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('permissionsError')).toBe(true);
expect(find('permissionsError').text()).toContain(
'You are not authorized to view Elasticsearch deprecations.'
);
});
it('shows upgraded message when all nodes have been upgraded', async () => {
const error = {
statusCode: 426,
error: 'Upgrade required',
message: 'There are some nodes running a different version of Elasticsearch',
attributes: {
// This is marked true in the scenario where none of the nodes have the same major version of Kibana,
// and therefore we assume all have been upgraded
allNodesUpgraded: true,
},
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('upgradedCallout')).toBe(true);
expect(find('upgradedCallout').text()).toContain('All Elasticsearch nodes have been upgraded.');
});
it('shows partially upgrade error when nodes are running different versions', async () => {
const error = {
statusCode: 426,
error: 'Upgrade required',
message: 'There are some nodes running a different version of Elasticsearch',
attributes: {
allNodesUpgraded: false,
},
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('partiallyUpgradedWarning')).toBe(true);
expect(find('partiallyUpgradedWarning').text()).toContain(
'Upgrade Kibana to the same version as your Elasticsearch cluster. One or more nodes in the cluster is running a different version than Kibana.'
);
});
it('handles generic error', async () => {
const error = {
statusCode: 500,
error: 'Internal server error',
message: 'Internal server error',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('requestError')).toBe(true);
expect(find('requestError').text()).toContain('Could not retrieve Elasticsearch deprecations.');
});
});

View file

@ -0,0 +1,117 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { act } from 'react-dom/test-utils';
import { ElasticsearchTestBed, setupElasticsearchPage, setupEnvironment } from '../helpers';
import { esDeprecationsMockResponse, MOCK_SNAPSHOT_ID, MOCK_JOB_ID } from './mocked_responses';
describe('Index settings deprecation flyout', () => {
let testBed: ElasticsearchTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
const indexSettingDeprecation = esDeprecationsMockResponse.deprecations[1];
afterAll(() => {
server.restore();
});
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
status: 'idle',
});
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
const { find, exists, actions, component } = testBed;
component.update();
await actions.clickIndexSettingsDeprecationAt(0);
expect(exists('indexSettingsDetails')).toBe(true);
expect(find('indexSettingsDetails.flyoutTitle').text()).toContain(
indexSettingDeprecation.message
);
expect(exists('removeSettingsPrompt')).toBe(true);
});
it('removes deprecated index settings', async () => {
const { find, actions } = testBed;
httpRequestsMockHelpers.setUpdateIndexSettingsResponse({
acknowledged: true,
});
await actions.clickDeleteSettingsButton();
const request = server.requests[server.requests.length - 1];
expect(request.method).toBe('POST');
expect(request.url).toBe(
`/api/upgrade_assistant/${indexSettingDeprecation.index!}/index_settings`
);
expect(request.status).toEqual(200);
// Verify the "Resolution" column of the table is updated
expect(find('indexSettingsResolutionStatusCell').at(0).text()).toEqual(
'Deprecated settings removed'
);
// Reopen the flyout
await actions.clickIndexSettingsDeprecationAt(0);
// Verify prompt to remove setting no longer displays
expect(find('removeSettingsPrompt').length).toEqual(0);
// Verify the action button no longer displays
expect(find('indexSettingsDetails.deleteSettingsButton').length).toEqual(0);
});
it('handles failure', async () => {
const { find, actions } = testBed;
const error = {
statusCode: 500,
error: 'Remove index settings error',
message: 'Remove index settings error',
};
httpRequestsMockHelpers.setUpdateIndexSettingsResponse(undefined, error);
await actions.clickDeleteSettingsButton();
const request = server.requests[server.requests.length - 1];
expect(request.method).toBe('POST');
expect(request.url).toBe(
`/api/upgrade_assistant/${indexSettingDeprecation.index!}/index_settings`
);
expect(request.status).toEqual(500);
// Verify the "Resolution" column of the table is updated
expect(find('indexSettingsResolutionStatusCell').at(0).text()).toEqual(
'Settings removal failed'
);
// Reopen the flyout
await actions.clickIndexSettingsDeprecationAt(0);
// Verify the flyout shows an error message
expect(find('indexSettingsDetails.deleteSettingsError').text()).toContain(
'Error deleting index settings'
);
// Verify the remove settings button text changes
expect(find('indexSettingsDetails.deleteSettingsButton').text()).toEqual(
'Retry removing deprecated settings'
);
});
});

View file

@ -0,0 +1,196 @@
/*
* 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 type { MlAction } from '../../../common/types';
import { ElasticsearchTestBed, setupElasticsearchPage, setupEnvironment } from '../helpers';
import { esDeprecationsMockResponse, MOCK_SNAPSHOT_ID, MOCK_JOB_ID } from './mocked_responses';
describe('Machine learning deprecation flyout', () => {
let testBed: ElasticsearchTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
const mlDeprecation = esDeprecationsMockResponse.deprecations[0];
afterAll(() => {
server.restore();
});
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
status: 'idle',
});
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
const { find, exists, actions, component } = testBed;
component.update();
await actions.clickMlDeprecationAt(0);
expect(exists('mlSnapshotDetails')).toBe(true);
expect(find('mlSnapshotDetails.flyoutTitle').text()).toContain(
'Upgrade or delete model snapshot'
);
});
describe('upgrade snapshots', () => {
it('successfully upgrades snapshots', async () => {
const { find, actions, exists } = testBed;
httpRequestsMockHelpers.setUpgradeMlSnapshotResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
status: 'in_progress',
});
httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
status: 'complete',
});
expect(find('mlSnapshotDetails.upgradeSnapshotButton').text()).toEqual('Upgrade');
await actions.clickUpgradeMlSnapshot();
// First, we expect a POST request to upgrade the snapshot
const upgradeRequest = server.requests[server.requests.length - 2];
expect(upgradeRequest.method).toBe('POST');
expect(upgradeRequest.url).toBe('/api/upgrade_assistant/ml_snapshots');
// Next, we expect a GET request to check the status of the upgrade
const statusRequest = server.requests[server.requests.length - 1];
expect(statusRequest.method).toBe('GET');
expect(statusRequest.url).toBe(
`/api/upgrade_assistant/ml_snapshots/${MOCK_JOB_ID}/${MOCK_SNAPSHOT_ID}`
);
// Verify the "Resolution" column of the table is updated
expect(find('mlActionResolutionCell').text()).toContain('Upgrade complete');
// Reopen the flyout
await actions.clickMlDeprecationAt(0);
// Flyout actions should not be visible if deprecation was resolved
expect(exists('mlSnapshotDetails.upgradeSnapshotButton')).toBe(false);
expect(exists('mlSnapshotDetails.deleteSnapshotButton')).toBe(false);
});
it('handles upgrade failure', async () => {
const { find, actions } = testBed;
const error = {
statusCode: 500,
error: 'Upgrade snapshot error',
message: 'Upgrade snapshot error',
};
httpRequestsMockHelpers.setUpgradeMlSnapshotResponse(undefined, error);
httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
status: 'error',
error,
});
await actions.clickUpgradeMlSnapshot();
const upgradeRequest = server.requests[server.requests.length - 1];
expect(upgradeRequest.method).toBe('POST');
expect(upgradeRequest.url).toBe('/api/upgrade_assistant/ml_snapshots');
// Verify the "Resolution" column of the table is updated
expect(find('mlActionResolutionCell').text()).toContain('Upgrade failed');
// Reopen the flyout
await actions.clickMlDeprecationAt(0);
// Verify the flyout shows an error message
expect(find('mlSnapshotDetails.resolveSnapshotError').text()).toContain(
'Error upgrading snapshot'
);
// Verify the upgrade button text changes
expect(find('mlSnapshotDetails.upgradeSnapshotButton').text()).toEqual('Retry upgrade');
});
});
describe('delete snapshots', () => {
it('successfully deletes snapshots', async () => {
const { find, actions } = testBed;
httpRequestsMockHelpers.setDeleteMlSnapshotResponse({
acknowledged: true,
});
expect(find('mlSnapshotDetails.deleteSnapshotButton').text()).toEqual('Delete');
await actions.clickDeleteMlSnapshot();
const request = server.requests[server.requests.length - 1];
expect(request.method).toBe('DELETE');
expect(request.url).toBe(
`/api/upgrade_assistant/ml_snapshots/${
(mlDeprecation.correctiveAction! as MlAction).jobId
}/${(mlDeprecation.correctiveAction! as MlAction).snapshotId}`
);
// Verify the "Resolution" column of the table is updated
expect(find('mlActionResolutionCell').at(0).text()).toEqual('Deletion complete');
// Reopen the flyout
await actions.clickMlDeprecationAt(0);
});
it('handles delete failure', async () => {
const { find, actions } = testBed;
const error = {
statusCode: 500,
error: 'Upgrade snapshot error',
message: 'Upgrade snapshot error',
};
httpRequestsMockHelpers.setDeleteMlSnapshotResponse(undefined, error);
await actions.clickDeleteMlSnapshot();
const request = server.requests[server.requests.length - 1];
expect(request.method).toBe('DELETE');
expect(request.url).toBe(
`/api/upgrade_assistant/ml_snapshots/${
(mlDeprecation.correctiveAction! as MlAction).jobId
}/${(mlDeprecation.correctiveAction! as MlAction).snapshotId}`
);
// Verify the "Resolution" column of the table is updated
expect(find('mlActionResolutionCell').at(0).text()).toEqual('Deletion failed');
// Reopen the flyout
await actions.clickMlDeprecationAt(0);
// Verify the flyout shows an error message
expect(find('mlSnapshotDetails.resolveSnapshotError').text()).toContain(
'Error deleting snapshot'
);
// Verify the upgrade button text changes
expect(find('mlSnapshotDetails.deleteSnapshotButton').text()).toEqual('Retry delete');
});
});
});

View file

@ -0,0 +1,119 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { ESUpgradeStatus, EnrichedDeprecationInfo } from '../../../common/types';
import { indexSettingDeprecations } from '../../../common/constants';
export const MOCK_SNAPSHOT_ID = '1';
export const MOCK_JOB_ID = 'deprecation_check_job';
export const MOCK_ML_DEPRECATION: EnrichedDeprecationInfo = {
isCritical: true,
resolveDuringUpgrade: false,
type: 'ml_settings',
message: 'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded',
details:
'model snapshot [%s] for job [%s] supports minimum version [%s] and needs to be at least [%s]',
url: 'doc_url',
correctiveAction: {
type: 'mlSnapshot',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
},
};
const MOCK_REINDEX_DEPRECATION: EnrichedDeprecationInfo = {
isCritical: true,
resolveDuringUpgrade: false,
type: 'index_settings',
message: 'Index created before 7.0',
details: 'deprecation details',
url: 'doc_url',
index: 'reindex_index',
correctiveAction: {
type: 'reindex',
},
};
const MOCK_INDEX_SETTING_DEPRECATION: EnrichedDeprecationInfo = {
isCritical: false,
resolveDuringUpgrade: false,
type: 'index_settings',
message: indexSettingDeprecations.translog.deprecationMessage,
details: 'deprecation details',
url: 'doc_url',
index: 'my_index',
correctiveAction: {
type: 'indexSetting',
deprecatedSettings: indexSettingDeprecations.translog.settings,
},
};
const MOCK_DEFAULT_DEPRECATION: EnrichedDeprecationInfo = {
isCritical: false,
resolveDuringUpgrade: false,
type: 'index_settings',
message: 'multi-fields within multi-fields',
details: 'deprecation details',
url: 'doc_url',
index: 'nested_multi-fields',
};
export const esDeprecationsMockResponse: ESUpgradeStatus = {
totalCriticalDeprecations: 2,
deprecations: [
MOCK_ML_DEPRECATION,
MOCK_INDEX_SETTING_DEPRECATION,
MOCK_DEFAULT_DEPRECATION,
MOCK_REINDEX_DEPRECATION,
],
};
// Useful for testing pagination where a large number of deprecations are needed
export const createEsDeprecationsMockResponse = (
numDeprecationsPerType: number
): ESUpgradeStatus => {
const mlDeprecations: EnrichedDeprecationInfo[] = Array.from(
{
length: numDeprecationsPerType,
},
() => MOCK_ML_DEPRECATION
);
const indexSettingsDeprecations: EnrichedDeprecationInfo[] = Array.from(
{
length: numDeprecationsPerType,
},
() => MOCK_INDEX_SETTING_DEPRECATION
);
const reindexDeprecations: EnrichedDeprecationInfo[] = Array.from(
{
length: numDeprecationsPerType,
},
() => MOCK_REINDEX_DEPRECATION
);
const defaultDeprecations: EnrichedDeprecationInfo[] = Array.from(
{
length: numDeprecationsPerType,
},
() => MOCK_DEFAULT_DEPRECATION
);
const deprecations: EnrichedDeprecationInfo[] = [
...defaultDeprecations,
...reindexDeprecations,
...indexSettingsDeprecations,
...mlDeprecations,
];
return {
totalCriticalDeprecations: mlDeprecations.length + reindexDeprecations.length,
deprecations,
};
};

View file

@ -0,0 +1,50 @@
/*
* 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 { ElasticsearchTestBed, setupElasticsearchPage, setupEnvironment } from '../helpers';
import { esDeprecationsMockResponse, MOCK_SNAPSHOT_ID, MOCK_JOB_ID } from './mocked_responses';
// Note: The reindexing flyout UX is subject to change; more tests should be added here once functionality is built out
describe('Reindex deprecation flyout', () => {
let testBed: ElasticsearchTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
afterAll(() => {
server.restore();
});
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
jobId: MOCK_JOB_ID,
status: 'idle',
});
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});
testBed.component.update();
});
it('renders a flyout with reindexing details', async () => {
const reindexDeprecation = esDeprecationsMockResponse.deprecations[3];
const { actions, find, exists } = testBed;
await actions.clickReindexDeprecationAt(0);
expect(exists('reindexDetails')).toBe(true);
expect(find('reindexDetails.flyoutTitle').text()).toContain(
`Reindex ${reindexDeprecation.index}`
);
});
});

View file

@ -1,67 +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 { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest';
import { EsDeprecationsContent } from '../../../public/application/components/es_deprecations';
import { WithAppDependencies } from './setup_environment';
const testBedConfig: TestBedConfig = {
memoryRouter: {
initialEntries: ['/es_deprecations/cluster'],
componentRoutePath: '/es_deprecations/:tabName',
},
doMountAsync: true,
};
export type ClusterTestBed = TestBed<ClusterTestSubjects> & {
actions: ReturnType<typeof createActions>;
};
const createActions = (testBed: TestBed) => {
/**
* User Actions
*/
const clickTab = (tabName: string) => {
const { find } = testBed;
const camelcaseTabName = tabName.charAt(0).toUpperCase() + tabName.slice(1);
find(`upgradeAssistant${camelcaseTabName}Tab`).simulate('click');
};
const clickExpandAll = () => {
const { find } = testBed;
find('expandAll').simulate('click');
};
return {
clickTab,
clickExpandAll,
};
};
export const setup = async (overrides?: Record<string, unknown>): Promise<ClusterTestBed> => {
const initTestBed = registerTestBed(
WithAppDependencies(EsDeprecationsContent, overrides),
testBedConfig
);
const testBed = await initTestBed();
return {
...testBed,
actions: createActions(testBed),
};
};
export type ClusterTestSubjects =
| 'expandAll'
| 'deprecationsContainer'
| 'permissionsError'
| 'requestError'
| 'upgradedCallout'
| 'partiallyUpgradedWarning'
| 'noDeprecationsPrompt'
| string;

View file

@ -0,0 +1,171 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { act } from 'react-dom/test-utils';
import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest';
import { EsDeprecations } from '../../../public/application/components/es_deprecations';
import { WithAppDependencies } from './setup_environment';
const testBedConfig: TestBedConfig = {
memoryRouter: {
initialEntries: ['/es_deprecations'],
componentRoutePath: '/es_deprecations',
},
doMountAsync: true,
};
export type ElasticsearchTestBed = TestBed & {
actions: ReturnType<typeof createActions>;
};
const createActions = (testBed: TestBed) => {
const { component, find } = testBed;
/**
* User Actions
*/
const clickRefreshButton = async () => {
await act(async () => {
find('refreshButton').simulate('click');
});
component.update();
};
const clickMlDeprecationAt = async (index: number) => {
await act(async () => {
find('deprecation-mlSnapshot').at(index).simulate('click');
});
component.update();
};
const clickUpgradeMlSnapshot = async () => {
await act(async () => {
find('mlSnapshotDetails.upgradeSnapshotButton').simulate('click');
});
component.update();
};
const clickDeleteMlSnapshot = async () => {
await act(async () => {
find('mlSnapshotDetails.deleteSnapshotButton').simulate('click');
});
component.update();
};
const clickIndexSettingsDeprecationAt = async (index: number) => {
await act(async () => {
find('deprecation-indexSetting').at(index).simulate('click');
});
component.update();
};
const clickDeleteSettingsButton = async () => {
await act(async () => {
find('deleteSettingsButton').simulate('click');
});
component.update();
};
const clickReindexDeprecationAt = async (index: number) => {
await act(async () => {
find('deprecation-reindex').at(index).simulate('click');
});
component.update();
};
const clickDefaultDeprecationAt = async (index: number) => {
await act(async () => {
find('deprecation-default').at(index).simulate('click');
});
component.update();
};
const clickCriticalFilterButton = async () => {
await act(async () => {
// EUI doesn't support data-test-subj's on the filter buttons, so we must access via CSS selector
find('searchBarContainer').find('.euiFilterButton').at(0).simulate('click');
});
component.update();
};
const clickTypeFilterDropdownAt = async (index: number) => {
await act(async () => {
// EUI doesn't support data-test-subj's on the filter buttons, so we must access via CSS selector
find('searchBarContainer')
.find('.euiPopover')
.find('.euiFilterButton')
.at(index)
.simulate('click');
});
component.update();
};
const setSearchInputValue = async (searchValue: string) => {
await act(async () => {
find('searchBarContainer')
.find('input')
.simulate('keyup', { target: { value: searchValue } });
});
component.update();
};
const clickPaginationAt = async (index: number) => {
await act(async () => {
find(`pagination-button-${index}`).simulate('click');
});
component.update();
};
const clickRowsPerPageDropdown = async () => {
await act(async () => {
find('tablePaginationPopoverButton').simulate('click');
});
component.update();
};
return {
clickRefreshButton,
clickMlDeprecationAt,
clickUpgradeMlSnapshot,
clickDeleteMlSnapshot,
clickIndexSettingsDeprecationAt,
clickDeleteSettingsButton,
clickReindexDeprecationAt,
clickDefaultDeprecationAt,
clickCriticalFilterButton,
clickTypeFilterDropdownAt,
setSearchInputValue,
clickPaginationAt,
clickRowsPerPageDropdown,
};
};
export const setup = async (overrides?: Record<string, unknown>): Promise<ElasticsearchTestBed> => {
const initTestBed = registerTestBed(
WithAppDependencies(EsDeprecations, overrides),
testBedConfig
);
const testBed = await initTestBed();
return {
...testBed,
actions: createActions(testBed),
};
};

View file

@ -51,11 +51,13 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};
const setUpdateIndexSettingsResponse = (response?: object) => {
const setUpdateIndexSettingsResponse = (response?: object, error?: ResponseError) => {
const status = error ? error.statusCode || 400 : 200;
const body = error ? error : response;
server.respondWith('POST', `${API_BASE_PATH}/:indexName/index_settings`, [
200,
status,
{ 'Content-Type': 'application/json' },
JSON.stringify(response),
JSON.stringify(body),
]);
};
@ -70,6 +72,17 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};
const setUpgradeMlSnapshotStatusResponse = (response?: object, error?: ResponseError) => {
const status = error ? error.statusCode || 400 : 200;
const body = error ? error : response;
server.respondWith('GET', `${API_BASE_PATH}/ml_snapshots/:jobId/:snapshotId`, [
status,
{ 'Content-Type': 'application/json' },
JSON.stringify(body),
]);
};
const setDeleteMlSnapshotResponse = (response?: object, error?: ResponseError) => {
const status = error ? error.statusCode || 400 : 200;
const body = error ? error : response;
@ -88,6 +101,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
setUpdateIndexSettingsResponse,
setUpgradeMlSnapshotResponse,
setDeleteMlSnapshotResponse,
setUpgradeMlSnapshotStatusResponse,
};
};

View file

@ -6,8 +6,7 @@
*/
export { setup as setupOverviewPage, OverviewTestBed } from './overview.helpers';
export { setup as setupIndicesPage, IndicesTestBed } from './indices.helpers';
export { setup as setupClusterPage, ClusterTestBed } from './cluster.helpers';
export { setup as setupElasticsearchPage, ElasticsearchTestBed } from './elasticsearch.helpers';
export { setup as setupKibanaPage, KibanaTestBed } from './kibana.helpers';
export { setupEnvironment } from './setup_environment';

View file

@ -1,75 +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 { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest';
import { EsDeprecationsContent } from '../../../public/application/components/es_deprecations';
import { WithAppDependencies } from './setup_environment';
const testBedConfig: TestBedConfig = {
memoryRouter: {
initialEntries: ['/es_deprecations/indices'],
componentRoutePath: '/es_deprecations/:tabName',
},
doMountAsync: true,
};
export type IndicesTestBed = TestBed<IndicesTestSubjects> & {
actions: ReturnType<typeof createActions>;
};
const createActions = (testBed: TestBed) => {
/**
* User Actions
*/
const clickTab = (tabName: string) => {
const { find } = testBed;
const camelcaseTabName = tabName.charAt(0).toUpperCase() + tabName.slice(1);
find(`upgradeAssistant${camelcaseTabName}Tab`).simulate('click');
};
const clickFixButton = () => {
const { find } = testBed;
find('removeIndexSettingsButton').simulate('click');
};
const clickExpandAll = () => {
const { find } = testBed;
find('expandAll').simulate('click');
};
return {
clickTab,
clickFixButton,
clickExpandAll,
};
};
export const setup = async (overrides?: Record<string, unknown>): Promise<IndicesTestBed> => {
const initTestBed = registerTestBed(
WithAppDependencies(EsDeprecationsContent, overrides),
testBedConfig
);
const testBed = await initTestBed();
return {
...testBed,
actions: createActions(testBed),
};
};
export type IndicesTestSubjects =
| 'expandAll'
| 'removeIndexSettingsButton'
| 'deprecationsContainer'
| 'permissionsError'
| 'requestError'
| 'indexCount'
| 'upgradedCallout'
| 'partiallyUpgradedWarning'
| 'noDeprecationsPrompt'
| string;

View file

@ -23,9 +23,12 @@ 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 { GlobalFlyout } from '../../../public/shared_imports';
import { servicesMock } from './services_mock';
import { init as initHttpRequests } from './http_requests';
const { GlobalFlyoutProvider } = GlobalFlyout;
const mockHttpClient = axios.create({ adapter: axiosXhrAdapter });
export const WithAppDependencies = (Comp: any, overrides: Record<string, unknown> = {}) => (
@ -55,7 +58,9 @@ export const WithAppDependencies = (Comp: any, overrides: Record<string, unknown
return (
<KibanaContextProvider services={{ ...servicesMock, ...(servicesOverrides as {}) }}>
<AppContextProvider value={{ ...contextValue, ...contextOverrides }}>
<Comp {...props} />
<GlobalFlyoutProvider>
<Comp {...props} />
</GlobalFlyoutProvider>
</AppContextProvider>
</KibanaContextProvider>
);

View file

@ -1,245 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { act } from 'react-dom/test-utils';
import { indexSettingDeprecations } from '../../common/constants';
import { ESUpgradeStatus } from '../../common/types';
import { IndicesTestBed, setupIndicesPage, setupEnvironment } from './helpers';
describe('Indices tab', () => {
let testBed: IndicesTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
afterAll(() => {
server.restore();
});
describe('with deprecations', () => {
const esDeprecationsMockResponse: ESUpgradeStatus = {
totalCriticalDeprecations: 0,
cluster: [],
indices: [
{
level: 'warning',
message: indexSettingDeprecations.translog.deprecationMessage,
url: 'doc_url',
index: 'my_index',
correctiveAction: {
type: 'indexSetting',
deprecatedSettings: indexSettingDeprecations.translog.settings,
},
},
],
};
beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({
isDeprecationLogIndexingEnabled: true,
isDeprecationLoggingEnabled: true,
});
await act(async () => {
testBed = await setupIndicesPage({ isReadOnlyMode: false });
});
const { actions, component } = testBed;
component.update();
// Navigate to the indices tab
await act(async () => {
actions.clickTab('indices');
});
component.update();
});
test('renders deprecations', () => {
const { exists, find } = testBed;
expect(exists('indexTabContent')).toBe(true);
expect(exists('deprecationsContainer')).toBe(true);
expect(find('indexCount').text()).toEqual('1');
});
describe('fix indices button', () => {
test('removes deprecated index settings', async () => {
const { component, actions, exists, find } = testBed;
expect(exists('deprecationsContainer')).toBe(true);
// Open all deprecations
actions.clickExpandAll();
const accordionTestSubj = `depgroup_${indexSettingDeprecations.translog.deprecationMessage
.split(' ')
.join('_')}`;
await act(async () => {
find(`${accordionTestSubj}.removeIndexSettingsButton`).simulate('click');
});
// We need to read the document "body" as the modal is added there and not inside
// the component DOM tree.
const modal = document.body.querySelector(
'[data-test-subj="indexSettingsDeleteConfirmModal"]'
);
const confirmButton: HTMLButtonElement | null = modal!.querySelector(
'[data-test-subj="confirmModalConfirmButton"]'
);
expect(modal).not.toBe(null);
expect(modal!.textContent).toContain('Remove deprecated settings');
const indexName = esDeprecationsMockResponse.indices[0].index;
httpRequestsMockHelpers.setUpdateIndexSettingsResponse({
acknowledged: true,
});
await act(async () => {
confirmButton!.click();
});
component.update();
const request = server.requests[server.requests.length - 1];
expect(request.method).toBe('POST');
expect(request.url).toBe(`/api/upgrade_assistant/${indexName}/index_settings`);
expect(request.status).toEqual(200);
});
});
});
describe('no deprecations', () => {
beforeEach(async () => {
const noDeprecationsResponse = {
totalCriticalDeprecations: 0,
cluster: [],
indices: [],
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(noDeprecationsResponse);
await act(async () => {
testBed = await setupIndicesPage({ isReadOnlyMode: false });
});
const { component } = testBed;
component.update();
});
test('renders prompt', () => {
const { exists, find } = testBed;
expect(exists('noDeprecationsPrompt')).toBe(true);
expect(find('noDeprecationsPrompt').text()).toContain('Ready to upgrade!');
});
});
describe('error handling', () => {
test('handles 403', async () => {
const error = {
statusCode: 403,
error: 'Forbidden',
message: 'Forbidden',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupIndicesPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('permissionsError')).toBe(true);
expect(find('permissionsError').text()).toContain(
'You are not authorized to view Elasticsearch deprecations.'
);
});
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 setupIndicesPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('upgradedCallout')).toBe(true);
expect(find('upgradedCallout').text()).toContain(
'Your configuration is up to date. Kibana and all Elasticsearch nodes are running the same version.'
);
});
test('handles partially upgrade 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 setupIndicesPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('partiallyUpgradedWarning')).toBe(true);
expect(find('partiallyUpgradedWarning').text()).toContain(
'Upgrade Kibana to the same version as your Elasticsearch cluster. One or more nodes in the cluster is running a different version than Kibana.'
);
});
test('handles generic error', async () => {
const error = {
statusCode: 500,
error: 'Internal server error',
message: 'Internal server error',
};
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error);
await act(async () => {
testBed = await setupIndicesPage({ isReadOnlyMode: false });
});
const { component, exists, find } = testBed;
component.update();
expect(exists('requestError')).toBe(true);
expect(find('requestError').text()).toContain(
'Could not retrieve Elasticsearch deprecations.'
);
});
});
});

View file

@ -78,7 +78,7 @@ describe('Kibana deprecations', () => {
// the component DOM tree.
let modal = document.body.querySelector('[data-test-subj="stepsModal"]');
expect(modal).not.toBe(null);
expect(modal).not.toBeNull();
expect(modal!.textContent).toContain(`Resolve deprecation in '${deprecation.domainId}'`);
const steps: NodeListOf<Element> | null = modal!.querySelectorAll(
@ -160,7 +160,9 @@ describe('Kibana deprecations', () => {
test('renders prompt', () => {
const { exists, find } = testBed;
expect(exists('noDeprecationsPrompt')).toBe(true);
expect(find('noDeprecationsPrompt').text()).toContain('Ready to upgrade!');
expect(find('noDeprecationsPrompt').text()).toContain(
'Your Kibana configuration is up to date'
);
});
});

View file

@ -10,19 +10,21 @@ import { ESUpgradeStatus } from '../../../../common/types';
export const esDeprecations: ESUpgradeStatus = {
totalCriticalDeprecations: 1,
cluster: [
deprecations: [
{
level: 'critical',
isCritical: true,
type: 'cluster_settings',
resolveDuringUpgrade: false,
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',
isCritical: false,
type: 'index_settings',
resolveDuringUpgrade: false,
message: 'translog retention settings are ignored',
url:
'https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html',
@ -35,8 +37,7 @@ export const esDeprecations: ESUpgradeStatus = {
export const esDeprecationsEmpty: ESUpgradeStatus = {
totalCriticalDeprecations: 0,
cluster: [],
indices: [],
deprecations: [],
};
export const kibanaDeprecations: DomainDeprecationDetails[] = [

View file

@ -84,7 +84,7 @@ describe('Overview - Fix deprecated settings step', () => {
component.update();
expect(exists('esStatsPanel')).toBe(true);
expect(find('esStatsPanel').find('a').props().href).toBe('/es_deprecations/cluster');
expect(find('esStatsPanel').find('a').props().href).toBe('/es_deprecations');
});
describe('Renders ES errors', () => {

View file

@ -5,6 +5,10 @@
* 2.0.
*/
import {
MigrationDeprecationInfoDeprecation,
MigrationDeprecationInfoResponse,
} from '@elastic/elasticsearch/api/types';
import { SavedObject, SavedObjectAttributes } from 'src/core/public';
export enum ReindexStep {
@ -116,13 +120,12 @@ export enum IndexGroup {
// Telemetry types
export const UPGRADE_ASSISTANT_TYPE = 'upgrade-assistant-telemetry';
export const UPGRADE_ASSISTANT_DOC_ID = 'upgrade-assistant-telemetry';
export type UIOpenOption = 'overview' | 'cluster' | 'indices' | 'kibana';
export type UIOpenOption = 'overview' | 'elasticsearch' | 'kibana';
export type UIReindexOption = 'close' | 'open' | 'start' | 'stop';
export interface UIOpen {
overview: boolean;
cluster: boolean;
indices: boolean;
elasticsearch: boolean;
kibana: boolean;
}
@ -136,8 +139,7 @@ export interface UIReindex {
export interface UpgradeAssistantTelemetrySavedObject {
ui_open: {
overview: number;
cluster: number;
indices: number;
elasticsearch: number;
kibana: number;
};
ui_reindex: {
@ -151,8 +153,7 @@ export interface UpgradeAssistantTelemetrySavedObject {
export interface UpgradeAssistantTelemetry {
ui_open: {
overview: number;
cluster: number;
indices: number;
elasticsearch: number;
kibana: number;
};
ui_reindex: {
@ -186,13 +187,6 @@ export interface DeprecationInfo {
export interface IndexSettingsDeprecationInfo {
[indexName: string]: DeprecationInfo[];
}
export interface DeprecationAPIResponse {
cluster_settings: DeprecationInfo[];
ml_settings: DeprecationInfo[];
node_settings: DeprecationInfo[];
index_settings: IndexSettingsDeprecationInfo;
}
export interface ReindexAction {
type: 'reindex';
/**
@ -215,15 +209,18 @@ export interface IndexSettingAction {
type: 'indexSetting';
deprecatedSettings: string[];
}
export interface EnrichedDeprecationInfo extends DeprecationInfo {
export interface EnrichedDeprecationInfo
extends Omit<MigrationDeprecationInfoDeprecation, 'level'> {
type: keyof MigrationDeprecationInfoResponse;
isCritical: boolean;
index?: string;
correctiveAction?: ReindexAction | MlAction | IndexSettingAction;
resolveDuringUpgrade: boolean;
}
export interface ESUpgradeStatus {
totalCriticalDeprecations: number;
cluster: EnrichedDeprecationInfo[];
indices: EnrichedDeprecationInfo[];
deprecations: EnrichedDeprecationInfo[];
}
export interface ResolveIndexResponseFromES {

View file

@ -8,17 +8,19 @@
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 { GlobalFlyout } from '../shared_imports';
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 { EsDeprecations } from './components/es_deprecations';
import { KibanaDeprecationsContent } from './components/kibana_deprecations';
import { Overview } from './components/overview';
import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public';
const { GlobalFlyoutProvider } = GlobalFlyout;
export interface AppDependencies extends ContextValue {
i18n: I18nStart;
history: ScopedHistory;
@ -37,7 +39,7 @@ const App: React.FunctionComponent = () => {
return (
<Switch>
<Route exact path="/overview" component={Overview} />
<Route exact path="/es_deprecations/:tabName" component={EsDeprecationsContent} />
<Route exact path="/es_deprecations" component={EsDeprecations} />
<Route exact path="/kibana_deprecations" component={KibanaDeprecationsContent} />
<Redirect from="/" to="/overview" />
</Switch>
@ -64,7 +66,9 @@ export const RootComponent = ({
<i18n.Context>
<KibanaContextProvider services={services}>
<AppContextProvider value={contextValue}>
<AppWithRouter history={history} />
<GlobalFlyoutProvider>
<AppWithRouter history={history} />
</GlobalFlyoutProvider>
</AppContextProvider>
</KibanaContextProvider>
</i18n.Context>

View file

@ -7,6 +7,8 @@
import { IconColor } from '@elastic/eui';
import { invert } from 'lodash';
import { i18n } from '@kbn/i18n';
import { DeprecationInfo } from '../../../common/types';
export const LEVEL_MAP: { [level: string]: number } = {
@ -26,3 +28,24 @@ export const COLOR_MAP: { [level: string]: IconColor } = {
};
export const DEPRECATIONS_PER_PAGE = 25;
export const DEPRECATION_TYPE_MAP = {
cluster_settings: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.clusterDeprecationTypeLabel',
{
defaultMessage: 'Cluster',
}
),
index_settings: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.indexDeprecationTypeLabel',
{
defaultMessage: 'Index',
}
),
node_settings: i18n.translate('xpack.upgradeAssistant.esDeprecations.nodeDeprecationTypeLabel', {
defaultMessage: 'Node',
}),
ml_settings: i18n.translate('xpack.upgradeAssistant.esDeprecations.mlDeprecationTypeLabel', {
defaultMessage: 'Machine Learning',
}),
};

View file

@ -1,870 +0,0 @@
{
"cluster": [
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 2",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 2",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 2",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 2",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 2 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 2 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 2 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 2 3",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 2 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 2 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 2 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 2 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 2 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 2 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 2 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 2 3 4",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 2 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 2 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 2 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 2 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 2 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 2 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 2 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 2 3 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 2 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 2 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 2 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 2 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 2 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 2 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 2 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 2 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 1 2 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 1 2 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
},
{
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead 0 1 2 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings 0 1 2 3 4 5",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
}
],
"nodes": [],
"indices": [
{
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: doc, field: spins], [type: doc, field: mlockall], [type: doc, field: node_master], [type: doc, field: primary]]",
"index": ".monitoring-es-6-2018.11.07"
},
{
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: tweet, field: liked]]",
"index": "twitter"
},
{
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: index-pattern, field: notExpandable], [type: config, field: xPackMonitoring:allowReport], [type: config, field: xPackMonitoring:showBanner], [type: dashboard, field: pause], [type: dashboard, field: timeRestore]]",
"index": ".kibana"
},
{
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: doc, field: notify], [type: doc, field: created], [type: doc, field: attach_payload], [type: doc, field: met]]",
"index": ".watcher-history-6-2018.11.07"
},
{
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: doc, field: snapshot]]",
"index": ".monitoring-kibana-6-2018.11.07"
},
{
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: tweet, field: liked]]",
"index": "twitter2"
},
{
"index": "twitter",
"level": "critical",
"message": "This index must be reindexed in order to upgrade the Elastic Stack.",
"details": "Reindexing is irreversible, so always back up your index before proceeding.",
"actions": [
{
"label": "Reindex in Console",
"url": "/app/dev_tools#/console?load_from=%2Fapi%2Fupgrade_assistant%2Freindex%2Fconsole_template%2Ftwitter.json"
}
],
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/reindex-upgrade.html"
},
{
"index": ".triggered_watches",
"level": "critical",
"message": "This index must be upgraded in order to upgrade the Elastic Stack.",
"details": "Upgrading is irreversible, so always back up your index before proceeding.",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-upgrade.html"
},
{
"index": ".reindex-status",
"level": "critical",
"message": "This index must be reindexed in order to upgrade the Elastic Stack.",
"details": "Reindexing is irreversible, so always back up your index before proceeding.",
"actions": [
{
"label": "Reindex in Console",
"url": "/app/dev_tools#/console?load_from=%2Fapi%2Fupgrade_assistant%2Freindex%2Fconsole_template%2F.reindex-status.json"
}
],
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/reindex-upgrade.html"
},
{
"index": "twitter2",
"level": "critical",
"message": "This index must be reindexed in order to upgrade the Elastic Stack.",
"details": "Reindexing is irreversible, so always back up your index before proceeding.",
"actions": [
{
"label": "Reindex in Console",
"url": "/app/dev_tools#/console?load_from=%2Fapi%2Fupgrade_assistant%2Freindex%2Fconsole_template%2Ftwitter2.json"
}
],
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/reindex-upgrade.html"
},
{
"index": ".watches",
"level": "critical",
"message": "This index must be upgraded in order to upgrade the Elastic Stack.",
"details": "Upgrading is irreversible, so always back up your index before proceeding.",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/migration-api-upgrade.html"
}
]
}

View file

@ -1 +1 @@
@import 'deprecations/index';
@import 'deprecation_types/index';

View file

@ -1,228 +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 { find, groupBy } from 'lodash';
import React, { FunctionComponent, useState, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiSpacer, EuiHorizontalRule } from '@elastic/eui';
import { EnrichedDeprecationInfo } from '../../../../common/types';
import { SectionLoading } from '../../../shared_imports';
import { GroupByOption, LevelFilterOption, UpgradeAssistantTabProps } from '../types';
import {
NoDeprecationsPrompt,
SearchBar,
DeprecationPagination,
DeprecationListBar,
} from '../shared';
import { DEPRECATIONS_PER_PAGE } from '../constants';
import { EsDeprecationErrors } from './es_deprecation_errors';
import { EsDeprecationAccordion } from './deprecations';
const i18nTexts = {
isLoading: i18n.translate('xpack.upgradeAssistant.esDeprecations.loadingText', {
defaultMessage: 'Loading deprecations…',
}),
};
export interface CheckupTabProps extends UpgradeAssistantTabProps {
checkupLabel: string;
}
export const createDependenciesFilter = (level: LevelFilterOption, search: string = '') => {
const conditions: Array<(dep: EnrichedDeprecationInfo) => boolean> = [];
if (level !== 'all') {
conditions.push((dep: EnrichedDeprecationInfo) => dep.level === level);
}
if (search.length > 0) {
conditions.push((dep) => {
try {
// 'i' is used for case-insensitive matching
const searchReg = new RegExp(search, 'i');
return searchReg.test(dep.message);
} catch (e) {
// ignore any regexp errors.
return true;
}
});
}
// Return true if every condition function returns true (boolean AND)
return (dep: EnrichedDeprecationInfo) => conditions.map((c) => c(dep)).every((t) => t);
};
const filterDeprecations = (
deprecations: EnrichedDeprecationInfo[] = [],
currentFilter: LevelFilterOption,
search: string
) => deprecations.filter(createDependenciesFilter(currentFilter, search));
const groupDeprecations = (
deprecations: EnrichedDeprecationInfo[],
currentFilter: LevelFilterOption,
search: string,
currentGroupBy: GroupByOption
) => groupBy(filterDeprecations(deprecations, currentFilter, search), currentGroupBy);
const getPageCount = (
deprecations: EnrichedDeprecationInfo[],
currentFilter: LevelFilterOption,
search: string,
currentGroupBy: GroupByOption
) =>
Math.ceil(
Object.keys(groupDeprecations(deprecations, currentFilter, search, currentGroupBy)).length /
DEPRECATIONS_PER_PAGE
);
/**
* Displays a list of deprecations that are filterable and groupable. Can be used for cluster,
* nodes, or indices deprecations.
*/
export const DeprecationTabContent: FunctionComponent<CheckupTabProps> = ({
checkupLabel,
deprecations,
error,
isLoading,
refreshCheckupData,
navigateToOverviewPage,
}) => {
const [currentFilter, setCurrentFilter] = useState<LevelFilterOption>('all');
const [search, setSearch] = useState<string>('');
const [currentGroupBy, setCurrentGroupBy] = useState<GroupByOption>(GroupByOption.message);
const [expandState, setExpandState] = useState({
forceExpand: false,
expandNumber: 0,
});
const [currentPage, setCurrentPage] = useState(0);
const getAvailableGroupByOptions = () => {
if (!deprecations) {
return [];
}
return Object.keys(GroupByOption).filter((opt) => find(deprecations, opt)) as GroupByOption[];
};
const setExpandAll = (expandAll: boolean) => {
setExpandState({ forceExpand: expandAll, expandNumber: expandState.expandNumber + 1 });
};
useEffect(() => {
if (deprecations) {
const pageCount = getPageCount(deprecations, currentFilter, search, currentGroupBy);
if (currentPage >= pageCount) {
setCurrentPage(0);
}
}
}, [currentPage, deprecations, currentFilter, search, currentGroupBy]);
if (deprecations && deprecations.length === 0) {
return (
<div data-test-subj={`${checkupLabel}TabContent`}>
<NoDeprecationsPrompt
deprecationType={checkupLabel}
navigateToOverviewPage={navigateToOverviewPage}
/>
</div>
);
}
let content: React.ReactNode;
if (isLoading) {
content = <SectionLoading>{i18nTexts.isLoading}</SectionLoading>;
} else if (deprecations?.length) {
const levelGroups = groupBy(deprecations, 'level');
const levelToDeprecationCountMap = Object.keys(levelGroups).reduce((counts, level) => {
counts[level] = levelGroups[level].length;
return counts;
}, {} as Record<string, number>);
const filteredDeprecations = filterDeprecations(deprecations, currentFilter, search);
const groups = groupDeprecations(deprecations, currentFilter, search, currentGroupBy);
content = (
<div data-test-subj="deprecationsContainer">
<SearchBar
allDeprecations={deprecations}
isLoading={isLoading}
loadData={refreshCheckupData}
currentFilter={currentFilter}
onFilterChange={setCurrentFilter}
onSearchChange={setSearch}
totalDeprecationsCount={deprecations.length}
levelToDeprecationCountMap={levelToDeprecationCountMap}
groupByFilterProps={{
availableGroupByOptions: getAvailableGroupByOptions(),
currentGroupBy,
onGroupByChange: setCurrentGroupBy,
}}
/>
<DeprecationListBar
allDeprecationsCount={deprecations.length}
filteredDeprecationsCount={filteredDeprecations.length}
setExpandAll={setExpandAll}
/>
<EuiHorizontalRule margin="m" />
<>
{Object.keys(groups)
.sort()
// Apply pagination
.slice(currentPage * DEPRECATIONS_PER_PAGE, (currentPage + 1) * DEPRECATIONS_PER_PAGE)
.map((groupName, index) => [
<div key={`es-deprecation-${index}`}>
<EsDeprecationAccordion
{...{
key: expandState.expandNumber,
id: `depgroup-${groupName}`,
dataTestSubj: `depgroup_${groupName.split(' ').join('_')}`,
title: groupName,
deprecations: groups[groupName],
currentGroupBy,
forceExpand: expandState.forceExpand,
}}
/>
<EuiHorizontalRule margin="s" />
</div>,
])}
{/* Only show pagination if we have more than DEPRECATIONS_PER_PAGE. */}
{Object.keys(groups).length > DEPRECATIONS_PER_PAGE && (
<>
<EuiSpacer />
<DeprecationPagination
pageCount={getPageCount(deprecations, currentFilter, search, currentGroupBy)}
activePage={currentPage}
setPage={setCurrentPage}
/>
</>
)}
</>
</div>
);
} else if (error) {
content = <EsDeprecationErrors error={error} />;
}
return (
<div data-test-subj={`${checkupLabel}TabContent`}>
<EuiSpacer />
{content}
</div>
);
};

View file

@ -0,0 +1,96 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiButtonEmpty,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiText,
EuiTextColor,
EuiLink,
} from '@elastic/eui';
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
export interface DefaultDeprecationFlyoutProps {
deprecation: EnrichedDeprecationInfo;
closeFlyout: () => void;
}
const i18nTexts = {
getFlyoutDescription: (indexName: string) =>
i18n.translate(
'xpack.upgradeAssistant.esDeprecations.deprecationDetailsFlyout.secondaryDescription',
{
defaultMessage: 'Index: {indexName}',
values: {
indexName,
},
}
),
learnMoreLinkLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.deprecationDetailsFlyout.learnMoreLinkLabel',
{
defaultMessage: 'Learn more about this deprecation',
}
),
closeButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.deprecationDetailsFlyout.closeButtonLabel',
{
defaultMessage: 'Close',
}
),
};
export const DefaultDeprecationFlyout = ({
deprecation,
closeFlyout,
}: DefaultDeprecationFlyoutProps) => {
const { message, url, details, index } = deprecation;
return (
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s" data-test-subj="flyoutTitle">
<h2>{message}</h2>
</EuiTitle>
{index && (
<EuiText data-test-subj="flyoutDescription">
<p>
<EuiTextColor color="subdued">{i18nTexts.getFlyoutDescription(index)}</EuiTextColor>
</p>
</EuiText>
)}
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiText>
<p>{details}</p>
<p>
<EuiLink target="_blank" href={url}>
{i18nTexts.learnMoreLinkLabel}
</EuiLink>
</p>
</EuiText>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={closeFlyout} flush="left">
{i18nTexts.closeButtonLabel}
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</>
);
};

View file

@ -0,0 +1,73 @@
/*
* 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, useEffect, useCallback } from 'react';
import { EuiTableRowCell } from '@elastic/eui';
import { GlobalFlyout } from '../../../../../shared_imports';
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
import { DeprecationTableColumns } from '../../../types';
import { EsDeprecationsTableCells } from '../../es_deprecations_table_cells';
import { DefaultDeprecationFlyout, DefaultDeprecationFlyoutProps } from './flyout';
const { useGlobalFlyout } = GlobalFlyout;
interface Props {
rowFieldNames: DeprecationTableColumns[];
deprecation: EnrichedDeprecationInfo;
}
export const DefaultTableRow: React.FunctionComponent<Props> = ({ rowFieldNames, deprecation }) => {
const [showFlyout, setShowFlyout] = useState(false);
const {
addContent: addContentToGlobalFlyout,
removeContent: removeContentFromGlobalFlyout,
} = useGlobalFlyout();
const closeFlyout = useCallback(() => {
setShowFlyout(false);
removeContentFromGlobalFlyout('deprecationDetails');
}, [removeContentFromGlobalFlyout]);
useEffect(() => {
if (showFlyout) {
addContentToGlobalFlyout<DefaultDeprecationFlyoutProps>({
id: 'deprecationDetails',
Component: DefaultDeprecationFlyout,
props: {
deprecation,
closeFlyout,
},
flyoutProps: {
onClose: closeFlyout,
'data-test-subj': 'defaultDeprecationDetails',
'aria-labelledby': 'defaultDeprecationDetailsFlyoutTitle',
},
});
}
}, [addContentToGlobalFlyout, closeFlyout, deprecation, showFlyout]);
return (
<>
{rowFieldNames.map((field) => {
return (
<EuiTableRowCell
key={field}
truncateText={false}
data-test-subj={`defaultTableCell-${field}`}
>
<EsDeprecationsTableCells
fieldName={field}
openFlyout={() => setShowFlyout(true)}
deprecation={deprecation}
/>
</EuiTableRowCell>
);
})}
</>
);
};

View file

@ -5,4 +5,7 @@
* 2.0.
*/
export { EsDeprecationAccordion } from './deprecation_group_item';
export { MlSnapshotsTableRow } from './ml_snapshots';
export { IndexSettingsTableRow } from './index_settings';
export { DefaultTableRow } from './default';
export { ReindexTableRow } from './reindex';

View file

@ -0,0 +1,204 @@
/*
* 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 { i18n } from '@kbn/i18n';
import {
EuiButton,
EuiButtonEmpty,
EuiCode,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiText,
EuiTextColor,
EuiLink,
EuiSpacer,
EuiCallOut,
} from '@elastic/eui';
import { EnrichedDeprecationInfo, IndexSettingAction } from '../../../../../../common/types';
import type { ResponseError } from '../../../../lib/api';
import type { Status } from '../../../types';
export interface RemoveIndexSettingsFlyoutProps {
deprecation: EnrichedDeprecationInfo;
closeFlyout: () => void;
removeIndexSettings: (index: string, settings: string[]) => Promise<void>;
status: {
statusType: Status;
details?: ResponseError;
};
}
const i18nTexts = {
getFlyoutDescription: (indexName: string) =>
i18n.translate(
'xpack.upgradeAssistant.esDeprecations.removeSettingsFlyout.secondaryDescription',
{
defaultMessage: 'Index: {indexName}',
values: {
indexName,
},
}
),
learnMoreLinkLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.removeSettingsFlyout.learnMoreLinkLabel',
{
defaultMessage: 'Learn more about this deprecation',
}
),
removeButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.removeSettingsFlyout.removeButtonLabel',
{
defaultMessage: 'Remove deprecated settings',
}
),
retryRemoveButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.removeSettingsFlyout.retryRemoveButtonLabel',
{
defaultMessage: 'Retry removing deprecated settings',
}
),
resolvedButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.removeSettingsFlyout.resolvedButtonLabel',
{
defaultMessage: 'Resolved',
}
),
closeButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.removeSettingsFlyout.closeButtonLabel',
{
defaultMessage: 'Close',
}
),
getConfirmationText: (indexSettingsCount: number) =>
i18n.translate('xpack.upgradeAssistant.esDeprecations.removeSettingsFlyout.description', {
defaultMessage:
'Remove the following deprecated index {indexSettingsCount, plural, one {setting} other {settings}}?',
values: {
indexSettingsCount,
},
}),
errorTitle: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.removeSettingsFlyout.deleteErrorTitle',
{
defaultMessage: 'Error deleting index settings',
}
),
};
export const RemoveIndexSettingsFlyout = ({
deprecation,
closeFlyout,
removeIndexSettings,
status,
}: RemoveIndexSettingsFlyoutProps) => {
const { index, message, details, url, correctiveAction } = deprecation;
const { statusType, details: statusDetails } = status;
// Flag used to hide certain parts of the UI if the deprecation has been resolved or is in progress
const isResolvable = ['idle', 'error'].includes(statusType);
return (
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s" data-test-subj="flyoutTitle">
<h2>{message}</h2>
</EuiTitle>
<EuiText>
<p>
<EuiTextColor color="subdued">{i18nTexts.getFlyoutDescription(index!)}</EuiTextColor>
</p>
</EuiText>
</EuiFlyoutHeader>
<EuiFlyoutBody>
{statusType === 'error' && (
<>
<EuiCallOut
title={i18nTexts.errorTitle}
color="danger"
iconType="alert"
data-test-subj="deleteSettingsError"
>
{statusDetails!.message}
</EuiCallOut>
<EuiSpacer />
</>
)}
<EuiText>
<p>{details}</p>
<p>
<EuiLink target="_blank" href={url}>
{i18nTexts.learnMoreLinkLabel}
</EuiLink>
</p>
</EuiText>
{isResolvable && (
<div data-test-subj="removeSettingsPrompt">
<EuiSpacer />
<EuiTitle size="xs">
<h3>
{i18nTexts.getConfirmationText(
(correctiveAction as IndexSettingAction).deprecatedSettings.length
)}
</h3>
</EuiTitle>
<EuiSpacer />
<EuiText>
<ul>
{(correctiveAction as IndexSettingAction).deprecatedSettings.map(
(setting, settingIndex) => (
<li key={`${setting}-${settingIndex}`}>
<EuiCode>{setting}</EuiCode>
</li>
)
)}
</ul>
</EuiText>
</div>
)}
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={closeFlyout} flush="left">
{i18nTexts.closeButtonLabel}
</EuiButtonEmpty>
</EuiFlexItem>
{isResolvable && (
<EuiFlexItem grow={false}>
<EuiButton
fill
data-test-subj="deleteSettingsButton"
color="danger"
onClick={() =>
removeIndexSettings(
index!,
(correctiveAction as IndexSettingAction).deprecatedSettings
)
}
>
{statusType === 'error'
? i18nTexts.retryRemoveButtonLabel
: i18nTexts.removeButtonLabel}
</EuiButton>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlyoutFooter>
</>
);
};

View file

@ -0,0 +1,130 @@
/*
* 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 {
EuiFlexItem,
EuiText,
EuiFlexGroup,
EuiIcon,
EuiLoadingSpinner,
EuiToolTip,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { Status } from '../../../types';
const i18nTexts = {
deleteInProgressText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.indexSettings.deletingButtonLabel',
{
defaultMessage: 'Settings removal in progress…',
}
),
deleteCompleteText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.indexSettings.deleteCompleteText',
{
defaultMessage: 'Deprecated settings removed',
}
),
deleteFailedText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.indexSettings.deleteFailedText',
{
defaultMessage: 'Settings removal failed',
}
),
resolutionText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.indexSettings.resolutionText',
{
defaultMessage: 'Remove settings',
}
),
resolutionTooltipLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.indexSettings.resolutionTooltipLabel',
{
defaultMessage:
'Resolve this deprecation by removing settings from this index. This is an automated resolution.',
}
),
};
interface Props {
status: {
statusType: Status;
};
}
export const IndexSettingsResolutionCell: React.FunctionComponent<Props> = ({ status }) => {
const { statusType } = status;
if (statusType === 'in_progress') {
return (
<EuiFlexGroup
gutterSize="s"
alignItems="center"
data-test-subj="indexSettingsResolutionStatusCell"
>
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.deleteInProgressText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
if (statusType === 'complete') {
return (
<EuiFlexGroup
gutterSize="s"
alignItems="center"
data-test-subj="indexSettingsResolutionStatusCell"
>
<EuiFlexItem grow={false}>
<EuiIcon type="check" color="success" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.deleteCompleteText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
if (statusType === 'error') {
return (
<EuiFlexGroup
gutterSize="s"
alignItems="center"
data-test-subj="indexSettingsResolutionStatusCell"
>
<EuiFlexItem grow={false}>
<EuiIcon type="alert" color="danger" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.deleteFailedText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
return (
<EuiToolTip position="top" content={i18nTexts.resolutionTooltipLabel}>
<EuiFlexGroup
gutterSize="s"
alignItems="center"
data-test-subj="indexSettingsResolutionStatusCell"
>
<EuiFlexItem grow={false}>
<EuiIcon type="indexSettings" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.resolutionText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiToolTip>
);
};

View file

@ -0,0 +1,103 @@
/*
* 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, useEffect, useCallback } from 'react';
import { EuiTableRowCell } from '@elastic/eui';
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
import { GlobalFlyout } from '../../../../../shared_imports';
import { useAppContext } from '../../../../app_context';
import type { ResponseError } from '../../../../lib/api';
import { EsDeprecationsTableCells } from '../../es_deprecations_table_cells';
import { DeprecationTableColumns, Status } from '../../../types';
import { IndexSettingsResolutionCell } from './resolution_table_cell';
import { RemoveIndexSettingsFlyout, RemoveIndexSettingsFlyoutProps } from './flyout';
const { useGlobalFlyout } = GlobalFlyout;
interface Props {
deprecation: EnrichedDeprecationInfo;
rowFieldNames: DeprecationTableColumns[];
}
export const IndexSettingsTableRow: React.FunctionComponent<Props> = ({
rowFieldNames,
deprecation,
}) => {
const [showFlyout, setShowFlyout] = useState(false);
const [status, setStatus] = useState<{
statusType: Status;
details?: ResponseError;
}>({ statusType: 'idle' });
const { api } = useAppContext();
const {
addContent: addContentToGlobalFlyout,
removeContent: removeContentFromGlobalFlyout,
} = useGlobalFlyout();
const closeFlyout = useCallback(() => {
setShowFlyout(false);
removeContentFromGlobalFlyout('indexSettingsFlyout');
}, [removeContentFromGlobalFlyout]);
const removeIndexSettings = useCallback(
async (index: string, settings: string[]) => {
setStatus({ statusType: 'in_progress' });
const { error } = await api.updateIndexSettings(index, settings);
setStatus({
statusType: error ? 'error' : 'complete',
details: error ?? undefined,
});
closeFlyout();
},
[api, closeFlyout]
);
useEffect(() => {
if (showFlyout) {
addContentToGlobalFlyout<RemoveIndexSettingsFlyoutProps>({
id: 'indexSettingsFlyout',
Component: RemoveIndexSettingsFlyout,
props: {
closeFlyout,
deprecation,
removeIndexSettings,
status,
},
flyoutProps: {
onClose: closeFlyout,
'data-test-subj': 'indexSettingsDetails',
'aria-labelledby': 'indexSettingsDetailsFlyoutTitle',
},
});
}
}, [addContentToGlobalFlyout, deprecation, removeIndexSettings, showFlyout, closeFlyout, status]);
return (
<>
{rowFieldNames.map((field: DeprecationTableColumns) => {
return (
<EuiTableRowCell
key={field}
truncateText={false}
data-test-subj={`indexSettingsTableCell-${field}`}
>
<EsDeprecationsTableCells
fieldName={field}
openFlyout={() => setShowFlyout(true)}
deprecation={deprecation}
resolutionTableCell={<IndexSettingsResolutionCell status={status} />}
/>
</EuiTableRowCell>
);
})}
</>
);
};

View file

@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useEffect, createContext, useContext } from 'react';
import { ApiService } from '../../../../lib/api';
import { useSnapshotState, SnapshotState } from './use_snapshot_state';
export interface MlSnapshotContext {
snapshotState: SnapshotState;
upgradeSnapshot: () => Promise<void>;
deleteSnapshot: () => Promise<void>;
}
const MlSnapshotsContext = createContext<MlSnapshotContext | undefined>(undefined);
export const useMlSnapshotContext = () => {
const context = useContext(MlSnapshotsContext);
if (context === undefined) {
throw new Error('useMlSnapshotContext must be used within a <MlSnapshotsStatusProvider />');
}
return context;
};
interface Props {
api: ApiService;
children: React.ReactNode;
snapshotId: string;
jobId: string;
}
export const MlSnapshotsStatusProvider: React.FunctionComponent<Props> = ({
api,
snapshotId,
jobId,
children,
}) => {
const { updateSnapshotStatus, snapshotState, upgradeSnapshot, deleteSnapshot } = useSnapshotState(
{
jobId,
snapshotId,
api,
}
);
useEffect(() => {
updateSnapshotStatus();
}, [updateSnapshotStatus]);
return (
<MlSnapshotsContext.Provider
value={{
snapshotState,
upgradeSnapshot,
deleteSnapshot,
}}
>
{children}
</MlSnapshotsContext.Provider>
);
};

View file

@ -13,28 +13,22 @@ import {
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiPortal,
EuiTitle,
EuiText,
EuiCallOut,
EuiSpacer,
EuiLink,
} from '@elastic/eui';
import { SnapshotStatus } from './use_snapshot_state';
import { ResponseError } from '../../../../lib/api';
interface SnapshotState extends SnapshotStatus {
error?: ResponseError;
}
interface Props {
upgradeSnapshot: () => Promise<void>;
deleteSnapshot: () => Promise<void>;
description: string;
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
import { MlSnapshotContext } from './context';
export interface FixSnapshotsFlyoutProps extends MlSnapshotContext {
deprecation: EnrichedDeprecationInfo;
closeFlyout: () => void;
snapshotState: SnapshotState;
}
const i18nTexts = {
@ -51,7 +45,7 @@ const i18nTexts = {
}
),
closeButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.cancelButtonLabel',
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.flyout.closeButtonLabel',
{
defaultMessage: 'Close',
}
@ -83,15 +77,24 @@ const i18nTexts = {
defaultMessage: 'Error upgrading snapshot',
}
),
learnMoreLinkLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.learnMoreLinkLabel',
{
defaultMessage: 'Learn more about this deprecation',
}
),
};
export const FixSnapshotsFlyout = ({
upgradeSnapshot,
deleteSnapshot,
description,
deprecation,
closeFlyout,
snapshotState,
}: Props) => {
upgradeSnapshot,
deleteSnapshot,
}: FixSnapshotsFlyoutProps) => {
// Flag used to hide certain parts of the UI if the deprecation has been resolved or is in progress
const isResolvable = ['idle', 'error'].includes(snapshotState.status);
const onUpgradeSnapshot = () => {
upgradeSnapshot();
closeFlyout();
@ -103,48 +106,48 @@ export const FixSnapshotsFlyout = ({
};
return (
<EuiPortal>
<EuiFlyout
onClose={closeFlyout}
ownFocus
size="m"
maxWidth
data-test-subj="fixSnapshotsFlyout"
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h2>{i18nTexts.flyoutTitle}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
{snapshotState.error && (
<>
<EuiCallOut
title={
snapshotState.action === 'delete'
? i18nTexts.deleteSnapshotErrorTitle
: i18nTexts.upgradeSnapshotErrorTitle
}
color="danger"
iconType="alert"
data-test-subj="upgradeSnapshotError"
>
{snapshotState.error.message}
</EuiCallOut>
<EuiSpacer />
</>
)}
<EuiText>
<p>{description}</p>
</EuiText>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={closeFlyout} flush="left">
{i18nTexts.closeButtonLabel}
</EuiButtonEmpty>
</EuiFlexItem>
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s" data-test-subj="flyoutTitle">
<h2>{i18nTexts.flyoutTitle}</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
{snapshotState.error && (
<>
<EuiCallOut
title={
snapshotState.action === 'delete'
? i18nTexts.deleteSnapshotErrorTitle
: i18nTexts.upgradeSnapshotErrorTitle
}
color="danger"
iconType="alert"
data-test-subj="resolveSnapshotError"
>
{snapshotState.error.message}
</EuiCallOut>
<EuiSpacer />
</>
)}
<EuiText>
<p>{deprecation.details}</p>
<p>
<EuiLink target="_blank" href={deprecation.url}>
{i18nTexts.learnMoreLinkLabel}
</EuiLink>
</p>
</EuiText>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty onClick={closeFlyout} flush="left">
{i18nTexts.closeButtonLabel}
</EuiButtonEmpty>
</EuiFlexItem>
{isResolvable && (
<EuiFlexItem grow={false}>
<EuiFlexGroup>
<EuiFlexItem>
@ -173,9 +176,9 @@ export const FixSnapshotsFlyout = ({
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
</EuiPortal>
)}
</EuiFlexGroup>
</EuiFlyoutFooter>
</>
);
};

View file

@ -0,0 +1,140 @@
/*
* 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 {
EuiToolTip,
EuiFlexItem,
EuiText,
EuiFlexGroup,
EuiIcon,
EuiLoadingSpinner,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useMlSnapshotContext } from './context';
const i18nTexts = {
upgradeInProgressText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.upgradeInProgressText',
{
defaultMessage: 'Upgrade in progress…',
}
),
deleteInProgressText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.deletingButtonLabel',
{
defaultMessage: 'Deletion in progress…',
}
),
upgradeCompleteText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.upgradeCompleteText',
{
defaultMessage: 'Upgrade complete',
}
),
deleteCompleteText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.deleteCompleteText',
{
defaultMessage: 'Deletion complete',
}
),
upgradeFailedText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.upgradeFailedText',
{
defaultMessage: 'Upgrade failed',
}
),
deleteFailedText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.deleteFailedText',
{
defaultMessage: 'Deletion failed',
}
),
resolutionText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.resolutionText',
{
defaultMessage: 'Upgrade or delete snapshots',
}
),
resolutionTooltipLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.resolutionTooltipLabel',
{
defaultMessage:
'Resolve this deprecation by upgrading or deleting a job model snapshot. This is an automated resolution.',
}
),
};
export const MlSnapshotsResolutionCell: React.FunctionComponent = () => {
const { snapshotState } = useMlSnapshotContext();
if (snapshotState.status === 'in_progress') {
return (
<EuiFlexGroup gutterSize="s" alignItems="center" data-test-subj="mlActionResolutionCell">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">
{snapshotState.action === 'delete'
? i18nTexts.deleteInProgressText
: i18nTexts.upgradeInProgressText}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
if (snapshotState.status === 'complete') {
return (
<EuiFlexGroup gutterSize="s" alignItems="center" data-test-subj="mlActionResolutionCell">
<EuiFlexItem grow={false}>
<EuiIcon type="check" color="success" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">
{snapshotState.action === 'delete'
? i18nTexts.deleteCompleteText
: i18nTexts.upgradeCompleteText}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
if (snapshotState.status === 'error') {
return (
<EuiFlexGroup gutterSize="s" alignItems="center" data-test-subj="mlActionResolutionCell">
<EuiFlexItem grow={false}>
<EuiIcon type="alert" color="danger" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">
{snapshotState.action === 'delete'
? i18nTexts.deleteFailedText
: i18nTexts.upgradeFailedText}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
return (
<EuiToolTip position="top" content={i18nTexts.resolutionTooltipLabel}>
<EuiFlexGroup gutterSize="s" alignItems="center" data-test-subj="mlActionResolutionCell">
<EuiFlexItem grow={false}>
<EuiIcon type="indexSettings" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.resolutionText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiToolTip>
);
};

View file

@ -0,0 +1,92 @@
/*
* 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, useEffect, useCallback } from 'react';
import { EuiTableRowCell } from '@elastic/eui';
import { EnrichedDeprecationInfo, MlAction } from '../../../../../../common/types';
import { GlobalFlyout } from '../../../../../shared_imports';
import { useAppContext } from '../../../../app_context';
import { DeprecationTableColumns } from '../../../types';
import { EsDeprecationsTableCells } from '../../es_deprecations_table_cells';
import { MlSnapshotsResolutionCell } from './resolution_table_cell';
import { FixSnapshotsFlyout, FixSnapshotsFlyoutProps } from './flyout';
import { MlSnapshotsStatusProvider, useMlSnapshotContext } from './context';
const { useGlobalFlyout } = GlobalFlyout;
interface TableRowProps {
deprecation: EnrichedDeprecationInfo;
rowFieldNames: DeprecationTableColumns[];
}
export const MlSnapshotsTableRowCells: React.FunctionComponent<TableRowProps> = ({
rowFieldNames,
deprecation,
}) => {
const [showFlyout, setShowFlyout] = useState(false);
const snapshotState = useMlSnapshotContext();
const {
addContent: addContentToGlobalFlyout,
removeContent: removeContentFromGlobalFlyout,
} = useGlobalFlyout();
const closeFlyout = useCallback(() => {
setShowFlyout(false);
removeContentFromGlobalFlyout('mlFlyout');
}, [removeContentFromGlobalFlyout]);
useEffect(() => {
if (showFlyout) {
addContentToGlobalFlyout<FixSnapshotsFlyoutProps>({
id: 'mlFlyout',
Component: FixSnapshotsFlyout,
props: {
deprecation,
closeFlyout,
...snapshotState,
},
flyoutProps: {
onClose: closeFlyout,
'data-test-subj': 'mlSnapshotDetails',
'aria-labelledby': 'mlSnapshotDetailsFlyoutTitle',
},
});
}
}, [snapshotState, addContentToGlobalFlyout, showFlyout, deprecation, closeFlyout]);
return (
<>
{rowFieldNames.map((field: DeprecationTableColumns) => {
return (
<EuiTableRowCell key={field} truncateText={false} data-test-subj={`mlTableCell-${field}`}>
<EsDeprecationsTableCells
fieldName={field}
openFlyout={() => setShowFlyout(true)}
deprecation={deprecation}
resolutionTableCell={<MlSnapshotsResolutionCell />}
/>
</EuiTableRowCell>
);
})}
</>
);
};
export const MlSnapshotsTableRow: React.FunctionComponent<TableRowProps> = (props) => {
const { api } = useAppContext();
return (
<MlSnapshotsStatusProvider
snapshotId={(props.deprecation.correctiveAction as MlAction).snapshotId}
jobId={(props.deprecation.correctiveAction as MlAction).jobId}
api={api}
>
<MlSnapshotsTableRowCells {...props} />
</MlSnapshotsStatusProvider>
);
};

View file

@ -8,16 +8,21 @@
import { useRef, useCallback, useState, useEffect } from 'react';
import { ApiService, ResponseError } from '../../../../lib/api';
import { Status } from '../../../types';
const POLL_INTERVAL_MS = 1000;
export interface SnapshotStatus {
interface SnapshotStatus {
snapshotId: string;
jobId: string;
status: 'complete' | 'in_progress' | 'error' | 'idle';
status: Status;
action?: 'upgrade' | 'delete';
}
export interface SnapshotState extends SnapshotStatus {
error: ResponseError | undefined;
}
export const useSnapshotState = ({
jobId,
snapshotId,

View file

@ -0,0 +1,61 @@
/*
* 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, createContext, useContext } from 'react';
import { ApiService } from '../../../../lib/api';
import { useReindexStatus, ReindexState } from './use_reindex_state';
export interface ReindexStateContext {
reindexState: ReindexState;
startReindex: () => Promise<void>;
cancelReindex: () => Promise<void>;
}
const ReindexContext = createContext<ReindexStateContext | undefined>(undefined);
export const useReindexContext = () => {
const context = useContext(ReindexContext);
if (context === undefined) {
throw new Error('useReindexContext must be used within a <ReindexStatusProvider />');
}
return context;
};
interface Props {
api: ApiService;
children: React.ReactNode;
indexName: string;
}
export const ReindexStatusProvider: React.FunctionComponent<Props> = ({
api,
indexName,
children,
}) => {
const { reindexState, startReindex, cancelReindex, updateStatus } = useReindexStatus({
indexName,
api,
});
useEffect(() => {
updateStatus();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<ReindexContext.Provider
value={{
reindexState,
startReindex,
cancelReindex,
}}
>
{children}
</ReindexContext.Provider>
);
};

View file

@ -11,7 +11,7 @@ import React from 'react';
import { ReindexStatus } from '../../../../../../../common/types';
import { LoadingState } from '../../../../types';
import { ReindexState } from '../polling_service';
import type { ReindexState } from '../use_reindex_state';
import { ChecklistFlyoutStep } from './checklist_step';
describe('ChecklistFlyout', () => {

View file

@ -22,7 +22,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { ReindexStatus } from '../../../../../../../common/types';
import { LoadingState } from '../../../../types';
import { ReindexState } from '../polling_service';
import type { ReindexState } from '../use_reindex_state';
import { ReindexProgress } from './progress';
const buttonLabel = (status?: ReindexStatus) => {
@ -45,7 +45,7 @@ const buttonLabel = (status?: ReindexStatus) => {
return (
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.reindexButton.doneLabel"
defaultMessage="Done!"
defaultMessage="Resolved"
/>
);
case ReindexStatus.paused:

View file

@ -0,0 +1,142 @@
/*
* 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 } from 'react';
import { DocLinksStart } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCallOut, EuiFlyoutHeader, EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui';
import {
EnrichedDeprecationInfo,
ReindexAction,
ReindexStatus,
} from '../../../../../../../common/types';
import { useAppContext } from '../../../../../app_context';
import type { ReindexStateContext } from '../context';
import { ChecklistFlyoutStep } from './checklist_step';
import { WarningsFlyoutStep } from './warnings_step';
enum ReindexFlyoutStep {
reindexWarnings,
checklist,
}
export interface ReindexFlyoutProps extends ReindexStateContext {
deprecation: EnrichedDeprecationInfo;
closeFlyout: () => void;
}
const getOpenAndCloseIndexDocLink = (docLinks: DocLinksStart) => (
<EuiLink target="_blank" href={`${docLinks.links.apis.openIndex}`}>
{i18n.translate(
'xpack.upgradeAssistant.checkupTab.reindexing.flyout.openAndCloseDocumentation',
{ defaultMessage: 'documentation' }
)}
</EuiLink>
);
const getIndexClosedCallout = (docLinks: DocLinksStart) => (
<>
<EuiCallOut
title={i18n.translate(
'xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutTitle',
{ defaultMessage: 'Index closed' }
)}
color="warning"
iconType="alert"
>
<p>
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutDetails"
defaultMessage="This index is currently closed. The Upgrade Assistant will open, reindex and then close the index. {reindexingMayTakeLongerEmph}. Please see the {docs} for more information."
values={{
docs: getOpenAndCloseIndexDocLink(docLinks),
reindexingMayTakeLongerEmph: (
<b>
{i18n.translate(
'xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutDetails.reindexingTakesLongerEmphasis',
{ defaultMessage: 'Reindexing may take longer than usual' }
)}
</b>
),
}}
/>
</p>
</EuiCallOut>
<EuiSpacer size="m" />
</>
);
export const ReindexFlyout: React.FunctionComponent<ReindexFlyoutProps> = ({
reindexState,
startReindex,
cancelReindex,
closeFlyout,
deprecation,
}) => {
const { status, reindexWarnings } = reindexState;
const { index, correctiveAction } = deprecation;
const { docLinks } = useAppContext();
// If there are any warnings and we haven't started reindexing, show the warnings step first.
const [currentFlyoutStep, setCurrentFlyoutStep] = useState<ReindexFlyoutStep>(
reindexWarnings && reindexWarnings.length > 0 && status === undefined
? ReindexFlyoutStep.reindexWarnings
: ReindexFlyoutStep.checklist
);
let flyoutContents: React.ReactNode;
const globalCallout =
(correctiveAction as ReindexAction).blockerForReindexing === 'index-closed' &&
reindexState.status !== ReindexStatus.completed
? getIndexClosedCallout(docLinks)
: undefined;
switch (currentFlyoutStep) {
case ReindexFlyoutStep.reindexWarnings:
flyoutContents = (
<WarningsFlyoutStep
renderGlobalCallouts={() => globalCallout}
closeFlyout={closeFlyout}
warnings={reindexState.reindexWarnings!}
advanceNextStep={() => setCurrentFlyoutStep(ReindexFlyoutStep.checklist)}
/>
);
break;
case ReindexFlyoutStep.checklist:
flyoutContents = (
<ChecklistFlyoutStep
renderGlobalCallouts={() => globalCallout}
closeFlyout={closeFlyout}
reindexState={reindexState}
startReindex={startReindex}
cancelReindex={cancelReindex}
/>
);
break;
default:
throw new Error(`Invalid flyout step: ${currentFlyoutStep}`);
}
return (
<>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s" data-test-subj="flyoutTitle">
<h2>
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.flyout.flyoutHeader"
defaultMessage="Reindex {index}"
values={{ index }}
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
{flyoutContents}
</>
);
};

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 { ReindexFlyout, ReindexFlyoutProps } from './container';

View file

@ -9,7 +9,7 @@ import { shallow } from 'enzyme';
import React from 'react';
import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../common/types';
import { ReindexState } from '../polling_service';
import type { ReindexState } from '../use_reindex_state';
import { ReindexProgress } from './progress';
describe('ReindexProgress', () => {

View file

@ -19,7 +19,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { IndexGroup, ReindexStatus, ReindexStep } from '../../../../../../../common/types';
import { LoadingState } from '../../../../types';
import { ReindexState } from '../polling_service';
import type { ReindexState } from '../use_reindex_state';
import { StepProgress, StepProgressStep } from './step_progress';
const ErrorCallout: React.FunctionComponent<{ errorMessage: string | null }> = ({

View file

@ -0,0 +1,158 @@
/*
* 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 { i18n } from '@kbn/i18n';
import {
EuiIcon,
EuiLoadingSpinner,
EuiText,
EuiFlexGroup,
EuiFlexItem,
EuiToolTip,
} from '@elastic/eui';
import { ReindexStatus } from '../../../../../../common/types';
import { LoadingState } from '../../../types';
import { useReindexContext } from './context';
const i18nTexts = {
reindexLoadingStatusText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.reindex.reindexLoadingStatusText',
{
defaultMessage: 'Loading status…',
}
),
reindexInProgressText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.reindex.reindexInProgressText',
{
defaultMessage: 'Reindexing in progress…',
}
),
reindexCompleteText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.reindex.reindexCompleteText',
{
defaultMessage: 'Reindex complete',
}
),
reindexFailedText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.reindex.reindexFailedText',
{
defaultMessage: 'Reindex failed',
}
),
reindexCanceledText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.reindex.reindexCanceledText',
{
defaultMessage: 'Reindex canceled',
}
),
reindexPausedText: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.reindex.reindexPausedText',
{
defaultMessage: 'Reindex paused',
}
),
resolutionText: i18n.translate('xpack.upgradeAssistant.esDeprecations.reindex.resolutionLabel', {
defaultMessage: 'Reindex',
}),
resolutionTooltipLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.reindex.resolutionTooltipLabel',
{
defaultMessage:
'Resolve this deprecation by reindexing this index. This is an automated resolution.',
}
),
};
export const ReindexResolutionCell: React.FunctionComponent = () => {
const { reindexState } = useReindexContext();
if (reindexState.loadingState === LoadingState.Loading) {
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.reindexLoadingStatusText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
switch (reindexState.status) {
case ReindexStatus.inProgress:
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="m" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.reindexInProgressText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
case ReindexStatus.completed:
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="check" color="success" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.reindexCompleteText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
case ReindexStatus.failed:
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="alert" color="danger" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.reindexFailedText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
case ReindexStatus.paused:
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="alert" color="danger" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.reindexPausedText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
case ReindexStatus.cancelled:
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="alert" color="danger" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.reindexCanceledText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
}
return (
<EuiToolTip position="top" content={i18nTexts.resolutionTooltipLabel}>
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="indexSettings" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s">{i18nTexts.resolutionText}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiToolTip>
);
};

View file

@ -0,0 +1,104 @@
/*
* 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, useEffect, useCallback } from 'react';
import { EuiTableRowCell } from '@elastic/eui';
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
import { GlobalFlyout } from '../../../../../shared_imports';
import { useAppContext } from '../../../../app_context';
import { DeprecationTableColumns } from '../../../types';
import { EsDeprecationsTableCells } from '../../es_deprecations_table_cells';
import { ReindexResolutionCell } from './resolution_table_cell';
import { ReindexFlyout, ReindexFlyoutProps } from './flyout';
import { ReindexStatusProvider, useReindexContext } from './context';
const { useGlobalFlyout } = GlobalFlyout;
interface TableRowProps {
deprecation: EnrichedDeprecationInfo;
rowFieldNames: DeprecationTableColumns[];
}
const ReindexTableRowCells: React.FunctionComponent<TableRowProps> = ({
rowFieldNames,
deprecation,
}) => {
const [showFlyout, setShowFlyout] = useState(false);
const reindexState = useReindexContext();
const { api } = useAppContext();
const {
addContent: addContentToGlobalFlyout,
removeContent: removeContentFromGlobalFlyout,
} = useGlobalFlyout();
const closeFlyout = useCallback(async () => {
removeContentFromGlobalFlyout('reindexFlyout');
setShowFlyout(false);
await api.sendReindexTelemetryData({ close: true });
}, [api, removeContentFromGlobalFlyout]);
useEffect(() => {
if (showFlyout) {
addContentToGlobalFlyout<ReindexFlyoutProps>({
id: 'reindexFlyout',
Component: ReindexFlyout,
props: {
deprecation,
closeFlyout,
...reindexState,
},
flyoutProps: {
onClose: closeFlyout,
'data-test-subj': 'reindexDetails',
'aria-labelledby': 'reindexDetailsFlyoutTitle',
},
});
}
}, [addContentToGlobalFlyout, deprecation, showFlyout, reindexState, closeFlyout]);
useEffect(() => {
if (showFlyout) {
async function sendTelemetry() {
await api.sendReindexTelemetryData({ open: true });
}
sendTelemetry();
}
}, [showFlyout, api]);
return (
<>
{rowFieldNames.map((field: DeprecationTableColumns) => {
return (
<EuiTableRowCell
key={field}
truncateText={false}
data-test-subj={`reindexTableCell-${field}`}
>
<EsDeprecationsTableCells
fieldName={field}
openFlyout={() => setShowFlyout(true)}
deprecation={deprecation}
resolutionTableCell={<ReindexResolutionCell />}
/>
</EuiTableRowCell>
);
})}
</>
);
};
export const ReindexTableRow: React.FunctionComponent<TableRowProps> = (props) => {
const { api } = useAppContext();
return (
<ReindexStatusProvider indexName={props.deprecation.index!} api={api}>
<ReindexTableRowCells {...props} />
</ReindexStatusProvider>
);
};

View file

@ -0,0 +1,187 @@
/*
* 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 { useRef, useCallback, useState, useEffect } from 'react';
import {
IndexGroup,
ReindexOperation,
ReindexStatus,
ReindexStep,
ReindexWarning,
} from '../../../../../../common/types';
import { LoadingState } from '../../../types';
import { ApiService } from '../../../../lib/api';
const POLL_INTERVAL = 1000;
export interface ReindexState {
loadingState: LoadingState;
cancelLoadingState?: LoadingState;
lastCompletedStep?: ReindexStep;
status?: ReindexStatus;
reindexTaskPercComplete: number | null;
errorMessage: string | null;
reindexWarnings?: ReindexWarning[];
hasRequiredPrivileges?: boolean;
indexGroup?: IndexGroup;
}
interface StatusResponse {
warnings?: ReindexWarning[];
reindexOp?: ReindexOperation;
hasRequiredPrivileges?: boolean;
indexGroup?: IndexGroup;
}
const getReindexState = (
reindexState: ReindexState,
{ reindexOp, warnings, hasRequiredPrivileges, indexGroup }: StatusResponse
) => {
const newReindexState = {
...reindexState,
loadingState: LoadingState.Success,
};
if (warnings) {
newReindexState.reindexWarnings = warnings;
}
if (hasRequiredPrivileges !== undefined) {
newReindexState.hasRequiredPrivileges = hasRequiredPrivileges;
}
if (indexGroup) {
newReindexState.indexGroup = indexGroup;
}
if (reindexOp) {
// Prevent the UI flickering back to inProgress after cancelling
newReindexState.lastCompletedStep = reindexOp.lastCompletedStep;
newReindexState.status = reindexOp.status;
newReindexState.reindexTaskPercComplete = reindexOp.reindexTaskPercComplete;
newReindexState.errorMessage = reindexOp.errorMessage;
if (reindexOp.status === ReindexStatus.cancelled) {
newReindexState.cancelLoadingState = LoadingState.Success;
}
}
return newReindexState;
};
export const useReindexStatus = ({ indexName, api }: { indexName: string; api: ApiService }) => {
const [reindexState, setReindexState] = useState<ReindexState>({
loadingState: LoadingState.Loading,
errorMessage: null,
reindexTaskPercComplete: null,
});
const pollIntervalIdRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const isMounted = useRef(false);
const clearPollInterval = useCallback(() => {
if (pollIntervalIdRef.current) {
clearTimeout(pollIntervalIdRef.current);
pollIntervalIdRef.current = null;
}
}, []);
const updateStatus = useCallback(async () => {
clearPollInterval();
const { data, error } = await api.getReindexStatus(indexName);
if (error) {
setReindexState({
...reindexState,
loadingState: LoadingState.Error,
status: ReindexStatus.failed,
});
return;
}
setReindexState(getReindexState(reindexState, data));
// Only keep polling if it exists and is in progress.
if (data.reindexOp && data.reindexOp.status === ReindexStatus.inProgress) {
pollIntervalIdRef.current = setTimeout(updateStatus, POLL_INTERVAL);
}
}, [clearPollInterval, api, indexName, reindexState]);
const startReindex = useCallback(async () => {
const currentReindexState = {
...reindexState,
};
setReindexState({
...currentReindexState,
// Only reset last completed step if we aren't currently paused
lastCompletedStep:
currentReindexState.status === ReindexStatus.paused
? currentReindexState.lastCompletedStep
: undefined,
status: ReindexStatus.inProgress,
reindexTaskPercComplete: null,
errorMessage: null,
cancelLoadingState: undefined,
});
api.sendReindexTelemetryData({ start: true });
const { data, error } = await api.startReindexTask(indexName);
if (error) {
setReindexState({
...reindexState,
loadingState: LoadingState.Error,
status: ReindexStatus.failed,
});
return;
}
setReindexState(getReindexState(reindexState, data));
updateStatus();
}, [api, indexName, reindexState, updateStatus]);
const cancelReindex = useCallback(async () => {
api.sendReindexTelemetryData({ stop: true });
const { error } = await api.cancelReindexTask(indexName);
setReindexState({
...reindexState,
cancelLoadingState: LoadingState.Loading,
});
if (error) {
setReindexState({
...reindexState,
cancelLoadingState: LoadingState.Error,
});
return;
}
}, [api, indexName, reindexState]);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
// Clean up on unmount.
clearPollInterval();
};
}, [clearPollInterval]);
return {
reindexState,
startReindex,
cancelReindex,
updateStatus,
};
};

View file

@ -1,4 +0,0 @@
.upgDeprecationCell {
overflow: hidden;
padding: $euiSize 0 0 $euiSizeL;
}

View file

@ -1,146 +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, { ReactNode, FunctionComponent } from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiLink,
EuiSpacer,
EuiText,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EnrichedDeprecationInfo,
MlAction,
ReindexAction,
IndexSettingAction,
} from '../../../../../common/types';
import { AppContext } from '../../../app_context';
import { ReindexButton } from './reindex';
import { FixIndexSettingsButton } from './index_settings';
import { FixMlSnapshotsButton } from './ml_snapshots';
interface DeprecationCellProps {
items?: Array<{ title?: string; body: string }>;
docUrl?: string;
headline?: string;
healthColor?: string;
children?: ReactNode;
correctiveAction?: EnrichedDeprecationInfo['correctiveAction'];
indexName?: string;
}
interface CellActionProps {
correctiveAction: EnrichedDeprecationInfo['correctiveAction'];
indexName?: string;
items: Array<{ title?: string; body: string }>;
}
const CellAction: FunctionComponent<CellActionProps> = ({ correctiveAction, indexName, items }) => {
const { type: correctiveActionType } = correctiveAction!;
switch (correctiveActionType) {
case 'mlSnapshot':
const { jobId, snapshotId } = correctiveAction as MlAction;
return (
<FixMlSnapshotsButton
jobId={jobId}
snapshotId={snapshotId}
// There will only ever be a single item for the cluster deprecations list, so we can use the index to access the first one
description={items[0]?.body}
/>
);
case 'reindex':
const { blockerForReindexing } = correctiveAction as ReindexAction;
return (
<AppContext.Consumer>
{({ http, docLinks }) => (
<ReindexButton
docLinks={docLinks}
reindexBlocker={blockerForReindexing}
indexName={indexName!}
http={http}
/>
)}
</AppContext.Consumer>
);
case 'indexSetting':
const { deprecatedSettings } = correctiveAction as IndexSettingAction;
return <FixIndexSettingsButton settings={deprecatedSettings} index={indexName!} />;
default:
throw new Error(`No UI defined for corrective action: ${correctiveActionType}`);
}
};
/**
* Used to display a deprecation with links to docs, a health indicator, and other descriptive information.
*/
export const DeprecationCell: FunctionComponent<DeprecationCellProps> = ({
headline,
healthColor,
correctiveAction,
indexName,
docUrl,
items = [],
children,
}) => (
<div className="upgDeprecationCell">
<EuiFlexGroup responsive={false} wrap alignItems="baseline">
{healthColor && (
<EuiFlexItem grow={false}>
<EuiIcon type="dot" color={healthColor} />
</EuiFlexItem>
)}
<EuiFlexItem grow>
{headline && (
<EuiTitle size="xxs">
<h2>{headline}</h2>
</EuiTitle>
)}
{items.map((item, index) => (
<EuiText key={`deprecation-item-${index}`}>
{item.title && <h6>{item.title}</h6>}
<p>{item.body}</p>
</EuiText>
))}
{docUrl && (
<>
<EuiSpacer size="s" />
<EuiLink href={docUrl} target="_blank">
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.deprecations.documentationButtonLabel"
defaultMessage="Documentation"
/>
</EuiLink>
</>
)}
</EuiFlexItem>
{correctiveAction && (
<EuiFlexItem grow={false}>
<CellAction correctiveAction={correctiveAction} indexName={indexName} items={items} />
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiSpacer size="s" />
{children}
</div>
);

View file

@ -1,75 +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 { EuiAccordion, EuiBadge } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { EnrichedDeprecationInfo } from '../../../../../common/types';
import { DeprecationHealth } from '../../shared';
import { GroupByOption } from '../../types';
import { EsDeprecationList } from './list';
import { LEVEL_MAP } from '../../constants';
export interface Props {
id: string;
deprecations: EnrichedDeprecationInfo[];
title: string;
currentGroupBy: GroupByOption;
forceExpand: boolean;
dataTestSubj: string;
}
/**
* A single accordion item for a grouped deprecation item.
*/
export const EsDeprecationAccordion: FunctionComponent<Props> = ({
id,
deprecations,
title,
currentGroupBy,
forceExpand,
dataTestSubj,
}) => {
const hasIndices = Boolean(
currentGroupBy === GroupByOption.message &&
(deprecations as EnrichedDeprecationInfo[]).filter((d) => d.index).length
);
const numIndices = hasIndices ? deprecations.length : null;
return (
<EuiAccordion
id={id}
key={id}
data-test-subj={dataTestSubj}
initialIsOpen={forceExpand}
buttonContent={title}
extraAction={
<div>
{hasIndices && (
<>
<EuiBadge color="hollow">
<span data-test-subj="indexCount">{numIndices}</span>{' '}
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.indicesBadgeLabel"
defaultMessage="{numIndices, plural, one {index} other {indices}}"
values={{ numIndices }}
/>
</EuiBadge>
&emsp;
</>
)}
<DeprecationHealth
single={currentGroupBy === GroupByOption.message}
deprecationLevels={deprecations.map((d) => LEVEL_MAP[d.level])}
/>
</div>
}
>
<EsDeprecationList deprecations={deprecations} currentGroupBy={currentGroupBy} />
</EuiAccordion>
);
};

View file

@ -1,54 +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 { EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { RemoveIndexSettingsProvider } from './remove_settings_provider';
const i18nTexts = {
fixButtonLabel: i18n.translate('xpack.upgradeAssistant.checkupTab.indexSettings.fixButtonLabel', {
defaultMessage: 'Fix',
}),
doneButtonLabel: i18n.translate(
'xpack.upgradeAssistant.checkupTab.indexSettings.doneButtonLabel',
{
defaultMessage: 'Done',
}
),
};
interface Props {
settings: string[];
index: string;
}
/**
* Renders a button if the given index contains deprecated index settings
*/
export const FixIndexSettingsButton: React.FunctionComponent<Props> = ({ settings, index }) => {
return (
<RemoveIndexSettingsProvider>
{(removeIndexSettingsPrompt, successfulRequests) => {
const isSuccessfulRequest = successfulRequests[index] === true;
return (
<EuiButton
size="s"
data-test-subj="removeIndexSettingsButton"
onClick={() => removeIndexSettingsPrompt(index, settings)}
isDisabled={isSuccessfulRequest}
iconType={isSuccessfulRequest ? 'check' : undefined}
>
{isSuccessfulRequest ? i18nTexts.doneButtonLabel : i18nTexts.fixButtonLabel}
</EuiButton>
);
}}
</RemoveIndexSettingsProvider>
);
};

View file

@ -1,131 +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, { useState, useRef } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiCode, EuiConfirmModal } from '@elastic/eui';
import { useAppContext } from '../../../../app_context';
interface Props {
children: (
removeSettingsPrompt: (index: string, settings: string[]) => void,
successfulRequests: { [key: string]: boolean }
) => React.ReactNode;
}
const i18nTexts = {
removeButtonLabel: i18n.translate(
'xpack.upgradeAssistant.checkupTab.confirmationModal.removeButtonLabel',
{
defaultMessage: 'Remove',
}
),
cancelButtonLabel: i18n.translate(
'xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.cancelButtonLabel',
{
defaultMessage: 'Cancel',
}
),
modalDescription: i18n.translate(
'xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.description',
{
defaultMessage: 'The following deprecated index settings were detected and will be removed:',
}
),
successNotificationText: i18n.translate(
'xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.successNotificationText',
{
defaultMessage: 'Index settings removed',
}
),
errorNotificationText: i18n.translate(
'xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.errorNotificationText',
{
defaultMessage: 'Error removing index settings',
}
),
};
export const RemoveIndexSettingsProvider = ({ children }: Props) => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [successfulRequests, setSuccessfulRequests] = useState<{ [key: string]: boolean }>({});
const [isLoading, setIsLoading] = useState<boolean>(false);
const deprecatedSettings = useRef<string[]>([]);
const indexName = useRef<string | undefined>(undefined);
const { api, notifications } = useAppContext();
const removeIndexSettings = async () => {
setIsLoading(true);
const { error } = await api.updateIndexSettings(indexName.current!, deprecatedSettings.current);
setIsLoading(false);
closeModal();
if (error) {
notifications.toasts.addDanger(i18nTexts.errorNotificationText);
} else {
setSuccessfulRequests({
[indexName.current!]: true,
});
notifications.toasts.addSuccess(i18nTexts.successNotificationText);
}
};
const closeModal = () => {
setIsModalOpen(false);
};
const removeSettingsPrompt = (index: string, settings: string[]) => {
setIsModalOpen(true);
setSuccessfulRequests({
[index]: false,
});
indexName.current = index;
deprecatedSettings.current = settings;
};
return (
<>
{children(removeSettingsPrompt, successfulRequests)}
{isModalOpen && (
<EuiConfirmModal
title={i18n.translate(
'xpack.upgradeAssistant.checkupTab.indexSettings.confirmationModal.title',
{
defaultMessage: `Remove deprecated settings from '{indexName}'?`,
values: {
indexName: indexName.current,
},
}
)}
data-test-subj="indexSettingsDeleteConfirmModal"
onCancel={closeModal}
onConfirm={removeIndexSettings}
cancelButtonText={i18nTexts.cancelButtonLabel}
buttonColor="danger"
confirmButtonText={i18nTexts.removeButtonLabel}
isLoading={isLoading}
>
<>
<p>{i18nTexts.modalDescription}</p>
<ul>
{deprecatedSettings.current.map((setting, index) => (
<li key={`${setting}-${index}`}>
<EuiCode>{setting}</EuiCode>
</li>
))}
</ul>
</>
</EuiConfirmModal>
)}
</>
);
};

View file

@ -1,99 +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 { shallow } from 'enzyme';
import { IndexDeprecationTableProps, IndexDeprecationTable } from './index_table';
describe('IndexDeprecationTable', () => {
const defaultProps = {
indices: [
{ index: 'index1', details: 'Index 1 deets', correctiveAction: { type: 'reindex' } },
{ index: 'index2', details: 'Index 2 deets', correctiveAction: { type: 'reindex' } },
{ index: 'index3', details: 'Index 3 deets', correctiveAction: { type: 'reindex' } },
],
} as IndexDeprecationTableProps;
// Relying pretty heavily on EUI to implement the table functionality correctly.
// This test simply verifies that the props passed to EuiBaseTable are the ones
// expected.
test('render', () => {
expect(shallow(<IndexDeprecationTable {...defaultProps} />)).toMatchInlineSnapshot(`
<EuiBasicTable
columns={
Array [
Object {
"field": "index",
"name": "Index",
"sortable": true,
},
Object {
"field": "details",
"name": "Details",
},
Object {
"actions": Array [
Object {
"render": [Function],
},
],
},
]
}
hasActions={false}
items={
Array [
Object {
"correctiveAction": Object {
"type": "reindex",
},
"details": "Index 1 deets",
"index": "index1",
},
Object {
"correctiveAction": Object {
"type": "reindex",
},
"details": "Index 2 deets",
"index": "index2",
},
Object {
"correctiveAction": Object {
"type": "reindex",
},
"details": "Index 3 deets",
"index": "index3",
},
]
}
noItemsMessage="No items found"
onChange={[Function]}
pagination={
Object {
"hidePerPageOptions": true,
"pageIndex": 0,
"pageSize": 10,
"pageSizeOptions": Array [],
"totalItemCount": 3,
}
}
responsive={true}
rowProps={[Function]}
sorting={
Object {
"sort": Object {
"direction": "asc",
"field": "index",
},
}
}
tableLayout="fixed"
/>
`);
});
});

View file

@ -1,200 +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 { sortBy } from 'lodash';
import React from 'react';
import { EuiBasicTable } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import {
EnrichedDeprecationInfo,
IndexSettingAction,
ReindexAction,
} from '../../../../../common/types';
import { AppContext } from '../../../app_context';
import { ReindexButton } from './reindex';
import { FixIndexSettingsButton } from './index_settings';
const PAGE_SIZES = [10, 25, 50, 100, 250, 500, 1000];
export interface IndexDeprecationDetails {
index: string;
correctiveAction?: EnrichedDeprecationInfo['correctiveAction'];
details?: string;
}
export interface IndexDeprecationTableProps {
indices: IndexDeprecationDetails[];
}
interface IndexDeprecationTableState {
sortField: string;
sortDirection: 'asc' | 'desc';
pageIndex: number;
pageSize: number;
}
export class IndexDeprecationTable extends React.Component<
IndexDeprecationTableProps,
IndexDeprecationTableState
> {
constructor(props: IndexDeprecationTableProps) {
super(props);
this.state = {
sortField: 'index',
sortDirection: 'asc',
pageIndex: 0,
pageSize: 10,
};
}
public render() {
const { pageIndex, pageSize, sortField, sortDirection } = this.state;
const columns = [
{
field: 'index',
name: i18n.translate(
'xpack.upgradeAssistant.checkupTab.deprecations.indexTable.indexColumnLabel',
{
defaultMessage: 'Index',
}
),
sortable: true,
},
{
field: 'details',
name: i18n.translate(
'xpack.upgradeAssistant.checkupTab.deprecations.indexTable.detailsColumnLabel',
{
defaultMessage: 'Details',
}
),
},
];
const actionsColumn = this.generateActionsColumn();
if (actionsColumn) {
columns.push(actionsColumn as any);
}
const sorting = {
sort: { field: sortField as keyof IndexDeprecationDetails, direction: sortDirection },
};
const pagination = {
pageIndex,
pageSize,
...this.pageSizeOptions(),
};
return (
<EuiBasicTable
items={this.getRows()}
columns={columns}
sorting={sorting}
pagination={pagination}
onChange={this.onTableChange}
hasActions={false}
rowProps={(indexDetails) => {
return {
'data-test-subj': `indexTableRow-${indexDetails.index}`,
};
}}
/>
);
}
private getRows() {
const { sortField, sortDirection, pageIndex, pageSize } = this.state;
const { indices } = this.props;
let sorted = sortBy(indices, sortField);
if (sortDirection === 'desc') {
sorted = sorted.reverse();
}
const start = pageIndex * pageSize;
return sorted.slice(start, start + pageSize);
}
private onTableChange = (tableProps: any) => {
this.setState({
sortField: tableProps.sort.field,
sortDirection: tableProps.sort.direction,
pageIndex: tableProps.page.index,
pageSize: tableProps.page.size,
});
};
private pageSizeOptions() {
const { indices } = this.props;
const totalItemCount = indices.length;
// If we only have that smallest page size, don't show any page size options.
if (totalItemCount <= PAGE_SIZES[0]) {
return { totalItemCount, pageSizeOptions: [], hidePerPageOptions: true };
}
// Keep a size option if the # of items is larger than the previous option.
// This avoids having a long list of useless page sizes.
const pageSizeOptions = PAGE_SIZES.filter((perPage, idx) => {
return idx === 0 || totalItemCount > PAGE_SIZES[idx - 1];
});
return { totalItemCount, pageSizeOptions, hidePerPageOptions: false };
}
private generateActionsColumn() {
// NOTE: this naive implementation assumes all indices in the table
// should show the reindex button or fix indices button. This should work for known use cases.
const { indices } = this.props;
const showReindexButton = Boolean(indices.find((i) => i.correctiveAction?.type === 'reindex'));
const showFixSettingsButton = Boolean(
indices.find((i) => i.correctiveAction?.type === 'indexSetting')
);
if (showReindexButton === false && showFixSettingsButton === false) {
return null;
}
return {
actions: [
{
render(indexDep: IndexDeprecationDetails) {
if (showReindexButton) {
return (
<AppContext.Consumer>
{({ http, docLinks }) => {
return (
<ReindexButton
docLinks={docLinks}
reindexBlocker={
(indexDep.correctiveAction as ReindexAction).blockerForReindexing
}
indexName={indexDep.index!}
http={http}
/>
);
}}
</AppContext.Consumer>
);
}
return (
<FixIndexSettingsButton
settings={(indexDep.correctiveAction as IndexSettingAction).deprecatedSettings}
index={indexDep.index}
/>
);
},
},
],
};
}
}

View file

@ -1,129 +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 { shallow } from 'enzyme';
import React from 'react';
import { EnrichedDeprecationInfo } from '../../../../../common/types';
import { GroupByOption } from '../../types';
import { EsDeprecationList } from './list';
describe('EsDeprecationList', () => {
describe('group by message', () => {
const defaultProps = {
deprecations: [
{ message: 'Issue 1', url: '', level: 'warning' },
{ message: 'Issue 1', url: '', level: 'warning' },
] as EnrichedDeprecationInfo[],
currentGroupBy: GroupByOption.message,
};
test('shows simple messages when index field is not present', () => {
expect(shallow(<EsDeprecationList {...defaultProps} />)).toMatchInlineSnapshot(`
<div>
<SimpleMessageDeprecation
deprecation={
Object {
"level": "warning",
"message": "Issue 1",
"url": "",
}
}
key="Issue 1-0"
/>
<SimpleMessageDeprecation
deprecation={
Object {
"level": "warning",
"message": "Issue 1",
"url": "",
}
}
key="Issue 1-1"
/>
</div>
`);
});
test('shows index deprecation when index field is present', () => {
// Add index fields to deprecation items
const props = {
...defaultProps,
deprecations: defaultProps.deprecations.map((d, index) => ({
...d,
index: index.toString(),
})),
};
const wrapper = shallow(<EsDeprecationList {...props} />);
expect(wrapper).toMatchInlineSnapshot(`
<IndexDeprecation
deprecation={
Object {
"index": "0",
"level": "warning",
"message": "Issue 1",
"url": "",
}
}
indices={
Array [
Object {
"correctiveAction": undefined,
"details": undefined,
"index": "0",
},
Object {
"correctiveAction": undefined,
"details": undefined,
"index": "1",
},
]
}
/>
`);
});
});
describe('group by index', () => {
const defaultProps = {
deprecations: [
{ message: 'Issue 1', index: 'index1', url: '', level: 'warning' },
{ message: 'Issue 2', index: 'index1', url: '', level: 'warning' },
] as EnrichedDeprecationInfo[],
currentGroupBy: GroupByOption.index,
};
test('shows detailed messages', () => {
expect(shallow(<EsDeprecationList {...defaultProps} />)).toMatchInlineSnapshot(`
<div>
<MessageDeprecation
deprecation={
Object {
"index": "index1",
"level": "warning",
"message": "Issue 1",
"url": "",
}
}
key="Issue 1-0"
/>
<MessageDeprecation
deprecation={
Object {
"index": "index1",
"level": "warning",
"message": "Issue 2",
"url": "",
}
}
key="Issue 2-1"
/>
</div>
`);
});
});
});

View file

@ -1,120 +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 { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../common/types';
import { GroupByOption } from '../../types';
import { COLOR_MAP, LEVEL_MAP } from '../../constants';
import { DeprecationCell } from './cell';
import { IndexDeprecationDetails, IndexDeprecationTable } from './index_table';
const sortByLevelDesc = (a: DeprecationInfo, b: DeprecationInfo) => {
return -1 * (LEVEL_MAP[a.level] - LEVEL_MAP[b.level]);
};
/**
* Used to show a single deprecation message with any detailed information.
*/
const MessageDeprecation: FunctionComponent<{
deprecation: EnrichedDeprecationInfo;
}> = ({ deprecation }) => {
const items = [];
if (deprecation.details) {
items.push({ body: deprecation.details });
}
return (
<DeprecationCell
headline={deprecation.message}
healthColor={COLOR_MAP[deprecation.level]}
correctiveAction={deprecation.correctiveAction}
indexName={deprecation.index}
docUrl={deprecation.url}
items={items}
/>
);
};
/**
* Used to show a single (simple) deprecation message with any detailed information.
*/
const SimpleMessageDeprecation: FunctionComponent<{ deprecation: EnrichedDeprecationInfo }> = ({
deprecation,
}) => {
const items = [];
if (deprecation.details) {
items.push({ body: deprecation.details });
}
return (
<DeprecationCell
correctiveAction={deprecation.correctiveAction}
indexName={deprecation.index}
items={items}
docUrl={deprecation.url}
/>
);
};
interface IndexDeprecationProps {
deprecation: EnrichedDeprecationInfo;
indices: IndexDeprecationDetails[];
}
/**
* Shows a single deprecation and table of affected indices with details for each index.
*/
const IndexDeprecation: FunctionComponent<IndexDeprecationProps> = ({ deprecation, indices }) => {
return (
<DeprecationCell docUrl={deprecation.url}>
<IndexDeprecationTable indices={indices} />
</DeprecationCell>
);
};
/**
* A list of deprecations that is either shown as individual deprecation cells or as a
* deprecation summary for a list of indices.
*/
export const EsDeprecationList: FunctionComponent<{
deprecations: EnrichedDeprecationInfo[];
currentGroupBy: GroupByOption;
}> = ({ deprecations, currentGroupBy }) => {
// If we're grouping by message and the first deprecation has an index field, show an index
// group deprecation. Otherwise, show each message.
if (currentGroupBy === GroupByOption.message && deprecations[0].index !== undefined) {
// We assume that every deprecation message is the same issue (since they have the same
// message) and that each deprecation will have an index associated with it.
const indices = deprecations.map((dep) => ({
index: dep.index!,
details: dep.details,
correctiveAction: dep.correctiveAction,
}));
return <IndexDeprecation indices={indices} deprecation={deprecations[0]} />;
} else if (currentGroupBy === GroupByOption.index) {
return (
<div>
{deprecations.sort(sortByLevelDesc).map((dep, index) => (
<MessageDeprecation deprecation={dep} key={`${dep.message}-${index}`} />
))}
</div>
);
} else {
return (
<div>
{deprecations.sort(sortByLevelDesc).map((dep, index) => (
<SimpleMessageDeprecation deprecation={dep} key={`${dep.message}-${index}`} />
))}
</div>
);
}
};

View file

@ -1,125 +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 { ButtonSize, EuiButton } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FixSnapshotsFlyout } from './fix_snapshots_flyout';
import { useAppContext } from '../../../../app_context';
import { useSnapshotState } from './use_snapshot_state';
const i18nTexts = {
fixButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.fixButtonLabel',
{
defaultMessage: 'Fix',
}
),
upgradingButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.upgradingButtonLabel',
{
defaultMessage: 'Upgrading…',
}
),
deletingButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.deletingButtonLabel',
{
defaultMessage: 'Deleting…',
}
),
doneButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.doneButtonLabel',
{
defaultMessage: 'Done',
}
),
failedButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.failedButtonLabel',
{
defaultMessage: 'Failed',
}
),
};
interface Props {
snapshotId: string;
jobId: string;
description: string;
}
export const FixMlSnapshotsButton: React.FunctionComponent<Props> = ({
snapshotId,
jobId,
description,
}) => {
const { api } = useAppContext();
const { snapshotState, upgradeSnapshot, deleteSnapshot, updateSnapshotStatus } = useSnapshotState(
{
jobId,
snapshotId,
api,
}
);
const [showFlyout, setShowFlyout] = useState(false);
useEffect(() => {
updateSnapshotStatus();
}, [updateSnapshotStatus]);
const commonButtonProps = {
size: 's' as ButtonSize,
onClick: () => setShowFlyout(true),
'data-test-subj': 'fixMlSnapshotsButton',
};
let button = <EuiButton {...commonButtonProps}>{i18nTexts.fixButtonLabel}</EuiButton>;
switch (snapshotState.status) {
case 'in_progress':
button = (
<EuiButton color="secondary" {...commonButtonProps} isLoading>
{snapshotState.action === 'delete'
? i18nTexts.deletingButtonLabel
: i18nTexts.upgradingButtonLabel}
</EuiButton>
);
break;
case 'complete':
button = (
<EuiButton color="secondary" iconType="check" {...commonButtonProps} disabled>
{i18nTexts.doneButtonLabel}
</EuiButton>
);
break;
case 'error':
button = (
<EuiButton color="danger" iconType="cross" {...commonButtonProps}>
{i18nTexts.failedButtonLabel}
</EuiButton>
);
break;
}
return (
<>
{button}
{showFlyout && (
<FixSnapshotsFlyout
snapshotState={snapshotState}
upgradeSnapshot={upgradeSnapshot}
deleteSnapshot={deleteSnapshot}
description={description}
closeFlyout={() => setShowFlyout(false)}
/>
)}
</>
);
};

View file

@ -1,5 +0,0 @@
.upgReindexButton__spinner {
position: relative;
top: $euiSizeXS / 2;
margin-right: $euiSizeXS;
}

View file

@ -1,244 +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 { set } from '@elastic/safer-lodash-set';
import React, { Fragment, ReactNode } from 'react';
import { i18n } from '@kbn/i18n';
import { Subscription } from 'rxjs';
import { EuiButton, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { DocLinksStart, HttpSetup } from 'src/core/public';
import { API_BASE_PATH } from '../../../../../../common/constants';
import { ReindexAction, ReindexStatus, UIReindexOption } from '../../../../../../common/types';
import { LoadingState } from '../../../types';
import { ReindexFlyout } from './flyout';
import { ReindexPollingService, ReindexState } from './polling_service';
interface ReindexButtonProps {
indexName: string;
http: HttpSetup;
docLinks: DocLinksStart;
reindexBlocker?: ReindexAction['blockerForReindexing'];
}
interface ReindexButtonState {
flyoutVisible: boolean;
reindexState: ReindexState;
}
/**
* Displays a button that will display a flyout when clicked with the reindexing status for
* the given `indexName`.
*/
export class ReindexButton extends React.Component<ReindexButtonProps, ReindexButtonState> {
private service: ReindexPollingService;
private subscription?: Subscription;
constructor(props: ReindexButtonProps) {
super(props);
this.service = this.newService();
this.state = {
flyoutVisible: false,
reindexState: this.service.status$.value,
};
}
public async componentDidMount() {
this.subscribeToUpdates();
}
public async componentWillUnmount() {
this.unsubscribeToUpdates();
}
public componentDidUpdate(prevProps: ReindexButtonProps) {
if (prevProps.indexName !== this.props.indexName) {
this.unsubscribeToUpdates();
this.service = this.newService();
this.subscribeToUpdates();
}
}
public render() {
const { indexName, reindexBlocker, docLinks } = this.props;
const { flyoutVisible, reindexState } = this.state;
const buttonProps: any = { size: 's', onClick: this.showFlyout };
let buttonContent: ReactNode = (
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.reindexLabel"
defaultMessage="Reindex"
/>
);
if (reindexState.loadingState === LoadingState.Loading) {
buttonProps.disabled = true;
buttonContent = (
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.loadingLabel"
defaultMessage="Loading…"
/>
);
} else {
switch (reindexState.status) {
case ReindexStatus.inProgress:
buttonContent = (
<span>
<EuiLoadingSpinner className="upgReindexButton__spinner" size="m" /> Reindexing
</span>
);
break;
case ReindexStatus.completed:
buttonProps.color = 'secondary';
buttonProps.iconSide = 'left';
buttonProps.iconType = 'check';
buttonContent = (
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.doneLabel"
defaultMessage="Done"
/>
);
break;
case ReindexStatus.failed:
buttonProps.color = 'danger';
buttonProps.iconSide = 'left';
buttonProps.iconType = 'cross';
buttonContent = (
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.failedLabel"
defaultMessage="Failed"
/>
);
break;
case ReindexStatus.paused:
buttonProps.color = 'warning';
buttonProps.iconSide = 'left';
buttonProps.iconType = 'pause';
buttonContent = (
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.pausedLabel"
defaultMessage="Paused"
/>
);
case ReindexStatus.cancelled:
buttonProps.color = 'danger';
buttonProps.iconSide = 'left';
buttonProps.iconType = 'cross';
buttonContent = (
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.cancelledLabel"
defaultMessage="Cancelled"
/>
);
break;
}
}
const showIndexedClosedWarning =
reindexBlocker === 'index-closed' && reindexState.status !== ReindexStatus.completed;
if (showIndexedClosedWarning) {
buttonProps.color = 'warning';
buttonProps.iconType = 'alert';
}
const button = <EuiButton {...buttonProps}>{buttonContent}</EuiButton>;
return (
<Fragment>
{showIndexedClosedWarning ? (
<EuiToolTip
position="top"
content={
<EuiText size="s">
{i18n.translate(
'xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.indexClosedToolTipDetails',
{
defaultMessage:
'"{indexName}" needs to be reindexed, but it is currently closed. The Upgrade Assistant will open, reindex and then close the index. Reindexing may take longer than usual.',
values: { indexName },
}
)}
</EuiText>
}
>
{button}
</EuiToolTip>
) : (
button
)}
{flyoutVisible && (
<ReindexFlyout
reindexBlocker={reindexBlocker}
docLinks={docLinks}
indexName={indexName}
closeFlyout={this.closeFlyout}
reindexState={reindexState}
startReindex={this.startReindex}
cancelReindex={this.cancelReindex}
/>
)}
</Fragment>
);
}
private newService() {
const { indexName, http } = this.props;
return new ReindexPollingService(indexName, http);
}
private subscribeToUpdates() {
this.service.updateStatus();
this.subscription = this.service!.status$.subscribe((reindexState) =>
this.setState({ reindexState })
);
}
private unsubscribeToUpdates() {
if (this.subscription) {
this.subscription.unsubscribe();
delete this.subscription;
}
if (this.service) {
this.service.stopPolling();
}
}
private startReindex = async () => {
if (!this.state.reindexState.status) {
// if status didn't exist we are starting a reindex action
this.sendUIReindexTelemetryInfo('start');
}
await this.service.startReindex();
};
private cancelReindex = async () => {
this.sendUIReindexTelemetryInfo('stop');
await this.service.cancelReindex();
};
private showFlyout = () => {
this.sendUIReindexTelemetryInfo('open');
this.setState({ flyoutVisible: true });
};
private closeFlyout = () => {
this.sendUIReindexTelemetryInfo('close');
this.setState({ flyoutVisible: false });
};
private async sendUIReindexTelemetryInfo(uiReindexAction: UIReindexOption) {
await this.props.http.put(`${API_BASE_PATH}/stats/ui_reindex`, {
body: JSON.stringify(set({}, uiReindexAction, true)),
});
}
}

View file

@ -1,172 +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 { DocLinksStart } from 'kibana/public';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiCallOut,
EuiFlyout,
EuiFlyoutHeader,
EuiLink,
EuiPortal,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { ReindexAction, ReindexStatus } from '../../../../../../../common/types';
import { ReindexState } from '../polling_service';
import { ChecklistFlyoutStep } from './checklist_step';
import { WarningsFlyoutStep } from './warnings_step';
enum ReindexFlyoutStep {
reindexWarnings,
checklist,
}
interface ReindexFlyoutProps {
indexName: string;
closeFlyout: () => void;
reindexState: ReindexState;
startReindex: () => void;
cancelReindex: () => void;
docLinks: DocLinksStart;
reindexBlocker?: ReindexAction['blockerForReindexing'];
}
interface ReindexFlyoutState {
currentFlyoutStep: ReindexFlyoutStep;
}
const getOpenAndCloseIndexDocLink = (docLinks: DocLinksStart) => (
<EuiLink target="_blank" href={`${docLinks.links.apis.openIndex}`}>
{i18n.translate(
'xpack.upgradeAssistant.checkupTab.reindexing.flyout.openAndCloseDocumentation',
{ defaultMessage: 'documentation' }
)}
</EuiLink>
);
const getIndexClosedCallout = (docLinks: DocLinksStart) => (
<>
<EuiCallOut
title={i18n.translate(
'xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutTitle',
{ defaultMessage: 'Index closed' }
)}
color="warning"
iconType="alert"
>
<p>
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutDetails"
defaultMessage="This index is currently closed. The Upgrade Assistant will open, reindex and then close the index. {reindexingMayTakeLongerEmph}. Please see the {docs} for more information."
values={{
docs: getOpenAndCloseIndexDocLink(docLinks),
reindexingMayTakeLongerEmph: (
<b>
{i18n.translate(
'xpack.upgradeAssistant.checkupTab.reindexing.flyout.indexClosedCallout.calloutDetails.reindexingTakesLongerEmphasis',
{ defaultMessage: 'Reindexing may take longer than usual' }
)}
</b>
),
}}
/>
</p>
</EuiCallOut>
<EuiSpacer size="m" />
</>
);
/**
* Wrapper for the contents of the flyout that manages which step of the flyout to show.
*/
export class ReindexFlyout extends React.Component<ReindexFlyoutProps, ReindexFlyoutState> {
constructor(props: ReindexFlyoutProps) {
super(props);
const { status, reindexWarnings } = props.reindexState;
this.state = {
// If there are any warnings and we haven't started reindexing, show the warnings step first.
currentFlyoutStep:
reindexWarnings && reindexWarnings.length > 0 && status === undefined
? ReindexFlyoutStep.reindexWarnings
: ReindexFlyoutStep.checklist,
};
}
public render() {
const {
closeFlyout,
indexName,
reindexState,
startReindex,
cancelReindex,
reindexBlocker,
docLinks,
} = this.props;
const { currentFlyoutStep } = this.state;
let flyoutContents: React.ReactNode;
const globalCallout =
reindexBlocker === 'index-closed' && reindexState.status !== ReindexStatus.completed
? getIndexClosedCallout(docLinks)
: undefined;
switch (currentFlyoutStep) {
case ReindexFlyoutStep.reindexWarnings:
flyoutContents = (
<WarningsFlyoutStep
renderGlobalCallouts={() => globalCallout}
closeFlyout={closeFlyout}
warnings={reindexState.reindexWarnings!}
advanceNextStep={this.advanceNextStep}
/>
);
break;
case ReindexFlyoutStep.checklist:
flyoutContents = (
<ChecklistFlyoutStep
renderGlobalCallouts={() => globalCallout}
closeFlyout={closeFlyout}
reindexState={reindexState}
startReindex={startReindex}
cancelReindex={cancelReindex}
/>
);
break;
default:
throw new Error(`Invalid flyout step: ${currentFlyoutStep}`);
}
return (
<EuiPortal>
<EuiFlyout onClose={closeFlyout} aria-labelledby="Reindex" ownFocus size="m" maxWidth>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="s">
<h2>
<FormattedMessage
id="xpack.upgradeAssistant.checkupTab.reindexing.flyout.flyoutHeader"
defaultMessage="Reindex {indexName}"
values={{ indexName }}
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
{flyoutContents}
</EuiFlyout>
</EuiPortal>
);
}
public advanceNextStep = () => {
this.setState({ currentFlyoutStep: ReindexFlyoutStep.checklist });
};
}

View file

@ -1,87 +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 { ReindexStatus, ReindexStep } from '../../../../../../common/types';
import { ReindexPollingService } from './polling_service';
import { httpServiceMock } from 'src/core/public/mocks';
const mockClient = httpServiceMock.createSetupContract();
describe('ReindexPollingService', () => {
beforeEach(() => {
mockClient.post.mockReset();
mockClient.get.mockReset();
});
it('does not poll when reindexOp is null', async () => {
mockClient.get.mockResolvedValueOnce({
warnings: [],
reindexOp: null,
});
const service = new ReindexPollingService('myIndex', mockClient);
service.updateStatus();
await new Promise((resolve) => setTimeout(resolve, 1200)); // wait for poll interval
expect(mockClient.get).toHaveBeenCalledTimes(1);
service.stopPolling();
});
it('does not poll when first check is a 200 and status is failed', async () => {
mockClient.get.mockResolvedValue({
warnings: [],
reindexOp: {
lastCompletedStep: ReindexStep.created,
status: ReindexStatus.failed,
errorMessage: `Oh no!`,
},
});
const service = new ReindexPollingService('myIndex', mockClient);
service.updateStatus();
await new Promise((resolve) => setTimeout(resolve, 1200)); // wait for poll interval
expect(mockClient.get).toHaveBeenCalledTimes(1);
expect(service.status$.value.errorMessage).toEqual(`Oh no!`);
service.stopPolling();
});
it('begins to poll when first check is a 200 and status is inProgress', async () => {
mockClient.get.mockResolvedValue({
warnings: [],
reindexOp: {
lastCompletedStep: ReindexStep.created,
status: ReindexStatus.inProgress,
},
});
const service = new ReindexPollingService('myIndex', mockClient);
service.updateStatus();
await new Promise((resolve) => setTimeout(resolve, 1200)); // wait for poll interval
expect(mockClient.get).toHaveBeenCalledTimes(2);
service.stopPolling();
});
describe('startReindex', () => {
it('posts to endpoint', async () => {
const service = new ReindexPollingService('myIndex', mockClient);
await service.startReindex();
expect(mockClient.post).toHaveBeenCalledWith('/api/upgrade_assistant/reindex/myIndex');
});
});
describe('cancelReindex', () => {
it('posts to cancel endpoint', async () => {
const service = new ReindexPollingService('myIndex', mockClient);
await service.cancelReindex();
expect(mockClient.post).toHaveBeenCalledWith('/api/upgrade_assistant/reindex/myIndex/cancel');
});
});
});

View file

@ -1,169 +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 { BehaviorSubject } from 'rxjs';
import { HttpSetup } from 'src/core/public';
import { API_BASE_PATH } from '../../../../../../common/constants';
import {
IndexGroup,
ReindexOperation,
ReindexStatus,
ReindexStep,
ReindexWarning,
} from '../../../../../../common/types';
import { LoadingState } from '../../../types';
const POLL_INTERVAL = 1000;
export interface ReindexState {
loadingState: LoadingState;
cancelLoadingState?: LoadingState;
lastCompletedStep?: ReindexStep;
status?: ReindexStatus;
reindexTaskPercComplete: number | null;
errorMessage: string | null;
reindexWarnings?: ReindexWarning[];
hasRequiredPrivileges?: boolean;
indexGroup?: IndexGroup;
}
interface StatusResponse {
warnings?: ReindexWarning[];
reindexOp?: ReindexOperation;
hasRequiredPrivileges?: boolean;
indexGroup?: IndexGroup;
}
/**
* Service used by the frontend to start reindexing and get updates on the state of a reindex
* operation. Exposes an Observable that can be used to subscribe to state updates.
*/
export class ReindexPollingService {
public status$: BehaviorSubject<ReindexState>;
private pollTimeout?: NodeJS.Timeout;
constructor(private indexName: string, private http: HttpSetup) {
this.status$ = new BehaviorSubject<ReindexState>({
loadingState: LoadingState.Loading,
errorMessage: null,
reindexTaskPercComplete: null,
});
}
public updateStatus = async () => {
// Prevent two loops from being started.
this.stopPolling();
try {
const data = await this.http.get<StatusResponse>(
`${API_BASE_PATH}/reindex/${this.indexName}`
);
this.updateWithResponse(data);
// Only keep polling if it exists and is in progress.
if (data.reindexOp && data.reindexOp.status === ReindexStatus.inProgress) {
this.pollTimeout = setTimeout(this.updateStatus, POLL_INTERVAL);
}
} catch (e) {
this.status$.next({
...this.status$.value,
status: ReindexStatus.failed,
});
}
};
public stopPolling = () => {
if (this.pollTimeout) {
clearTimeout(this.pollTimeout);
}
};
public startReindex = async () => {
try {
// Optimistically assume it will start, reset other state.
const currentValue = this.status$.value;
this.status$.next({
...currentValue,
// Only reset last completed step if we aren't currently paused
lastCompletedStep:
currentValue.status === ReindexStatus.paused ? currentValue.lastCompletedStep : undefined,
status: ReindexStatus.inProgress,
reindexTaskPercComplete: null,
errorMessage: null,
cancelLoadingState: undefined,
});
const data = await this.http.post<ReindexOperation>(
`${API_BASE_PATH}/reindex/${this.indexName}`
);
this.updateWithResponse({ reindexOp: data });
this.updateStatus();
} catch (e) {
this.status$.next({ ...this.status$.value, status: ReindexStatus.failed });
}
};
public cancelReindex = async () => {
try {
this.status$.next({
...this.status$.value,
cancelLoadingState: LoadingState.Loading,
});
await this.http.post(`${API_BASE_PATH}/reindex/${this.indexName}/cancel`);
} catch (e) {
this.status$.next({
...this.status$.value,
cancelLoadingState: LoadingState.Error,
});
}
};
private updateWithResponse = ({
reindexOp,
warnings,
hasRequiredPrivileges,
indexGroup,
}: StatusResponse) => {
const currentValue = this.status$.value;
// Next value should always include the entire state, not just what changes.
// We make a shallow copy as a starting new state.
const nextValue = {
...currentValue,
// If we're getting any updates, set to success.
loadingState: LoadingState.Success,
};
if (warnings) {
nextValue.reindexWarnings = warnings;
}
if (hasRequiredPrivileges !== undefined) {
nextValue.hasRequiredPrivileges = hasRequiredPrivileges;
}
if (indexGroup) {
nextValue.indexGroup = indexGroup;
}
if (reindexOp) {
// Prevent the UI flickering back to inProgres after cancelling.
nextValue.lastCompletedStep = reindexOp.lastCompletedStep;
nextValue.status = reindexOp.status;
nextValue.reindexTaskPercComplete = reindexOp.reindexTaskPercComplete;
nextValue.errorMessage = reindexOp.errorMessage;
if (reindexOp.status === ReindexStatus.cancelled) {
nextValue.cancelLoadingState = LoadingState.Success;
}
}
this.status$.next(nextValue);
};
}

View file

@ -10,7 +10,7 @@ import React from 'react';
import { EuiCallOut } from '@elastic/eui';
import { ResponseError } from '../../lib/api';
import { getEsDeprecationError } from '../../lib/es_deprecation_errors';
import { getEsDeprecationError } from '../../lib/get_es_deprecation_error';
interface Props {
error: ResponseError;
}

View file

@ -5,204 +5,88 @@
* 2.0.
*/
import React, { useMemo, useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import {
EuiButton,
EuiButtonEmpty,
EuiPageHeader,
EuiTabbedContent,
EuiTabbedContentTab,
EuiToolTip,
EuiNotificationBadge,
EuiSpacer,
} from '@elastic/eui';
import { EuiPageHeader, EuiSpacer, EuiPageContent } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { SectionLoading } from '../../../shared_imports';
import { useAppContext } from '../../app_context';
import { UpgradeAssistantTabProps, EsTabs, TelemetryState } from '../types';
import { DeprecationTabContent } from './deprecation_tab_content';
import { EsDeprecationsTable } from './es_deprecations_table';
import { EsDeprecationErrors } from './es_deprecation_errors';
import { NoDeprecationsPrompt } from '../shared';
const i18nTexts = {
pageTitle: i18n.translate('xpack.upgradeAssistant.esDeprecations.pageTitle', {
defaultMessage: 'Elasticsearch',
defaultMessage: 'Elasticsearch deprecation warnings',
}),
pageDescription: i18n.translate('xpack.upgradeAssistant.esDeprecations.pageDescription', {
defaultMessage:
'Review the deprecated cluster and index settings. You must resolve any critical issues before upgrading.',
'You must resolve all critical issues before upgrading. Back up recommended. Make sure you have a current snapshot before modifying your configuration or reindexing.',
}),
docLinkText: i18n.translate('xpack.upgradeAssistant.esDeprecations.docLinkText', {
defaultMessage: 'Documentation',
isLoading: i18n.translate('xpack.upgradeAssistant.esDeprecations.loadingText', {
defaultMessage: 'Loading deprecations…',
}),
backupDataButton: {
label: i18n.translate('xpack.upgradeAssistant.esDeprecations.backupDataButtonLabel', {
defaultMessage: 'Back up your data',
}),
tooltipText: i18n.translate('xpack.upgradeAssistant.esDeprecations.backupDataTooltipText', {
defaultMessage: 'Take a snapshot before you make any changes.',
}),
},
clusterTab: {
tabName: i18n.translate('xpack.upgradeAssistant.esDeprecations.clusterTabLabel', {
defaultMessage: 'Cluster',
}),
deprecationType: i18n.translate('xpack.upgradeAssistant.esDeprecations.clusterLabel', {
defaultMessage: 'cluster',
}),
},
indicesTab: {
tabName: i18n.translate('xpack.upgradeAssistant.esDeprecations.indicesTabLabel', {
defaultMessage: 'Indices',
}),
deprecationType: i18n.translate('xpack.upgradeAssistant.esDeprecations.indexLabel', {
defaultMessage: 'index',
}),
},
};
interface MatchParams {
tabName: EsTabs;
}
export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => {
const { api, breadcrumbs } = useAppContext();
export const EsDeprecationsContent = withRouter(
({
match: {
params: { tabName },
},
history,
}: RouteComponentProps<MatchParams>) => {
const [telemetryState, setTelemetryState] = useState<TelemetryState>(TelemetryState.Complete);
const {
data: esDeprecations,
isLoading,
error,
resendRequest,
isInitialRequest,
} = api.useLoadEsDeprecations();
const { api, breadcrumbs, getUrlForApp, docLinks } = useAppContext();
useEffect(() => {
breadcrumbs.setBreadcrumbs('esDeprecations');
}, [breadcrumbs]);
const { data: checkupData, isLoading, error, resendRequest } = api.useLoadUpgradeStatus();
const onTabClick = (selectedTab: EuiTabbedContentTab) => {
history.push(`/es_deprecations/${selectedTab.id}`);
};
const tabs = useMemo(() => {
const commonTabProps: UpgradeAssistantTabProps = {
error,
isLoading,
refreshCheckupData: resendRequest,
navigateToOverviewPage: () => history.push('/overview'),
};
return [
{
id: 'cluster',
'data-test-subj': 'upgradeAssistantClusterTab',
name: (
<span>
{i18nTexts.clusterTab.tabName}
{checkupData && checkupData.cluster.length > 0 && (
<>
{' '}
<EuiNotificationBadge>{checkupData.cluster.length}</EuiNotificationBadge>
</>
)}
</span>
),
content: (
<DeprecationTabContent
key="cluster"
deprecations={checkupData ? checkupData.cluster : undefined}
checkupLabel={i18nTexts.clusterTab.deprecationType}
{...commonTabProps}
/>
),
},
{
id: 'indices',
'data-test-subj': 'upgradeAssistantIndicesTab',
name: (
<span>
{i18nTexts.indicesTab.tabName}
{checkupData && checkupData.indices.length > 0 && (
<>
{' '}
<EuiNotificationBadge>{checkupData.indices.length}</EuiNotificationBadge>
</>
)}
</span>
),
content: (
<DeprecationTabContent
key="indices"
deprecations={checkupData ? checkupData.indices : undefined}
checkupLabel={i18nTexts.indicesTab.deprecationType}
{...commonTabProps}
/>
),
},
];
}, [checkupData, error, history, isLoading, resendRequest]);
useEffect(() => {
breadcrumbs.setBreadcrumbs('esDeprecations');
}, [breadcrumbs]);
useEffect(() => {
if (isLoading === false) {
setTelemetryState(TelemetryState.Running);
async function sendTelemetryData() {
await api.sendTelemetryData({
[tabName]: true,
});
setTelemetryState(TelemetryState.Complete);
}
sendTelemetryData();
useEffect(() => {
if (isLoading === false && isInitialRequest) {
async function sendTelemetryData() {
await api.sendPageTelemetryData({
elasticsearch: true,
});
}
}, [api, tabName, isLoading]);
sendTelemetryData();
}
}, [api, isLoading, isInitialRequest]);
if (error) {
return <EsDeprecationErrors error={error} />;
}
if (isLoading) {
return (
<>
<EuiPageHeader
pageTitle={i18nTexts.pageTitle}
description={i18nTexts.pageDescription}
rightSideItems={[
<EuiButtonEmpty
href={docLinks.links.upgradeAssistant}
target="_blank"
iconType="help"
data-test-subj="documentationLink"
>
{i18nTexts.docLinkText}
</EuiButtonEmpty>,
]}
>
<EuiToolTip position="bottom" content={i18nTexts.backupDataButton.tooltipText}>
<EuiButton
fill
href={getUrlForApp('management', {
path: 'data/snapshot_restore',
})}
iconType="popout"
iconSide="right"
target="_blank"
>
{i18nTexts.backupDataButton.label}
</EuiButton>
</EuiToolTip>
</EuiPageHeader>
<EuiSpacer size="l" />
<EuiTabbedContent
data-test-subj={
telemetryState === TelemetryState.Running
? 'upgradeAssistantTelemetryRunning'
: undefined
}
tabs={tabs}
onTabClick={onTabClick}
selectedTab={tabs.find((tab) => tab.id === tabName)}
/>
</>
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="subdued">
<SectionLoading>{i18nTexts.isLoading}</SectionLoading>
</EuiPageContent>
);
}
);
if (esDeprecations?.deprecations?.length === 0) {
return (
<EuiPageContent verticalPosition="center" horizontalPosition="center" color="subdued">
<NoDeprecationsPrompt
deprecationType="Elasticsearch"
navigateToOverviewPage={() => history.push('/overview')}
/>
</EuiPageContent>
);
}
return (
<div data-test-subj="esDeprecationsContent">
<EuiPageHeader pageTitle={i18nTexts.pageTitle} description={i18nTexts.pageDescription} />
<EuiSpacer size="l" />
<EsDeprecationsTable deprecations={esDeprecations?.deprecations} reload={resendRequest} />
</div>
);
});

View file

@ -0,0 +1,316 @@
/*
* 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, useEffect, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { sortBy } from 'lodash';
import {
EuiButton,
EuiFlexGroup,
EuiTable,
EuiTableRow,
EuiTableHeaderCell,
EuiTableHeader,
EuiSearchBar,
EuiSpacer,
EuiFlexItem,
EuiTableBody,
EuiTablePagination,
EuiCallOut,
EuiTableRowCell,
Pager,
Query,
} from '@elastic/eui';
import { EnrichedDeprecationInfo } from '../../../../common/types';
import {
MlSnapshotsTableRow,
DefaultTableRow,
IndexSettingsTableRow,
ReindexTableRow,
} from './deprecation_types';
import { DeprecationTableColumns } from '../types';
import { DEPRECATION_TYPE_MAP } from '../constants';
const i18nTexts = {
refreshButtonLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.table.refreshButtonLabel',
{
defaultMessage: 'Refresh',
}
),
noDeprecationsMessage: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.table.noDeprecationsMessage',
{
defaultMessage: 'No Elasticsearch deprecation issues found',
}
),
typeFilterLabel: i18n.translate('xpack.upgradeAssistant.esDeprecations.table.typeFilterLabel', {
defaultMessage: 'Type',
}),
criticalFilterLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.table.criticalFilterLabel',
{
defaultMessage: 'Critical',
}
),
searchPlaceholderLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.table.searchPlaceholderLabel',
{
defaultMessage: 'Filter',
}
),
};
const cellToLabelMap = {
isCritical: {
label: i18n.translate('xpack.upgradeAssistant.esDeprecations.table.statusColumnTitle', {
defaultMessage: 'Status',
}),
width: '8px',
},
message: {
label: i18n.translate('xpack.upgradeAssistant.esDeprecations.table.issueColumnTitle', {
defaultMessage: 'Issue',
}),
width: '36px',
},
type: {
label: i18n.translate('xpack.upgradeAssistant.esDeprecations.table.typeColumnTitle', {
defaultMessage: 'Type',
}),
width: '10px',
},
index: {
label: i18n.translate('xpack.upgradeAssistant.esDeprecations.table.nameColumnTitle', {
defaultMessage: 'Name',
}),
width: '24px',
},
correctiveAction: {
label: i18n.translate('xpack.upgradeAssistant.esDeprecations.table.resolutionColumnTitle', {
defaultMessage: 'Resolution',
}),
width: '24px',
},
};
const cellTypes = Object.keys(cellToLabelMap) as DeprecationTableColumns[];
const pageSizeOptions = [50, 100, 200];
const renderTableRowCells = (deprecation: EnrichedDeprecationInfo) => {
switch (deprecation.correctiveAction?.type) {
case 'mlSnapshot':
return <MlSnapshotsTableRow deprecation={deprecation} rowFieldNames={cellTypes} />;
case 'indexSetting':
return <IndexSettingsTableRow deprecation={deprecation} rowFieldNames={cellTypes} />;
case 'reindex':
return <ReindexTableRow deprecation={deprecation} rowFieldNames={cellTypes} />;
default:
return <DefaultTableRow deprecation={deprecation} rowFieldNames={cellTypes} />;
}
};
interface Props {
deprecations?: EnrichedDeprecationInfo[];
reload: () => void;
}
interface SortConfig {
isSortAscending: boolean;
sortField: DeprecationTableColumns;
}
const getSortedItems = (deprecations: EnrichedDeprecationInfo[], sortConfig: SortConfig) => {
const { isSortAscending, sortField } = sortConfig;
const sorted = sortBy(deprecations, [
(deprecation) => {
if (sortField === 'isCritical') {
// Critical deprecations should take precendence in ascending order
return deprecation.isCritical !== true;
}
return deprecation[sortField];
},
]);
return isSortAscending ? sorted : sorted.reverse();
};
export const EsDeprecationsTable: React.FunctionComponent<Props> = ({
deprecations = [],
reload,
}) => {
const [sortConfig, setSortConfig] = useState<SortConfig>({
isSortAscending: true,
sortField: 'isCritical',
});
const [itemsPerPage, setItemsPerPage] = useState(pageSizeOptions[0]);
const [currentPageIndex, setCurrentPageIndex] = useState(0);
const [searchQuery, setSearchQuery] = useState<Query>(EuiSearchBar.Query.MATCH_ALL);
const [searchError, setSearchError] = useState<{ message: string } | undefined>(undefined);
const [filteredDeprecations, setFilteredDeprecations] = useState<EnrichedDeprecationInfo[]>(
getSortedItems(deprecations, sortConfig)
);
const pager = useMemo(() => new Pager(deprecations.length, itemsPerPage, currentPageIndex), [
currentPageIndex,
deprecations,
itemsPerPage,
]);
const visibleDeprecations = useMemo(
() => filteredDeprecations.slice(pager.firstItemIndex, pager.lastItemIndex + 1),
[filteredDeprecations, pager]
);
const handleSort = useCallback(
(fieldName: DeprecationTableColumns) => {
const newSortConfig = {
isSortAscending: sortConfig.sortField === fieldName ? !sortConfig.isSortAscending : true,
sortField: fieldName,
};
setSortConfig(newSortConfig);
},
[sortConfig]
);
const handleSearch = useCallback(({ query, error }) => {
if (error) {
setSearchError(error);
} else {
setSearchError(undefined);
setSearchQuery(query);
}
}, []);
useEffect(() => {
const { setTotalItems, goToPageIndex } = pager;
const deprecationsFilteredByQuery = EuiSearchBar.Query.execute(searchQuery, deprecations);
const deprecationsSortedByFieldType = getSortedItems(deprecationsFilteredByQuery, sortConfig);
setTotalItems(deprecationsSortedByFieldType.length);
setFilteredDeprecations(deprecationsSortedByFieldType);
// Reset pagination if the filtered results return a different length
if (deprecationsSortedByFieldType.length !== filteredDeprecations.length) {
goToPageIndex(0);
}
}, [deprecations, sortConfig, pager, searchQuery, filteredDeprecations.length]);
return (
<>
<EuiFlexGroup gutterSize="m">
<EuiFlexItem data-test-subj="searchBarContainer">
<EuiSearchBar
box={{
placeholder: i18nTexts.searchPlaceholderLabel,
incremental: true,
}}
filters={[
{
type: 'is',
field: 'isCritical',
name: i18nTexts.criticalFilterLabel,
},
{
type: 'field_value_selection',
field: 'type',
name: i18nTexts.typeFilterLabel,
multiSelect: false,
options: (Object.keys(DEPRECATION_TYPE_MAP) as Array<
keyof typeof DEPRECATION_TYPE_MAP
>).map((type) => ({
value: type,
name: DEPRECATION_TYPE_MAP[type],
})),
},
]}
onChange={handleSearch}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
iconType="refresh"
onClick={reload}
data-test-subj="refreshButton"
key="refreshButton"
>
{i18nTexts.refreshButtonLabel}
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
{searchError && (
<div data-test-subj="invalidSearchQueryMessage">
<EuiSpacer size="l" />
<EuiCallOut
iconType="alert"
color="danger"
title={`Invalid search: ${searchError.message}`}
/>
</div>
)}
<EuiSpacer size="m" />
<EuiTable>
<EuiTableHeader>
{Object.entries(cellToLabelMap).map(([fieldName, cell]) => {
return (
<EuiTableHeaderCell
width={cell.width}
key={cell.label}
onSort={() => handleSort(fieldName as DeprecationTableColumns)}
isSorted={sortConfig.sortField === fieldName}
isSortAscending={sortConfig.isSortAscending}
>
{cell.label}
</EuiTableHeaderCell>
);
})}
</EuiTableHeader>
{filteredDeprecations.length === 0 ? (
<EuiTableBody>
<EuiTableRow data-test-subj="noDeprecationsRow">
<EuiTableRowCell align="center" colSpan={cellTypes.length} isMobileFullWidth={true}>
{i18nTexts.noDeprecationsMessage}
</EuiTableRowCell>
</EuiTableRow>
</EuiTableBody>
) : (
<EuiTableBody>
{visibleDeprecations.map((deprecation, index) => {
return (
<EuiTableRow data-test-subj="deprecationTableRow" key={`deprecation-row-${index}`}>
{renderTableRowCells(deprecation)}
</EuiTableRow>
);
})}
</EuiTableBody>
)}
</EuiTable>
<EuiSpacer size="m" />
<EuiTablePagination
data-test-subj="esDeprecationsPagination"
activePage={pager.getCurrentPageIndex()}
itemsPerPage={pager.itemsPerPage}
itemsPerPageOptions={pageSizeOptions}
pageCount={pager.getTotalPages()}
onChangeItemsPerPage={setItemsPerPage}
onChangePage={setCurrentPageIndex}
/>
</>
);
};

View file

@ -0,0 +1,74 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiBadge, EuiLink } from '@elastic/eui';
import { EnrichedDeprecationInfo } from '../../../../common/types';
import { DEPRECATION_TYPE_MAP } from '../constants';
import { DeprecationTableColumns } from '../types';
interface Props {
resolutionTableCell?: React.ReactNode;
fieldName: DeprecationTableColumns;
deprecation: EnrichedDeprecationInfo;
openFlyout: () => void;
}
const i18nTexts = {
criticalBadgeLabel: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.defaultDeprecation.criticalBadgeLabel',
{
defaultMessage: 'Critical',
}
),
};
export const EsDeprecationsTableCells: React.FunctionComponent<Props> = ({
resolutionTableCell,
fieldName,
deprecation,
openFlyout,
}) => {
// "Status column"
if (fieldName === 'isCritical') {
if (deprecation.isCritical === true) {
return <EuiBadge color="danger">{i18nTexts.criticalBadgeLabel}</EuiBadge>;
}
return <>{''}</>;
}
// "Issue" column
if (fieldName === 'message') {
return (
<EuiLink
data-test-subj={`deprecation-${deprecation.correctiveAction?.type ?? 'default'}`}
onClick={openFlyout}
>
{deprecation.message}
</EuiLink>
);
}
// "Type" column
if (fieldName === 'type') {
return <>{DEPRECATION_TYPE_MAP[deprecation.type as EnrichedDeprecationInfo['type']]}</>;
}
// "Resolution column"
if (fieldName === 'correctiveAction') {
if (resolutionTableCell) {
return <>{resolutionTableCell}</>;
}
return <>{''}</>;
}
// Default behavior: render value or empty string if undefined
return <>{deprecation[fieldName] ?? ''}</>;
};

View file

@ -5,4 +5,4 @@
* 2.0.
*/
export { EsDeprecationsContent } from './es_deprecations';
export { EsDeprecations } from './es_deprecations';

View file

@ -112,7 +112,7 @@ export const KibanaDeprecationsContent = withRouter(({ history }: RouteComponent
useEffect(() => {
async function sendTelemetryData() {
await api.sendTelemetryData({
await api.sendPageTelemetryData({
kibana: true,
});
}

View file

@ -31,7 +31,7 @@ export const Overview: FunctionComponent = () => {
useEffect(() => {
async function sendTelemetryData() {
await api.sendTelemetryData({
await api.sendPageTelemetryData({
overview: true,
});
}

View file

@ -50,13 +50,12 @@ const i18nTexts = {
criticalDeprecations,
},
}),
getWarningDeprecationMessage: (clusterCount: number, indexCount: number) =>
i18n.translate('xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTooltip', {
getWarningDeprecationMessage: (warningDeprecations: number) =>
i18n.translate('xpack.upgradeAssistant.esDeprecationStats.warningDeprecationsTooltip', {
defaultMessage:
'This cluster is using {clusterCount} deprecated cluster settings and {indexCount} deprecated index settings',
'This cluster has {warningDeprecations} non-critical {warningDeprecations, plural, one {deprecation} other {deprecations}}',
values: {
clusterCount,
indexCount,
warningDeprecations,
},
}),
};
@ -65,15 +64,12 @@ export const ESDeprecationStats: FunctionComponent = () => {
const history = useHistory();
const { api } = useAppContext();
const { data: esDeprecations, isLoading, error } = api.useLoadUpgradeStatus();
const { data: esDeprecations, isLoading, error } = api.useLoadEsDeprecations();
const allDeprecations = esDeprecations?.cluster?.concat(esDeprecations?.indices) ?? [];
const warningDeprecations = allDeprecations.filter(
(deprecation) => deprecation.level === 'warning'
);
const criticalDeprecations = allDeprecations.filter(
(deprecation) => deprecation.level === 'critical'
);
const warningDeprecations =
esDeprecations?.deprecations?.filter((deprecation) => deprecation.isCritical === false) || [];
const criticalDeprecations =
esDeprecations?.deprecations?.filter((deprecation) => deprecation.isCritical) || [];
const hasWarnings = warningDeprecations.length > 0;
const hasCritical = criticalDeprecations.length > 0;
@ -90,7 +86,7 @@ export const ESDeprecationStats: FunctionComponent = () => {
{error && <EsStatsErrors error={error} />}
</>
}
{...(!hasNoDeprecations && reactRouterNavigate(history, '/es_deprecations/cluster'))}
{...(!hasNoDeprecations && reactRouterNavigate(history, '/es_deprecations'))}
>
<EuiSpacer />
<EuiFlexGroup>
@ -137,10 +133,7 @@ export const ESDeprecationStats: FunctionComponent = () => {
<p>
{isLoading
? i18nTexts.loadingText
: i18nTexts.getWarningDeprecationMessage(
esDeprecations?.cluster.length ?? 0,
esDeprecations?.indices.length ?? 0
)}
: i18nTexts.getWarningDeprecationMessage(warningDeprecations.length)}
</p>
</EuiScreenReaderOnly>
)}

View file

@ -9,7 +9,7 @@ import React from 'react';
import { EuiIconTip } from '@elastic/eui';
import { ResponseError } from '../../../../lib/api';
import { getEsDeprecationError } from '../../../../lib/es_deprecation_errors';
import { getEsDeprecationError } from '../../../../lib/get_es_deprecation_error';
interface Props {
error: ResponseError;

View file

@ -12,14 +12,14 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
const i18nTexts = {
emptyPromptTitle: i18n.translate('xpack.upgradeAssistant.noDeprecationsPrompt.title', {
defaultMessage: 'Ready to upgrade!',
}),
getEmptyPromptDescription: (deprecationType: string) =>
getEmptyPromptTitle: (deprecationType: string) =>
i18n.translate('xpack.upgradeAssistant.noDeprecationsPrompt.description', {
defaultMessage: 'Your configuration is up to date.',
defaultMessage: 'Your {deprecationType} configuration is up to date',
values: {
deprecationType,
},
}),
getEmptyPromptNextStepsDescription: (navigateToOverviewPage: () => void) => (
getEmptyPromptDescription: (navigateToOverviewPage: () => void) => (
<FormattedMessage
id="xpack.upgradeAssistant.noDeprecationsPrompt.nextStepsDescription"
defaultMessage="Check the {overviewButton} for other Stack deprecations."
@ -47,15 +47,14 @@ export const NoDeprecationsPrompt: FunctionComponent<Props> = ({
}) => {
return (
<EuiEmptyPrompt
iconType="faceHappy"
iconType="check"
data-test-subj="noDeprecationsPrompt"
title={<h2>{i18nTexts.emptyPromptTitle}</h2>}
title={<h2>{i18nTexts.getEmptyPromptTitle(deprecationType)}</h2>}
body={
<>
<p data-test-subj="upgradeAssistantIssueSummary">
{i18nTexts.getEmptyPromptDescription(deprecationType)}
{i18nTexts.getEmptyPromptDescription(navigateToOverviewPage)}
</p>
<p>{i18nTexts.getEmptyPromptNextStepsDescription(navigateToOverviewPage)}</p>
</>
}
/>

View file

@ -5,27 +5,8 @@
* 2.0.
*/
import React from 'react';
import { EnrichedDeprecationInfo, ESUpgradeStatus } from '../../../common/types';
import { ResponseError } from '../lib/api';
export interface UpgradeAssistantTabProps {
alertBanner?: React.ReactNode;
checkupData?: ESUpgradeStatus | null;
deprecations?: EnrichedDeprecationInfo[];
refreshCheckupData: () => void;
error: ResponseError | null;
isLoading: boolean;
navigateToOverviewPage: () => void;
}
// eslint-disable-next-line react/prefer-stateless-function
export class UpgradeAssistantTabComponent<
T extends UpgradeAssistantTabProps = UpgradeAssistantTabProps,
S = {}
> extends React.Component<T, S> {}
export enum LoadingState {
Loading,
Success,
@ -40,13 +21,14 @@ export enum GroupByOption {
node = 'node',
}
export enum TelemetryState {
Running,
Complete,
}
export type EsTabs = 'cluster' | 'indices';
export type DeprecationTableColumns =
| 'type'
| 'index'
| 'message'
| 'correctiveAction'
| 'isCritical';
export type Status = 'in_progress' | 'complete' | 'idle' | 'error';
export interface DeprecationLoggingPreviewProps {
isDeprecationLogIndexingEnabled: boolean;
onlyDeprecationLogWritingEnabled: boolean;

View file

@ -45,14 +45,14 @@ export class ApiService {
this.client = httpClient;
}
public useLoadUpgradeStatus() {
public useLoadEsDeprecations() {
return this.useRequest<ESUpgradeStatus>({
path: `${API_BASE_PATH}/es_deprecations`,
method: 'get',
});
}
public async sendTelemetryData(telemetryData: { [tabName: string]: boolean }) {
public async sendPageTelemetryData(telemetryData: { [tabName: string]: boolean }) {
const result = await this.sendRequest({
path: `${API_BASE_PATH}/stats/ui_open`,
method: 'put',
@ -125,6 +125,37 @@ export class ApiService {
method: 'get',
});
}
public async sendReindexTelemetryData(telemetryData: { [key: string]: boolean }) {
const result = await this.sendRequest({
path: `${API_BASE_PATH}/stats/ui_reindex`,
method: 'put',
body: JSON.stringify(telemetryData),
});
return result;
}
public async getReindexStatus(indexName: string) {
return await this.sendRequest({
path: `${API_BASE_PATH}/reindex/${indexName}`,
method: 'get',
});
}
public async startReindexTask(indexName: string) {
return await this.sendRequest({
path: `${API_BASE_PATH}/reindex/${indexName}`,
method: 'post',
});
}
public async cancelReindexTask(indexName: string) {
return await this.sendRequest({
path: `${API_BASE_PATH}/reindex/${indexName}/cancel`,
method: 'post',
});
}
}
export const apiService = new ApiService();

View file

@ -16,7 +16,7 @@ const i18nTexts = {
defaultMessage: 'Upgrade Assistant',
}),
esDeprecations: i18n.translate('xpack.upgradeAssistant.breadcrumb.esDeprecationsLabel', {
defaultMessage: 'Elasticsearch deprecations',
defaultMessage: 'Elasticsearch deprecation warnings',
}),
kibanaDeprecations: i18n.translate(
'xpack.upgradeAssistant.breadcrumb.kibanaDeprecationsLabel',

View file

@ -25,8 +25,7 @@ const i18nTexts = {
upgradedMessage: i18n.translate(
'xpack.upgradeAssistant.esDeprecationErrors.upgradedWarningMessage',
{
defaultMessage:
'Your configuration is up to date. Kibana and all Elasticsearch nodes are running the same version.',
defaultMessage: 'All Elasticsearch nodes have been upgraded.',
}
),
loadingError: i18n.translate('xpack.upgradeAssistant.esDeprecationErrors.loadingErrorMessage', {

View file

@ -15,6 +15,7 @@ export {
useRequest,
UseRequestConfig,
SectionLoading,
GlobalFlyout,
} from '../../../../src/plugins/es_ui_shared/public/';
export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public';

View file

@ -4,13 +4,15 @@
"level": "warning",
"message": "Template patterns are no longer using `template` field, but `index_patterns` instead",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template"
"details": "templates using `template` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template",
"resolve_during_rolling_upgrade": false
},
{
"level": "warning",
"message": "one or more templates use deprecated mapping settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}"
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}",
"resolve_during_rolling_upgrade": false
}
],
"ml_settings": [
@ -18,7 +20,8 @@
"level": "warning",
"message": "Datafeed [deprecation-datafeed] uses deprecated query options",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-7.0.html#breaking_70_search_changes",
"details": "[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]"
"details": "[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]",
"resolve_during_rolling_upgrade": false
},
{
"level": "critical",
@ -28,7 +31,8 @@
"_meta": {
"snapshot_id": "1",
"job_id": "deprecation_check_job"
}
},
"resolve_during_rolling_upgrade": false
}
],
"node_settings": [
@ -36,7 +40,8 @@
"level": "critical",
"message": "A node-level issue",
"url": "http://nodeissue.com",
"details": "This node thing is wrong"
"details": "This node thing is wrong",
"resolve_during_rolling_upgrade": true
}
],
"index_settings": {
@ -45,7 +50,8 @@
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: doc, field: spins], [type: doc, field: mlockall], [type: doc, field: node_master], [type: doc, field: primary]]"
"details": "[[type: doc, field: spins], [type: doc, field: mlockall], [type: doc, field: node_master], [type: doc, field: primary]]",
"resolve_during_rolling_upgrade": false
}
],
"twitter": [
@ -53,7 +59,8 @@
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: tweet, field: liked]]"
"details": "[[type: tweet, field: liked]]",
"resolve_during_rolling_upgrade": false
}
],
"old_index": [
@ -62,7 +69,8 @@
"message": "Index created before 7.0",
"url":
"https: //www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html",
"details": "This index was created using version: 6.8.13"
"details": "This index was created using version: 6.8.13",
"resolve_during_rolling_upgrade": false
}
],
"closed_index": [
@ -70,7 +78,8 @@
"level": "critical",
"message": "Index created before 7.0",
"url": "https: //www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html",
"details": "This index was created using version: 6.8.13"
"details": "This index was created using version: 6.8.13",
"resolve_during_rolling_upgrade": false
}
],
"deprecated_settings": [
@ -80,7 +89,8 @@
"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)"
"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)",
"resolve_during_rolling_upgrade": false
}
],
".kibana": [
@ -88,7 +98,8 @@
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: index-pattern, field: notExpandable], [type: config, field: xPackMonitoring:allowReport], [type: config, field: xPackMonitoring:showBanner], [type: dashboard, field: pause], [type: dashboard, field: timeRestore]]"
"details": "[[type: index-pattern, field: notExpandable], [type: config, field: xPackMonitoring:allowReport], [type: config, field: xPackMonitoring:showBanner], [type: dashboard, field: pause], [type: dashboard, field: timeRestore]]",
"resolve_during_rolling_upgrade": false
}
],
".watcher-history-6-2018.11.07": [
@ -96,7 +107,8 @@
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: doc, field: notify], [type: doc, field: created], [type: doc, field: attach_payload], [type: doc, field: met]]"
"details": "[[type: doc, field: notify], [type: doc, field: created], [type: doc, field: attach_payload], [type: doc, field: met]]",
"resolve_during_rolling_upgrade": false
}
],
".monitoring-kibana-6-2018.11.07": [
@ -104,7 +116,8 @@
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: doc, field: snapshot]]"
"details": "[[type: doc, field: snapshot]]",
"resolve_during_rolling_upgrade": false
}
],
"twitter2": [
@ -112,7 +125,8 @@
"level": "warning",
"message": "Coercion of boolean fields",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
"details": "[[type: tweet, field: liked]]"
"details": "[[type: tweet, field: liked]]",
"resolve_during_rolling_upgrade": false
}
]
}

View file

@ -2,26 +2,32 @@
exports[`getESUpgradeStatus returns the correct shape of data 1`] = `
Object {
"cluster": Array [
"deprecations": Array [
Object {
"correctiveAction": undefined,
"details": "templates using \`template\` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template",
"level": "warning",
"isCritical": false,
"message": "Template patterns are no longer using \`template\` field, but \`index_patterns\` instead",
"resolveDuringUpgrade": false,
"type": "cluster_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html#_index_templates_use_literal_index_patterns_literal_instead_of_literal_template_literal",
},
Object {
"correctiveAction": undefined,
"details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}",
"level": "warning",
"isCritical": false,
"message": "one or more templates use deprecated mapping settings",
"resolveDuringUpgrade": false,
"type": "cluster_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html",
},
Object {
"correctiveAction": undefined,
"details": "[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]",
"level": "warning",
"isCritical": false,
"message": "Datafeed [deprecation-datafeed] uses deprecated query options",
"resolveDuringUpgrade": false,
"type": "ml_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-7.0.html#breaking_70_search_changes",
},
Object {
@ -31,33 +37,39 @@ Object {
"type": "mlSnapshot",
},
"details": "details",
"level": "critical",
"isCritical": true,
"message": "model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded",
"resolveDuringUpgrade": false,
"type": "ml_settings",
"url": "",
},
Object {
"correctiveAction": undefined,
"details": "This node thing is wrong",
"level": "critical",
"isCritical": true,
"message": "A node-level issue",
"resolveDuringUpgrade": true,
"type": "node_settings",
"url": "http://nodeissue.com",
},
],
"indices": Array [
Object {
"correctiveAction": undefined,
"details": "[[type: doc, field: spins], [type: doc, field: mlockall], [type: doc, field: node_master], [type: doc, field: primary]]",
"index": ".monitoring-es-6-2018.11.07",
"level": "warning",
"isCritical": false,
"message": "Coercion of boolean fields",
"resolveDuringUpgrade": false,
"type": "index_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
},
Object {
"correctiveAction": undefined,
"details": "[[type: tweet, field: liked]]",
"index": "twitter",
"level": "warning",
"isCritical": false,
"message": "Coercion of boolean fields",
"resolveDuringUpgrade": false,
"type": "index_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
},
Object {
@ -67,8 +79,10 @@ Object {
},
"details": "This index was created using version: 6.8.13",
"index": "old_index",
"level": "critical",
"isCritical": true,
"message": "Index created before 7.0",
"resolveDuringUpgrade": false,
"type": "index_settings",
"url": "https: //www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html",
},
Object {
@ -78,8 +92,10 @@ Object {
},
"details": "This index was created using version: 6.8.13",
"index": "closed_index",
"level": "critical",
"isCritical": true,
"message": "Index created before 7.0",
"resolveDuringUpgrade": false,
"type": "index_settings",
"url": "https: //www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html",
},
Object {
@ -92,40 +108,50 @@ Object {
},
"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": "deprecated_settings",
"level": "warning",
"isCritical": false,
"message": "translog retention settings are ignored",
"resolveDuringUpgrade": false,
"type": "index_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html",
},
Object {
"correctiveAction": undefined,
"details": "[[type: index-pattern, field: notExpandable], [type: config, field: xPackMonitoring:allowReport], [type: config, field: xPackMonitoring:showBanner], [type: dashboard, field: pause], [type: dashboard, field: timeRestore]]",
"index": ".kibana",
"level": "warning",
"isCritical": false,
"message": "Coercion of boolean fields",
"resolveDuringUpgrade": false,
"type": "index_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
},
Object {
"correctiveAction": undefined,
"details": "[[type: doc, field: notify], [type: doc, field: created], [type: doc, field: attach_payload], [type: doc, field: met]]",
"index": ".watcher-history-6-2018.11.07",
"level": "warning",
"isCritical": false,
"message": "Coercion of boolean fields",
"resolveDuringUpgrade": false,
"type": "index_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
},
Object {
"correctiveAction": undefined,
"details": "[[type: doc, field: snapshot]]",
"index": ".monitoring-kibana-6-2018.11.07",
"level": "warning",
"isCritical": false,
"message": "Coercion of boolean fields",
"resolveDuringUpgrade": false,
"type": "index_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
},
Object {
"correctiveAction": undefined,
"details": "[[type: tweet, field: liked]]",
"index": "twitter2",
"level": "warning",
"isCritical": false,
"message": "Coercion of boolean fields",
"resolveDuringUpgrade": false,
"type": "index_settings",
"url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields",
},
],

View file

@ -8,7 +8,7 @@
import _ from 'lodash';
import { RequestEvent } from '@elastic/elasticsearch/lib/Transport';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import { DeprecationAPIResponse } from '../../common/types';
import { MigrationDeprecationInfoResponse } from '@elastic/elasticsearch/api/types';
import { getESUpgradeStatus } from './es_deprecations_status';
import fakeDeprecations from './__fixtures__/fake_deprecations.json';
@ -32,12 +32,11 @@ describe('getESUpgradeStatus', () => {
};
// @ts-expect-error mock data is too loosely typed
const deprecationsResponse: DeprecationAPIResponse = _.cloneDeep(fakeDeprecations);
const deprecationsResponse: MigrationDeprecationInfoResponse = _.cloneDeep(fakeDeprecations);
const esClient = elasticsearchServiceMock.createScopedClusterClient();
esClient.asCurrentUser.migration.deprecations.mockResolvedValue(
// @ts-expect-error not full interface
asApiResponse(deprecationsResponse)
);

View file

@ -5,13 +5,13 @@
* 2.0.
*/
import {
MigrationDeprecationInfoDeprecation,
MigrationDeprecationInfoResponse,
} from '@elastic/elasticsearch/api/types';
import { IScopedClusterClient } from 'src/core/server';
import { indexSettingDeprecations } from '../../common/constants';
import {
DeprecationAPIResponse,
EnrichedDeprecationInfo,
ESUpgradeStatus,
} from '../../common/types';
import { EnrichedDeprecationInfo, ESUpgradeStatus } from '../../common/types';
import { esIndicesStateCheck } from './es_indices_state_check';
@ -20,33 +20,82 @@ export async function getESUpgradeStatus(
): Promise<ESUpgradeStatus> {
const { body: deprecations } = await dataClient.asCurrentUser.migration.deprecations();
const cluster = getClusterDeprecations(deprecations);
const indices = await getCombinedIndexInfos(deprecations, dataClient);
const getCombinedDeprecations = async () => {
const indices = await getCombinedIndexInfos(deprecations, dataClient);
const totalCriticalDeprecations = cluster.concat(indices).filter((d) => d.level === 'critical')
.length;
return Object.keys(deprecations).reduce((combinedDeprecations, deprecationType) => {
if (deprecationType === 'index_settings') {
combinedDeprecations = combinedDeprecations.concat(indices);
} else {
const deprecationsByType = deprecations[
deprecationType as keyof MigrationDeprecationInfoResponse
] as MigrationDeprecationInfoDeprecation[];
const enrichedDeprecationInfo = deprecationsByType.map(
({
details,
level,
message,
url,
// @ts-expect-error @elastic/elasticsearch _meta not available yet in MigrationDeprecationInfoResponse
_meta: metadata,
// @ts-expect-error @elastic/elasticsearch resolve_during_rolling_upgrade not available yet in MigrationDeprecationInfoResponse
resolve_during_rolling_upgrade: resolveDuringUpgrade,
}) => {
return {
details,
message,
url,
type: deprecationType as keyof MigrationDeprecationInfoResponse,
isCritical: level === 'critical',
resolveDuringUpgrade,
correctiveAction: getCorrectiveAction(message, metadata),
};
}
);
combinedDeprecations = combinedDeprecations.concat(enrichedDeprecationInfo);
}
return combinedDeprecations;
}, [] as EnrichedDeprecationInfo[]);
};
const combinedDeprecations = await getCombinedDeprecations();
const criticalWarnings = combinedDeprecations.filter(({ isCritical }) => isCritical === true);
return {
totalCriticalDeprecations,
cluster,
indices,
totalCriticalDeprecations: criticalWarnings.length,
deprecations: combinedDeprecations,
};
}
// Reformats the index deprecations to an array of deprecation warnings extended with an index field.
const getCombinedIndexInfos = async (
deprecations: DeprecationAPIResponse,
deprecations: MigrationDeprecationInfoResponse,
dataClient: IScopedClusterClient
) => {
const indices = Object.keys(deprecations.index_settings).reduce(
(indexDeprecations, indexName) => {
return indexDeprecations.concat(
deprecations.index_settings[indexName].map(
(d) =>
({
details,
message,
url,
level,
// @ts-expect-error @elastic/elasticsearch resolve_during_rolling_upgrade not available yet in MigrationDeprecationInfoResponse
resolve_during_rolling_upgrade: resolveDuringUpgrade,
}) =>
({
...d,
details,
message,
url,
index: indexName,
correctiveAction: getCorrectiveAction(d.message),
type: 'index_settings',
isCritical: level === 'critical',
correctiveAction: getCorrectiveAction(message),
resolveDuringUpgrade,
} as EnrichedDeprecationInfo)
)
);
@ -71,21 +120,10 @@ const getCombinedIndexInfos = async (
return indices as EnrichedDeprecationInfo[];
};
const getClusterDeprecations = (deprecations: DeprecationAPIResponse) => {
const combinedDeprecations = deprecations.cluster_settings
.concat(deprecations.ml_settings)
.concat(deprecations.node_settings);
return combinedDeprecations.map((deprecation) => {
const { _meta: metadata, ...deprecationInfo } = deprecation;
return {
...deprecationInfo,
correctiveAction: getCorrectiveAction(deprecation.message, metadata),
};
}) as EnrichedDeprecationInfo[];
};
const getCorrectiveAction = (message: string, metadata?: { [key: string]: string }) => {
const getCorrectiveAction = (
message: string,
metadata?: { [key: string]: string }
): EnrichedDeprecationInfo['correctiveAction'] => {
const indexSettingDeprecation = Object.values(indexSettingDeprecations).find(
({ deprecationMessage }) => deprecationMessage === message
);

View file

@ -22,13 +22,12 @@ describe('Upgrade Assistant Telemetry SavedObject UIOpen', () => {
await upsertUIOpenOption({
overview: true,
cluster: true,
indices: true,
elasticsearch: true,
kibana: true,
savedObjects: { createInternalRepository: () => internalRepo } as any,
});
expect(internalRepo.incrementCounter).toHaveBeenCalledTimes(4);
expect(internalRepo.incrementCounter).toHaveBeenCalledTimes(3);
expect(internalRepo.incrementCounter).toHaveBeenCalledWith(
UPGRADE_ASSISTANT_TYPE,
UPGRADE_ASSISTANT_DOC_ID,
@ -37,12 +36,7 @@ describe('Upgrade Assistant Telemetry SavedObject UIOpen', () => {
expect(internalRepo.incrementCounter).toHaveBeenCalledWith(
UPGRADE_ASSISTANT_TYPE,
UPGRADE_ASSISTANT_DOC_ID,
['ui_open.cluster']
);
expect(internalRepo.incrementCounter).toHaveBeenCalledWith(
UPGRADE_ASSISTANT_TYPE,
UPGRADE_ASSISTANT_DOC_ID,
['ui_open.indices']
['ui_open.elasticsearch']
);
expect(internalRepo.incrementCounter).toHaveBeenCalledWith(
UPGRADE_ASSISTANT_TYPE,

View file

@ -33,8 +33,7 @@ type UpsertUIOpenOptionDependencies = UIOpen & { savedObjects: SavedObjectsServi
export async function upsertUIOpenOption({
overview,
cluster,
indices,
elasticsearch,
savedObjects,
kibana,
}: UpsertUIOpenOptionDependencies): Promise<UIOpen> {
@ -42,12 +41,8 @@ export async function upsertUIOpenOption({
await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'overview' });
}
if (cluster) {
await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'cluster' });
}
if (indices) {
await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'indices' });
if (elasticsearch) {
await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'elasticsearch' });
}
if (kibana) {
@ -56,8 +51,7 @@ export async function upsertUIOpenOption({
return {
overview,
cluster,
indices,
elasticsearch,
kibana,
};
}

View file

@ -54,8 +54,7 @@ describe('Upgrade Assistant Usage Collector', () => {
return {
attributes: {
'ui_open.overview': 10,
'ui_open.cluster': 20,
'ui_open.indices': 30,
'ui_open.elasticsearch': 20,
'ui_open.kibana': 15,
'ui_reindex.close': 1,
'ui_reindex.open': 4,
@ -94,8 +93,7 @@ describe('Upgrade Assistant Usage Collector', () => {
expect(upgradeAssistantStats).toEqual({
ui_open: {
overview: 10,
cluster: 20,
indices: 30,
elasticsearch: 20,
kibana: 15,
},
ui_reindex: {

View file

@ -77,8 +77,7 @@ export async function fetchUpgradeAssistantMetrics(
const defaultTelemetrySavedObject = {
ui_open: {
overview: 0,
cluster: 0,
indices: 0,
elasticsearch: 0,
kibana: 0,
},
ui_reindex: {
@ -96,8 +95,7 @@ export async function fetchUpgradeAssistantMetrics(
return {
ui_open: {
overview: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.overview', 0),
cluster: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.cluster', 0),
indices: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.indices', 0),
elasticsearch: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.elasticsearch', 0),
kibana: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.kibana', 0),
},
ui_reindex: {
@ -146,18 +144,10 @@ export function registerUpgradeAssistantUsageCollector({
},
},
ui_open: {
cluster: {
elasticsearch: {
type: 'long',
_meta: {
description:
'Number of times a user viewed the list of Elasticsearch cluster deprecations.',
},
},
indices: {
type: 'long',
_meta: {
description:
'Number of times a user viewed the list of Elasticsearch index deprecations.',
description: 'Number of times a user viewed the list of Elasticsearch deprecations.',
},
},
overview: {

Some files were not shown because too many files have changed in this diff Show more