diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 50933335710d..d2fbbf147efd 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -5314,7 +5314,10 @@ "deprecation_logging": { "properties": { "enabled": { - "type": "boolean" + "type": "boolean", + "_meta": { + "description": "Whether user has enabled Elasticsearch deprecation logging" + } } } } @@ -5323,13 +5326,28 @@ "ui_open": { "properties": { "cluster": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times a user viewed the list of Elasticsearch cluster deprecations." + } }, "indices": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times a user viewed the list of Elasticsearch index deprecations." + } }, "overview": { - "type": "long" + "type": "long", + "_meta": { + "description": "Number of times a user viewed the Overview page." + } + }, + "kibana": { + "type": "long", + "_meta": { + "description": "Number of times a user viewed the list of Kibana deprecations" + } } } }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9375eb93ee55..a6187937cbf6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -22111,15 +22111,9 @@ "xpack.uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlFormatGeneralErrorMessage": "無効なフォーマット。例:{exampleUrl}", "xpack.upgradeAssistant.appTitle": "{version} アップグレードアシスタント", "xpack.upgradeAssistant.checkupTab.changeFiltersShowMoreLabel": "より多く表示させるにはフィルターを変更します。", - "xpack.upgradeAssistant.checkupTab.controls.collapseAllButtonLabel": "すべて縮小", - "xpack.upgradeAssistant.checkupTab.controls.expandAllButtonLabel": "すべて拡張", "xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel": "致命的", - "xpack.upgradeAssistant.checkupTab.controls.filterErrorMessageLabel": "フィルター無効:{searchTermError}", "xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIndexLabel": "インデックス別", "xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIssueLabel": "問題別", - "xpack.upgradeAssistant.checkupTab.controls.refreshButtonLabel": "更新", - "xpack.upgradeAssistant.checkupTab.controls.searchBarPlaceholder": "フィルター", - "xpack.upgradeAssistant.checkupTab.controls.searchBarPlaceholderAriaLabel": "フィルター", "xpack.upgradeAssistant.checkupTab.deprecations.criticalActionTooltip": "アップグレード前にこの問題を解決してください。", "xpack.upgradeAssistant.checkupTab.deprecations.criticalLabel": "致命的", "xpack.upgradeAssistant.checkupTab.deprecations.documentationButtonLabel": "ドキュメント", @@ -22128,9 +22122,6 @@ "xpack.upgradeAssistant.checkupTab.deprecations.warningActionTooltip": "アップグレード前にこの問題を解決することをお勧めしますが、必須ではありません。", "xpack.upgradeAssistant.checkupTab.deprecations.warningLabel": "警告", "xpack.upgradeAssistant.checkupTab.noDeprecationsLabel": "説明がありません", - "xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail": "{overviewTabButton} で次のステップを確認してください。", - "xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail.overviewTabButtonLabel": "概要タブ", - "xpack.upgradeAssistant.checkupTab.noIssues.noIssuesTitle": "完璧です!", "xpack.upgradeAssistant.checkupTab.numDeprecationsShownLabel": "{total} 件中 {numShown} 件を表示中", "xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.cancelButtonLabel": "キャンセル", "xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.closeButtonLabel": "閉じる", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 9fdc8d44d3da..6a452f25f028 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -22462,15 +22462,9 @@ "xpack.uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlFormatGeneralErrorMessage": "格式无效。例如:{exampleUrl}", "xpack.upgradeAssistant.appTitle": "{version} 升级助手", "xpack.upgradeAssistant.checkupTab.changeFiltersShowMoreLabel": "更改筛选以显示更多内容。", - "xpack.upgradeAssistant.checkupTab.controls.collapseAllButtonLabel": "折叠全部", - "xpack.upgradeAssistant.checkupTab.controls.expandAllButtonLabel": "展开全部", "xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel": "紧急", - "xpack.upgradeAssistant.checkupTab.controls.filterErrorMessageLabel": "筛选无效:{searchTermError}", "xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIndexLabel": "按索引", "xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIssueLabel": "按问题", - "xpack.upgradeAssistant.checkupTab.controls.refreshButtonLabel": "刷新", - "xpack.upgradeAssistant.checkupTab.controls.searchBarPlaceholder": "筛选", - "xpack.upgradeAssistant.checkupTab.controls.searchBarPlaceholderAriaLabel": "筛选", "xpack.upgradeAssistant.checkupTab.deprecations.criticalActionTooltip": "请解决此问题后再升级。", "xpack.upgradeAssistant.checkupTab.deprecations.criticalLabel": "紧急", "xpack.upgradeAssistant.checkupTab.deprecations.documentationButtonLabel": "文档", @@ -22480,9 +22474,6 @@ "xpack.upgradeAssistant.checkupTab.deprecations.warningLabel": "警告", "xpack.upgradeAssistant.checkupTab.indicesBadgeLabel": "{numIndices, plural, other { 个索引}}", "xpack.upgradeAssistant.checkupTab.noDeprecationsLabel": "无弃用内容", - "xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail": "选中 {overviewTabButton} 以执行后续步骤。", - "xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail.overviewTabButtonLabel": "“概述”选项卡", - "xpack.upgradeAssistant.checkupTab.noIssues.noIssuesTitle": "全部清除!", "xpack.upgradeAssistant.checkupTab.numDeprecationsShownLabel": "显示 {numShown} 个,共 {total} 个", "xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.cancelButtonLabel": "取消", "xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.closeButtonLabel": "关闭", diff --git a/x-pack/plugins/upgrade_assistant/common/types.ts b/x-pack/plugins/upgrade_assistant/common/types.ts index b8a5a7c1ab8c..0471fc30f28e 100644 --- a/x-pack/plugins/upgrade_assistant/common/types.ts +++ b/x-pack/plugins/upgrade_assistant/common/types.ts @@ -117,13 +117,14 @@ 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'; +export type UIOpenOption = 'overview' | 'cluster' | 'indices' | 'kibana'; export type UIReindexOption = 'close' | 'open' | 'start' | 'stop'; export interface UIOpen { overview: boolean; cluster: boolean; indices: boolean; + kibana: boolean; } export interface UIReindex { @@ -138,6 +139,7 @@ export interface UpgradeAssistantTelemetrySavedObject { overview: number; cluster: number; indices: number; + kibana: number; }; ui_reindex: { close: number; @@ -152,6 +154,7 @@ export interface UpgradeAssistantTelemetry { overview: number; cluster: number; indices: number; + kibana: number; }; ui_reindex: { close: number; diff --git a/x-pack/plugins/upgrade_assistant/public/application/app.tsx b/x-pack/plugins/upgrade_assistant/public/application/app.tsx index 7be723e335e8..8086d3322c0e 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app.tsx @@ -11,6 +11,7 @@ import { I18nStart, ScopedHistory } from 'src/core/public'; import { AppContextProvider, ContextValue, useAppContext } from './app_context'; import { ComingSoonPrompt } from './components/coming_soon_prompt'; import { EsDeprecationsContent } from './components/es_deprecations'; +import { KibanaDeprecationsContent } from './components/kibana_deprecations'; import { DeprecationsOverview } from './components/overview'; export interface AppDependencies extends ContextValue { @@ -30,6 +31,7 @@ const App: React.FunctionComponent = () => { + ); diff --git a/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx index 18df47d4cbd4..049318f5b78d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx @@ -5,7 +5,13 @@ * 2.0. */ -import { CoreStart, DocLinksStart, HttpSetup, NotificationsStart } from 'src/core/public'; +import { + CoreStart, + DeprecationsServiceStart, + DocLinksStart, + HttpSetup, + NotificationsStart, +} from 'src/core/public'; import React, { createContext, useContext } from 'react'; import { ApiService } from './lib/api'; import { BreadcrumbService } from './lib/breadcrumbs'; @@ -26,6 +32,7 @@ export interface ContextValue { api: ApiService; breadcrumbs: BreadcrumbService; getUrlForApp: CoreStart['application']['getUrlForApp']; + deprecations: DeprecationsServiceStart; } export const AppContext = createContext({} as any); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/constants.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/constants.tsx similarity index 87% rename from x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/constants.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/constants.tsx index feff6010efe3..7b4bee75bc75 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/constants.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/constants.tsx @@ -7,7 +7,7 @@ import { IconColor } from '@elastic/eui'; import { invert } from 'lodash'; -import { DeprecationInfo } from '../../../../common/types'; +import { DeprecationInfo } from '../../../common/types'; export const LEVEL_MAP: { [level: string]: number } = { warning: 0, @@ -24,3 +24,5 @@ export const COLOR_MAP: { [level: string]: IconColor } = { warning: 'default', critical: 'danger', }; + +export const DEPRECATIONS_PER_PAGE = 25; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/controls.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/controls.tsx deleted file mode 100644 index 7212c2db4c6b..000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/controls.tsx +++ /dev/null @@ -1,108 +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, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { DeprecationInfo } from '../../../../common/types'; -import { validateRegExpString } from '../../lib/utils'; -import { GroupByOption, LevelFilterOption } from '../types'; -import { FilterBar } from './filter_bar'; -import { GroupByBar } from './group_by_bar'; - -interface CheckupControlsProps { - allDeprecations?: DeprecationInfo[]; - isLoading: boolean; - loadData: () => void; - currentFilter: LevelFilterOption; - onFilterChange: (filter: LevelFilterOption) => void; - onSearchChange: (filter: string) => void; - availableGroupByOptions: GroupByOption[]; - currentGroupBy: GroupByOption; - onGroupByChange: (groupBy: GroupByOption) => void; -} - -export const CheckupControls: FunctionComponent = ({ - allDeprecations, - isLoading, - loadData, - currentFilter, - onFilterChange, - onSearchChange, - availableGroupByOptions, - currentGroupBy, - onGroupByChange, -}) => { - const [searchTermError, setSearchTermError] = useState(null); - const filterInvalid = Boolean(searchTermError); - return ( - - - - - { - const string = e.target.value; - const errorMessage = validateRegExpString(string); - if (errorMessage) { - // Emit an empty search term to listeners if search term is invalid. - onSearchChange(''); - setSearchTermError(errorMessage); - } else { - onSearchChange(e.target.value); - if (searchTermError) { - setSearchTermError(null); - } - } - }} - /> - - - {/* These two components provide their own EuiFlexItem wrappers */} - - - - - - - - - - - {filterInvalid && ( - - - - )} - - ); -}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab_content.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab_content.tsx index 9e8678fea0eb..8be407371f03 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab_content.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab_content.tsx @@ -5,18 +5,24 @@ * 2.0. */ -import { find } from 'lodash'; -import React, { FunctionComponent, useState } from 'react'; - -import { EuiEmptyPrompt, EuiLink, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +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 { CheckupControls } from './controls'; -import { GroupedDeprecations } from './deprecations/grouped'; +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', { @@ -28,6 +34,54 @@ 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. @@ -40,11 +94,16 @@ export const DeprecationTabContent: FunctionComponent = ({ refreshCheckupData, navigateToOverviewPage, }) => { - const [currentFilter, setCurrentFilter] = useState(LevelFilterOption.all); + const [currentFilter, setCurrentFilter] = useState('all'); const [search, setSearch] = useState(''); const [currentGroupBy, setCurrentGroupBy] = useState(GroupByOption.message); + const [expandState, setExpandState] = useState({ + forceExpand: false, + expandNumber: 0, + }); + const [currentPage, setCurrentPage] = useState(0); - const availableGroupByOptions = () => { + const getAvailableGroupByOptions = () => { if (!deprecations) { return []; } @@ -52,46 +111,28 @@ export const DeprecationTabContent: FunctionComponent = ({ 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 ( - - - - } - body={ - <> -

- -

-

- - - - ), - }} - /> -

- - } - /> +
+ +
); } @@ -100,28 +141,77 @@ export const DeprecationTabContent: FunctionComponent = ({ if (isLoading) { content = {i18nTexts.isLoading}; } 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); + + const filteredDeprecations = filterDeprecations(deprecations, currentFilter, search); + + const groups = groupDeprecations(deprecations, currentFilter, search, currentGroupBy); + content = (
- - - - + + + + <> + {Object.keys(groups) + .sort() + // Apply pagination + .slice(currentPage * DEPRECATIONS_PER_PAGE, (currentPage + 1) * DEPRECATIONS_PER_PAGE) + .map((groupName, index) => [ +
+ + +
, + ])} + + {/* Only show pagination if we have more than DEPRECATIONS_PER_PAGE. */} + {Object.keys(groups).length > DEPRECATIONS_PER_PAGE && ( + <> + + + + + )} +
); } else if (error) { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_deprecations.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_deprecations.scss deleted file mode 100644 index 445ef6269afb..000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_deprecations.scss +++ /dev/null @@ -1,18 +0,0 @@ -.upgDeprecations { - // Pull the container through the padding of EuiPageContent - margin-left: -$euiSizeL; - margin-right: -$euiSizeL; -} - -.upgDeprecations__item { - padding: $euiSize $euiSizeL; - border-top: $euiBorderThin; - - &:last-of-type { - margin-bottom: -$euiSizeL; - } -} - -.upgDeprecations__itemName { - font-weight: $euiFontWeightMedium; -} diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_index.scss b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_index.scss index 55aff6b379db..1f4f0352e793 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_index.scss +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/_index.scss @@ -1,3 +1,2 @@ @import 'cell'; -@import 'deprecations'; @import 'reindex/index'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx index 4a8b9614dcdd..6e00a57b40a9 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/cell.tsx @@ -63,26 +63,25 @@ export const DeprecationCell: FunctionComponent = ({ )} + {items.map((item, index) => ( + + {item.title &&
{item.title}
} +

{item.body}

+
+ ))} + {docUrl && ( -
+ <> + + - -
+ )} - - {items.map((item) => ( -
- - {item.title &&
{item.title}
} -

{item.body}

-
-
- ))} {reindex && ( diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/deprecation_group_item.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/deprecation_group_item.tsx new file mode 100644 index 000000000000..66e2a5d25998 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/deprecation_group_item.tsx @@ -0,0 +1,75 @@ +/* + * 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 = ({ + 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 ( + + {hasIndices && ( + <> + + {numIndices}{' '} + + +   + + )} + LEVEL_MAP[d.level])} + /> + + } + > + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.test.tsx deleted file mode 100644 index 00059fe0456c..000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.test.tsx +++ /dev/null @@ -1,215 +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 { range } from 'lodash'; -import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; -import { EuiBadge, EuiPagination } from '@elastic/eui'; - -import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../common/types'; -import { GroupByOption, LevelFilterOption } from '../../types'; -import { DeprecationAccordion, filterDeps, GroupedDeprecations } from './grouped'; - -describe('filterDeps', () => { - test('filters on levels', () => { - const fd = filterDeps(LevelFilterOption.critical); - expect(fd({ level: 'critical' } as DeprecationInfo)).toBe(true); - expect(fd({ level: 'warning' } as DeprecationInfo)).toBe(false); - }); - - test('filters on title search', () => { - const fd = filterDeps(LevelFilterOption.critical, 'wow'); - expect(fd({ level: 'critical', message: 'the wow error' } as DeprecationInfo)).toBe(true); - expect(fd({ level: 'critical', message: 'other error' } as DeprecationInfo)).toBe(false); - }); - - test('filters on index search', () => { - const fd = filterDeps(LevelFilterOption.critical, 'myIndex'); - expect( - fd({ - level: 'critical', - message: 'the wow error', - index: 'myIndex-2', - } as EnrichedDeprecationInfo) - ).toBe(true); - expect( - fd({ - level: 'critical', - message: 'other error', - index: 'notIndex', - } as EnrichedDeprecationInfo) - ).toBe(false); - }); - - test('filters on node search', () => { - const fd = filterDeps(LevelFilterOption.critical, 'myNode'); - expect( - fd({ - level: 'critical', - message: 'the wow error', - index: 'myNode-123', - } as EnrichedDeprecationInfo) - ).toBe(true); - expect( - fd({ - level: 'critical', - message: 'other error', - index: 'notNode', - } as EnrichedDeprecationInfo) - ).toBe(false); - }); -}); - -describe('GroupedDeprecations', () => { - const defaultProps = { - currentFilter: LevelFilterOption.all, - search: '', - currentGroupBy: GroupByOption.message, - allDeprecations: [ - { message: 'Cluster error 1', url: '', level: 'warning' }, - { message: 'Cluster error 2', url: '', level: 'critical' }, - ] as EnrichedDeprecationInfo[], - }; - - describe('expand + collapse all', () => { - const expectNumOpen = (wrapper: any, numExpected: number) => - expect(wrapper.find('div.euiAccordion-isOpen')).toHaveLength(numExpected); - - test('clicking opens and closes panels', () => { - const wrapper = mountWithIntl(); - expectNumOpen(wrapper, 0); - - // Test expand all - wrapper.find('button[data-test-subj="expandAll"]').simulate('click'); - expectNumOpen(wrapper, 2); - - // Test collapse all - wrapper.find('button[data-test-subj="collapseAll"]').simulate('click'); - expectNumOpen(wrapper, 0); - }); - - test('clicking overrides current state when some are open', () => { - const wrapper = mountWithIntl(); - - // Open a single deprecation - wrapper.find('button.euiAccordion__button').first().simulate('click'); - expectNumOpen(wrapper, 1); - - // Test expand all - wrapper.find('button[data-test-subj="expandAll"]').simulate('click'); - expectNumOpen(wrapper, 2); - - // Close a single deprecation - wrapper.find('button.euiAccordion__button').first().simulate('click'); - expectNumOpen(wrapper, 1); - - // Test collapse all - wrapper.find('button[data-test-subj="collapseAll"]').simulate('click'); - expectNumOpen(wrapper, 0); - }); - }); - - describe('pagination', () => { - const paginationProps = { - ...defaultProps, - allDeprecations: range(0, 40).map((i) => ({ - message: `Message ${i}`, - level: 'warning', - })) as DeprecationInfo[], - }; - - test('it only displays 25 items', () => { - const wrapper = shallowWithIntl(); - expect(wrapper.find(DeprecationAccordion)).toHaveLength(25); - }); - - test('it displays pagination', () => { - const wrapper = shallowWithIntl(); - expect(wrapper.find(EuiPagination).exists()).toBe(true); - }); - - test('shows next page on click', () => { - const wrapper = mountWithIntl(); - wrapper.find('button[data-test-subj="pagination-button-next"]').simulate('click'); - expect(wrapper.find(DeprecationAccordion)).toHaveLength(15); // 40 total - 25 first page = 15 second page - }); - }); - - describe('grouping', () => { - test('group by message', () => { - const wrapper = shallowWithIntl( - - ); - - // Only 2 groups should exist b/c there are only 2 unique messages - expect(wrapper.find(DeprecationAccordion)).toHaveLength(2); - }); - - test('group by index', () => { - const wrapper = shallowWithIntl( - - ); - - // Only 3 groups should exist b/c there are only 3 unique indexes - expect(wrapper.find(DeprecationAccordion)).toHaveLength(3); - }); - }); -}); - -describe('DeprecationAccordion', () => { - const defaultProps = { - id: 'x', - dataTestSubj: 'data-test-subj', - title: 'Issue 1', - currentGroupBy: GroupByOption.message, - forceExpand: false, - deprecations: [{ index: 'index1' }, { index: 'index2' }] as EnrichedDeprecationInfo[], - }; - - test('shows indices count badge', () => { - const wrapper = mountWithIntl(); - expect(wrapper.find(EuiBadge).find('[data-test-subj="indexCount"]').text()).toEqual('2'); - }); -}); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.tsx deleted file mode 100644 index 9879b977f1cf..000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/grouped.tsx +++ /dev/null @@ -1,261 +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 { groupBy } from 'lodash'; -import React, { Fragment, FunctionComponent } from 'react'; - -import { - EuiAccordion, - EuiBadge, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiPagination, - EuiSpacer, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../common/types'; -import { GroupByOption, LevelFilterOption } from '../../types'; - -import { DeprecationCountSummary } from './count_summary'; -import { DeprecationHealth } from './health'; -import { DeprecationList } from './list'; - -// exported only for testing -export const filterDeps = (level: LevelFilterOption, search: string = '') => { - const conditions: Array<(dep: EnrichedDeprecationInfo) => boolean> = []; - - if (level !== LevelFilterOption.all) { - conditions.push((dep: DeprecationInfo) => dep.level === level); - } - - if (search.length > 0) { - // Change everything to lower case for a case-insensitive comparison - conditions.push((dep) => { - try { - const searchReg = new RegExp(search.toLowerCase()); - return Boolean( - dep.message.toLowerCase().match(searchReg) || - (dep.details && dep.details.toLowerCase().match(searchReg)) || - (dep.index && dep.index.toLowerCase().match(searchReg)) || - (dep.node && dep.node.toLowerCase().match(searchReg)) - ); - } 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); -}; - -/** - * A single accordion item for a grouped deprecation item. - */ -export const DeprecationAccordion: FunctionComponent<{ - id: string; - deprecations: EnrichedDeprecationInfo[]; - title: string; - currentGroupBy: GroupByOption; - forceExpand: boolean; - dataTestSubj: string; -}> = ({ id, deprecations, title, currentGroupBy, forceExpand, dataTestSubj }) => { - const hasIndices = Boolean( - currentGroupBy === GroupByOption.message && deprecations.filter((d) => d.index).length - ); - const numIndices = hasIndices ? deprecations.length : null; - - return ( - {title}} - extraAction={ -
- {hasIndices && ( - - - {numIndices}{' '} - - -   - - )} - -
- } - > - -
- ); -}; - -interface GroupedDeprecationsProps { - currentFilter: LevelFilterOption; - search: string; - currentGroupBy: GroupByOption; - allDeprecations?: EnrichedDeprecationInfo[]; -} - -interface GroupedDeprecationsState { - forceExpand: true | false | null; - expandNumber: number; - currentPage: number; -} - -const PER_PAGE = 25; - -/** - * Collection of calculated fields based on props, extracted for reuse in - * `render` and `getDerivedStateFromProps`. - */ -const CalcFields = { - filteredDeprecations(props: GroupedDeprecationsProps) { - const { allDeprecations = [], currentFilter, search } = props; - return allDeprecations.filter(filterDeps(currentFilter, search)); - }, - - groups(props: GroupedDeprecationsProps) { - const { currentGroupBy } = props; - return groupBy(CalcFields.filteredDeprecations(props), currentGroupBy); - }, - - numPages(props: GroupedDeprecationsProps) { - return Math.ceil(Object.keys(CalcFields.groups(props)).length / PER_PAGE); - }, -}; - -/** - * Displays groups of deprecation messages in an accordion. - */ -export class GroupedDeprecations extends React.Component< - GroupedDeprecationsProps, - GroupedDeprecationsState -> { - public static getDerivedStateFromProps( - nextProps: GroupedDeprecationsProps, - { currentPage }: GroupedDeprecationsState - ) { - // If filters change and the currentPage is now bigger than the num of pages we're going to show, - // reset the current page to 0. - if (currentPage >= CalcFields.numPages(nextProps)) { - return { currentPage: 0 }; - } else { - return null; - } - } - - public state = { - forceExpand: false, - // `expandNumber` is used as workaround to force EuiAccordion to re-render by - // incrementing this number (used as a key) when expand all or collapse all is clicked. - expandNumber: 0, - currentPage: 0, - }; - - public render() { - const { currentGroupBy, allDeprecations = [] } = this.props; - const { forceExpand, expandNumber, currentPage } = this.state; - - const filteredDeprecations = CalcFields.filteredDeprecations(this.props); - const groups = CalcFields.groups(this.props); - - return ( - - - - this.setExpand(true)} - data-test-subj="expandAll" - > - - - - - this.setExpand(false)} - data-test-subj="collapseAll" - > - - - - - - - - - - - -
- {Object.keys(groups) - .sort() - // Apply pagination - .slice(currentPage * PER_PAGE, (currentPage + 1) * PER_PAGE) - .map((groupName) => [ - , - ])} - - {/* Only show pagination if we have more than PER_PAGE. */} - {Object.keys(groups).length > PER_PAGE && ( - - - - - - - - - - )} -
-
- ); - } - - private setExpand = (forceExpand: boolean) => { - this.setState({ forceExpand, expandNumber: this.state.expandNumber + 1 }); - }; - - private setPage = (currentPage: number) => this.setState({ currentPage }); -} diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index.tsx index e361e98beffb..a4152e52a35b 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/index.tsx @@ -5,4 +5,4 @@ * 2.0. */ -export { GroupedDeprecations } from './grouped'; +export { EsDeprecationAccordion } from './deprecation_group_item'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx index c1b6357d504e..579cf1f4a55b 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.test.tsx @@ -10,9 +10,9 @@ import React from 'react'; import { EnrichedDeprecationInfo } from '../../../../../common/types'; import { GroupByOption } from '../../types'; -import { DeprecationList } from './list'; +import { EsDeprecationList } from './list'; -describe('DeprecationList', () => { +describe('EsDeprecationList', () => { describe('group by message', () => { const defaultProps = { deprecations: [ @@ -23,7 +23,7 @@ describe('DeprecationList', () => { }; test('shows simple messages when index field is not present', () => { - expect(shallow()).toMatchInlineSnapshot(` + expect(shallow()).toMatchInlineSnapshot(`
{ "url": "", } } - key="Issue 1" + key="Issue 1-0" /> { "url": "", } } - key="Issue 1" + key="Issue 1-1" />
`); @@ -58,7 +58,7 @@ describe('DeprecationList', () => { index: index.toString(), })), }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchInlineSnapshot(` { }; test('shows detailed messages', () => { - expect(shallow()).toMatchInlineSnapshot(` + expect(shallow()).toMatchInlineSnapshot(`
{ "url": "", } } - key="Issue 1" + key="Issue 1-0" /> { "url": "", } } - key="Issue 2" + key="Issue 2-1" />
`); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx index 69350f2fdb89..563def5e3b12 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/list.tsx @@ -10,7 +10,7 @@ import React, { FunctionComponent } from 'react'; import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../common/types'; import { GroupByOption } from '../../types'; -import { COLOR_MAP, LEVEL_MAP } from '../constants'; +import { COLOR_MAP, LEVEL_MAP } from '../../constants'; import { DeprecationCell } from './cell'; import { IndexDeprecationDetails, IndexDeprecationTable } from './index_table'; @@ -86,7 +86,7 @@ const IndexDeprecation: FunctionComponent = ({ deprecatio * A list of deprecations that is either shown as individual deprecation cells or as a * deprecation summary for a list of indices. */ -export const DeprecationList: FunctionComponent<{ +export const EsDeprecationList: FunctionComponent<{ deprecations: EnrichedDeprecationInfo[]; currentGroupBy: GroupByOption; }> = ({ deprecations, currentGroupBy }) => { @@ -106,16 +106,16 @@ export const DeprecationList: FunctionComponent<{ } else if (currentGroupBy === GroupByOption.index) { return (
- {deprecations.sort(sortByLevelDesc).map((dep) => ( - + {deprecations.sort(sortByLevelDesc).map((dep, index) => ( + ))}
); } else { return (
- {deprecations.sort(sortByLevelDesc).map((dep) => ( - + {deprecations.sort(sortByLevelDesc).map((dep, index) => ( + ))}
); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx deleted file mode 100644 index 848ac3b14a81..000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx +++ /dev/null @@ -1,84 +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 { groupBy } from 'lodash'; -import React from 'react'; - -import { EuiFilterButton, EuiFilterGroup, EuiFlexItem } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { DeprecationInfo } from '../../../../common/types'; -import { LevelFilterOption } from '../types'; - -const LocalizedOptions: { [option: string]: string } = { - warning: i18n.translate( - 'xpack.upgradeAssistant.checkupTab.controls.filterBar.warningButtonLabel', - { - defaultMessage: 'warning', - } - ), - critical: i18n.translate( - 'xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel', - { defaultMessage: 'critical' } - ), -}; - -interface FilterBarProps { - allDeprecations?: DeprecationInfo[]; - currentFilter: LevelFilterOption; - onFilterChange(level: LevelFilterOption): void; -} - -export const FilterBar: React.FunctionComponent = ({ - allDeprecations = [], - currentFilter, - onFilterChange, -}) => { - const levelGroups = groupBy(allDeprecations, 'level'); - const levelCounts = Object.keys(levelGroups).reduce((counts, level) => { - counts[level] = levelGroups[level].length; - return counts; - }, {} as { [level: string]: number }); - - return ( - - - { - onFilterChange( - currentFilter !== LevelFilterOption.critical - ? LevelFilterOption.critical - : LevelFilterOption.all - ); - }} - hasActiveFilters={currentFilter === LevelFilterOption.critical} - numFilters={levelCounts[LevelFilterOption.critical] || undefined} - data-test-subj="criticalLevelFilter" - > - {LocalizedOptions[LevelFilterOption.critical]} - - { - onFilterChange( - currentFilter !== LevelFilterOption.warning - ? LevelFilterOption.warning - : LevelFilterOption.all - ); - }} - hasActiveFilters={currentFilter === LevelFilterOption.warning} - numFilters={levelCounts[LevelFilterOption.warning] || undefined} - data-test-subj="warningLevelFilter" - > - {LocalizedOptions[LevelFilterOption.warning]} - - - - ); -}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_item.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_item.tsx new file mode 100644 index 000000000000..5bcc49590c55 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_item.tsx @@ -0,0 +1,145 @@ +/* + * 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, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiText, + EuiCallOut, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import type { DomainDeprecationDetails } from 'kibana/public'; +import { DeprecationHealth } from '../shared'; +import { LEVEL_MAP } from '../constants'; +import { StepsModalContent } from './steps_modal'; + +const i18nTexts = { + getDeprecationTitle: (domainId: string) => { + return i18n.translate('xpack.upgradeAssistant.deprecationGroupItemTitle', { + defaultMessage: "'{domainId}' is using a deprecated feature", + values: { + domainId, + }, + }); + }, + docLinkText: i18n.translate('xpack.upgradeAssistant.deprecationGroupItem.docLinkText', { + defaultMessage: 'View documentation', + }), + manualFixButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.deprecationGroupItem.fixButtonLabel', + { + defaultMessage: 'Show steps to fix', + } + ), + resolveButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.deprecationGroupItem.resolveButtonLabel', + { + defaultMessage: 'Quick resolve', + } + ), +}; + +export interface Props { + deprecation: DomainDeprecationDetails; + index: number; + forceExpand: boolean; + showStepsModal: (modalContent: StepsModalContent) => void; + showResolveModal: (deprecation: DomainDeprecationDetails) => void; +} + +export const KibanaDeprecationAccordion: FunctionComponent = ({ + deprecation, + forceExpand, + index, + showStepsModal, + showResolveModal, +}) => { + const { domainId, level, message, documentationUrl, correctiveActions } = deprecation; + + return ( + } + > + + + + {level === 'fetch_error' ? ( + + ) : ( + <> +

{message}

+ + {(documentationUrl || correctiveActions?.manualSteps) && ( + + {correctiveActions?.api && ( + + showResolveModal(deprecation)} + > + {i18nTexts.resolveButtonLabel} + + + )} + + {correctiveActions?.manualSteps && ( + + + showStepsModal({ + domainId, + steps: correctiveActions.manualSteps!, + documentationUrl, + }) + } + > + {i18nTexts.manualFixButtonLabel} + + + )} + + {documentationUrl && ( + + + {i18nTexts.docLinkText} + + + )} + + )} + + )} +
+
+
+
+ ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_list.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_list.tsx new file mode 100644 index 000000000000..fb61efc373ac --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/deprecation_list.tsx @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent, useState, useEffect } from 'react'; +import { groupBy } from 'lodash'; +import { EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; + +import type { DomainDeprecationDetails } from 'kibana/public'; + +import { LevelFilterOption } from '../types'; +import { SearchBar, DeprecationListBar, DeprecationPagination } from '../shared'; +import { DEPRECATIONS_PER_PAGE } from '../constants'; +import { KibanaDeprecationAccordion } from './deprecation_item'; +import { StepsModalContent } from './steps_modal'; +import { KibanaDeprecationErrors } from './kibana_deprecation_errors'; + +interface Props { + deprecations: DomainDeprecationDetails[]; + showStepsModal: (newStepsModalContent: StepsModalContent) => void; + showResolveModal: (deprecation: DomainDeprecationDetails) => void; + reloadDeprecations: () => Promise; + isLoading: boolean; +} + +const getFilteredDeprecations = ( + deprecations: DomainDeprecationDetails[], + level: LevelFilterOption, + search: string +) => { + return deprecations + .filter((deprecation) => { + return level === 'all' || deprecation.level === level; + }) + .filter((filteredDep) => { + if (search.length > 0) { + try { + // 'i' is used for case-insensitive matching + const searchReg = new RegExp(search, 'i'); + return searchReg.test(filteredDep.message); + } catch (e) { + // ignore any regexp errors + return true; + } + } + return true; + }); +}; + +export const KibanaDeprecationList: FunctionComponent = ({ + deprecations, + showStepsModal, + showResolveModal, + reloadDeprecations, + isLoading, +}) => { + const [currentFilter, setCurrentFilter] = useState('all'); + const [search, setSearch] = useState(''); + const [expandState, setExpandState] = useState({ + forceExpand: false, + expandNumber: 0, + }); + const [currentPage, setCurrentPage] = useState(0); + + const setExpandAll = (expandAll: boolean) => { + setExpandState({ forceExpand: expandAll, expandNumber: expandState.expandNumber + 1 }); + }; + + const levelGroups = groupBy(deprecations, 'level'); + const levelToDeprecationCountMap = Object.keys(levelGroups).reduce((counts, level) => { + counts[level] = levelGroups[level].length; + return counts; + }, {} as { [level: string]: number }); + + const filteredDeprecations = getFilteredDeprecations(deprecations, currentFilter, search); + + const deprecationsWithErrors = deprecations.filter((dep) => dep.level === 'fetch_error'); + + useEffect(() => { + const pageCount = Math.ceil(filteredDeprecations.length / DEPRECATIONS_PER_PAGE); + if (currentPage >= pageCount) { + setCurrentPage(0); + } + }, [filteredDeprecations, currentPage]); + + return ( + <> + + + {deprecationsWithErrors.length > 0 && ( + <> + + + + )} + + + + + + <> + {filteredDeprecations + .slice(currentPage * DEPRECATIONS_PER_PAGE, (currentPage + 1) * DEPRECATIONS_PER_PAGE) + .map((deprecation, index) => [ +
+ + +
, + ])} + + {/* Only show pagination if we have more than DEPRECATIONS_PER_PAGE */} + {filteredDeprecations.length > DEPRECATIONS_PER_PAGE && ( + <> + + + + + )} + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/index.ts new file mode 100644 index 000000000000..84d2b8875718 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { KibanaDeprecationsContent } from './kibana_deprecations'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecation_errors.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecation_errors.tsx new file mode 100644 index 000000000000..e6ba83919c31 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecation_errors.tsx @@ -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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut } from '@elastic/eui'; + +interface Props { + errorType: 'pluginError' | 'requestError'; +} + +const i18nTexts = { + pluginError: i18n.translate('xpack.upgradeAssistant.kibanaDeprecationErrors.pluginErrorMessage', { + defaultMessage: + 'Not all Kibana deprecations were retrieved successfully. This list may be incomplete. Check the Kibana server logs for errors.', + }), + loadingError: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecationErrors.loadingErrorMessage', + { + defaultMessage: + 'Could not retrieve Kibana deprecations. Check the Kibana server logs for errors.', + } + ), +}; + +export const KibanaDeprecationErrors: React.FunctionComponent = ({ errorType }) => { + if (errorType === 'pluginError') { + return ( + + ); + } + + return ( + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx new file mode 100644 index 000000000000..bb8a7366beb4 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/kibana_deprecations.tsx @@ -0,0 +1,210 @@ +/* + * 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, useCallback } from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import { + EuiButtonEmpty, + EuiPageBody, + EuiPageHeader, + EuiPageContent, + EuiPageContentBody, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import type { DomainDeprecationDetails } from 'kibana/public'; +import { SectionLoading } from '../../../shared_imports'; +import { useAppContext } from '../../app_context'; +import { NoDeprecationsPrompt } from '../shared'; +import { KibanaDeprecationList } from './deprecation_list'; +import { StepsModal, StepsModalContent } from './steps_modal'; +import { KibanaDeprecationErrors } from './kibana_deprecation_errors'; +import { ResolveDeprecationModal } from './resolve_deprecation_modal'; +import { LEVEL_MAP } from '../constants'; + +const i18nTexts = { + pageTitle: i18n.translate('xpack.upgradeAssistant.kibanaDeprecations.pageTitle', { + defaultMessage: 'Kibana', + }), + pageDescription: i18n.translate('xpack.upgradeAssistant.kibanaDeprecations.pageDescription', { + defaultMessage: 'Some Kibana issues may require your attention. Resolve them before upgrading.', + }), + docLinkText: i18n.translate('xpack.upgradeAssistant.kibanaDeprecations.docLinkText', { + defaultMessage: 'Documentation', + }), + deprecationLabel: i18n.translate('xpack.upgradeAssistant.kibanaDeprecations.deprecationLabel', { + defaultMessage: 'Kibana', + }), + isLoading: i18n.translate('xpack.upgradeAssistant.kibanaDeprecations.loadingText', { + defaultMessage: 'Loading deprecations…', + }), + successMessage: i18n.translate('xpack.upgradeAssistant.kibanaDeprecations.successMessage', { + defaultMessage: 'Deprecation resolved', + }), + errorMessage: i18n.translate('xpack.upgradeAssistant.kibanaDeprecations.errorMessage', { + defaultMessage: 'Error resolving deprecation', + }), +}; + +const sortByLevelDesc = (a: DomainDeprecationDetails, b: DomainDeprecationDetails) => { + return -1 * (LEVEL_MAP[a.level] - LEVEL_MAP[b.level]); +}; + +export const KibanaDeprecationsContent = withRouter(({ history }: RouteComponentProps) => { + const [kibanaDeprecations, setKibanaDeprecations] = useState< + DomainDeprecationDetails[] | undefined + >(undefined); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + const [stepsModalContent, setStepsModalContent] = useState( + undefined + ); + const [resolveModalContent, setResolveModalContent] = useState< + undefined | DomainDeprecationDetails + >(undefined); + const [isResolvingDeprecation, setIsResolvingDeprecation] = useState(false); + + const { deprecations, breadcrumbs, docLinks, api, notifications } = useAppContext(); + + const getAllDeprecations = useCallback(async () => { + setIsLoading(true); + + try { + const response = await deprecations.getAllDeprecations(); + const sortedDeprecations = response.sort(sortByLevelDesc); + setKibanaDeprecations(sortedDeprecations); + } catch (e) { + setError(e); + } + + setIsLoading(false); + }, [deprecations]); + + const toggleStepsModal = (newStepsModalContent?: StepsModalContent) => { + setStepsModalContent(newStepsModalContent); + }; + + const toggleResolveModal = (newResolveModalContent?: DomainDeprecationDetails) => { + setResolveModalContent(newResolveModalContent); + }; + + const resolveDeprecation = async (deprecationDetails: DomainDeprecationDetails) => { + setIsResolvingDeprecation(true); + + const response = await deprecations.resolveDeprecation(deprecationDetails); + + setIsResolvingDeprecation(false); + toggleResolveModal(); + + // Handle error case + if (response.status === 'fail') { + notifications.toasts.addError(new Error(response.reason), { + title: i18nTexts.errorMessage, + }); + + return; + } + + notifications.toasts.addSuccess(i18nTexts.successMessage); + // Refetch deprecations + getAllDeprecations(); + }; + + useEffect(() => { + async function sendTelemetryData() { + await api.sendTelemetryData({ + kibana: true, + }); + } + + sendTelemetryData(); + }, [api]); + + useEffect(() => { + breadcrumbs.setBreadcrumbs('kibanaDeprecations'); + }, [breadcrumbs]); + + useEffect(() => { + getAllDeprecations(); + }, [deprecations, getAllDeprecations]); + + const getPageContent = () => { + if (kibanaDeprecations && kibanaDeprecations.length === 0) { + return ( + history.push('/overview')} + /> + ); + } + + let content: React.ReactNode; + + if (isLoading) { + content = {i18nTexts.isLoading}; + } else if (kibanaDeprecations?.length) { + content = ( + + ); + } else if (error) { + content = ; + } + + return ( +
+ + {content} +
+ ); + }; + + return ( + + + + {i18nTexts.docLinkText} + , + ]} + /> + + + {getPageContent()} + + {stepsModalContent && ( + toggleStepsModal()} modalContent={stepsModalContent} /> + )} + + {resolveModalContent && ( + toggleResolveModal()} + resolveDeprecation={resolveDeprecation} + isResolvingDeprecation={isResolvingDeprecation} + deprecation={resolveModalContent} + /> + )} + + + + ); +}); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/resolve_deprecation_modal.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/resolve_deprecation_modal.tsx new file mode 100644 index 000000000000..dd78c3513f97 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/resolve_deprecation_modal.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiConfirmModal } from '@elastic/eui'; +import type { DomainDeprecationDetails } from 'kibana/public'; + +interface Props { + closeModal: () => void; + deprecation: DomainDeprecationDetails; + isResolvingDeprecation: boolean; + resolveDeprecation: (deprecationDetails: DomainDeprecationDetails) => Promise; +} + +const i18nTexts = { + getModalTitle: (domainId: string) => + i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecations.resolveConfirmationModal.modalTitle', + { + defaultMessage: "Resolve '{domainId}'?", + values: { + domainId, + }, + } + ), + cancelButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecations.resolveConfirmationModal.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + ), + resolveButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecations.resolveConfirmationModal.resolveButtonLabel', + { + defaultMessage: 'Resolve', + } + ), +}; + +export const ResolveDeprecationModal: FunctionComponent = ({ + closeModal, + deprecation, + isResolvingDeprecation, + resolveDeprecation, +}) => { + return ( + resolveDeprecation(deprecation)} + cancelButtonText={i18nTexts.cancelButtonLabel} + confirmButtonText={i18nTexts.resolveButtonLabel} + defaultFocusedButton="confirm" + isLoading={isResolvingDeprecation} + /> + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/steps_modal.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/steps_modal.tsx new file mode 100644 index 000000000000..7646fcba6ad3 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/kibana_deprecations/steps_modal.tsx @@ -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, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { + EuiText, + EuiSteps, + EuiSpacer, + EuiButton, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiTitle, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; + +export interface StepsModalContent { + domainId: string; + steps: string[]; + documentationUrl?: string; +} + +interface Props { + closeModal: () => void; + modalContent: StepsModalContent; +} + +const i18nTexts = { + getModalTitle: (domainId: string) => + i18n.translate('xpack.upgradeAssistant.kibanaDeprecations.stepsModal.modalTitle', { + defaultMessage: "Fix '{domainId}'", + values: { + domainId, + }, + }), + getStepTitle: (step: number) => + i18n.translate('xpack.upgradeAssistant.kibanaDeprecations.stepsModal.stepTitle', { + defaultMessage: 'Step {step}', + values: { + step, + }, + }), + modalDescription: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecations.stepsModal.modalDescription', + { + defaultMessage: 'Follow the steps below to address this deprecation.', + } + ), + docLinkLabel: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecations.stepsModal.docLinkLabel', + { + defaultMessage: 'View documentation', + } + ), + closeButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecations.stepsModal.closeButtonLabel', + { + defaultMessage: 'Close', + } + ), +}; + +export const StepsModal: FunctionComponent = ({ closeModal, modalContent }) => { + const { domainId, steps, documentationUrl } = modalContent; + + return ( + + + + +

{i18nTexts.getModalTitle(domainId)}

+
+
+
+ + + <> + +

{i18nTexts.modalDescription}

+
+ + + + { + return { + title: i18nTexts.getStepTitle(index + 1), + children: ( + +

{step}

+
+ ), + }; + })} + /> + +
+ + + + {documentationUrl && ( + + + {i18nTexts.docLinkLabel} + + + )} + + + + {i18nTexts.closeButtonLabel} + + + + +
+ ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats.tsx index 51a66bdd3539..3152639d3f10 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats.tsx @@ -16,6 +16,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIconTip, + EuiScreenReaderOnly, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -46,6 +47,16 @@ const i18nTexts = { defaultMessage: 'View deprecations', } ), + loadingText: i18n.translate('xpack.upgradeAssistant.esDeprecationStats.loadingText', { + defaultMessage: 'Loading Elasticsearch deprecation stats…', + }), + getCriticalDeprecationsMessage: (criticalDeprecations: number) => + i18n.translate('xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsLabel', { + defaultMessage: 'This cluster has {criticalDeprecations} critical deprecations', + values: { + criticalDeprecations, + }, + }), getTotalDeprecationsTooltip: (clusterCount: number, indexCount: number) => i18n.translate('xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTooltip', { defaultMessage: @@ -105,11 +116,27 @@ export const ESDeprecationStats: FunctionComponent = ({ history }) => { esDeprecations?.indices.length ?? 0 )} position="right" + iconProps={{ + tabIndex: -1, + }} /> } isLoading={isLoading} - /> + > + {error === null && ( + +

+ {isLoading + ? i18nTexts.loadingText + : i18nTexts.getTotalDeprecationsTooltip( + esDeprecations?.cluster.length ?? 0, + esDeprecations?.indices.length ?? 0 + )} +

+
+ )} + @@ -120,6 +147,16 @@ export const ESDeprecationStats: FunctionComponent = ({ history }) => { titleColor="danger" isLoading={isLoading} > + {error === null && ( + +

+ {isLoading + ? i18nTexts.loadingText + : i18nTexts.getCriticalDeprecationsMessage(criticalDeprecations.length)} +

+
+ )} + {error && }
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/kibana_stats.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/kibana_stats.tsx new file mode 100644 index 000000000000..28941d1305ad --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/kibana_stats.tsx @@ -0,0 +1,194 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent, useEffect, useState } from 'react'; + +import { + EuiLink, + EuiPanel, + EuiStat, + EuiTitle, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiScreenReaderOnly, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { RouteComponentProps } from 'react-router-dom'; +import type { DomainDeprecationDetails } from 'kibana/public'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { useAppContext } from '../../app_context'; + +const i18nTexts = { + statsTitle: i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.statsTitle', { + defaultMessage: 'Kibana', + }), + totalDeprecationsTitle: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecationStats.totalDeprecationsTitle', + { + defaultMessage: 'Deprecations', + } + ), + criticalDeprecationsTitle: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecationStats.criticalDeprecationsTitle', + { + defaultMessage: 'Critical', + } + ), + viewDeprecationsLink: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecationStats.viewDeprecationsLinkText', + { + defaultMessage: 'View deprecations', + } + ), + loadingError: i18n.translate( + 'xpack.upgradeAssistant.kibanaDeprecationStats.loadingErrorMessage', + { + defaultMessage: 'An error occurred while retrieving Kibana deprecations.', + } + ), + loadingText: i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.loadingText', { + defaultMessage: 'Loading Kibana deprecation stats…', + }), + getCriticalDeprecationsMessage: (criticalDeprecations: number) => + i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.criticalDeprecationsLabel', { + defaultMessage: 'Kibana has {criticalDeprecations} critical deprecations', + values: { + criticalDeprecations, + }, + }), + getTotalDeprecationsMessage: (totalDeprecations: number) => + i18n.translate('xpack.upgradeAssistant.kibanaDeprecationStats.totalDeprecationsLabel', { + defaultMessage: 'Kibana has {totalDeprecations} total deprecations', + values: { + totalDeprecations, + }, + }), +}; + +interface Props { + history: RouteComponentProps['history']; +} + +export const KibanaDeprecationStats: FunctionComponent = ({ history }) => { + const { deprecations } = useAppContext(); + + const [kibanaDeprecations, setKibanaDeprecations] = useState< + DomainDeprecationDetails[] | undefined + >(undefined); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + + useEffect(() => { + async function getAllDeprecations() { + setIsLoading(true); + + try { + const response = await deprecations.getAllDeprecations(); + setKibanaDeprecations(response); + } catch (e) { + setError(e); + } + + setIsLoading(false); + } + + getAllDeprecations(); + }, [deprecations]); + + return ( + + + + +

{i18nTexts.statsTitle}

+
+
+ + + {i18nTexts.viewDeprecationsLink} + + +
+ + + + + + + {error === undefined && ( + +

+ {isLoading + ? i18nTexts.loadingText + : i18nTexts.getTotalDeprecationsMessage(kibanaDeprecations?.length ?? 0)} +

+
+ )} +
+
+ + + deprecation.level === 'critical') + ?.length ?? '0' + : '--' + } + description={i18nTexts.criticalDeprecationsTitle} + titleColor="danger" + isLoading={isLoading} + > + {error === undefined && ( + +

+ {isLoading + ? i18nTexts.loadingText + : i18nTexts.getCriticalDeprecationsMessage( + kibanaDeprecations + ? kibanaDeprecations.filter( + (deprecation) => deprecation.level === 'critical' + )?.length ?? 0 + : 0 + )} +

+
+ )} + + {error && ( + <> + + + + + )} +
+
+
+
+ ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx index 0784fbc10280..b346d918f212 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx @@ -27,6 +27,7 @@ import { RouteComponentProps } from 'react-router-dom'; import { useAppContext } from '../../app_context'; import { LatestMinorBanner } from '../latest_minor_banner'; import { ESDeprecationStats } from './es_stats'; +import { KibanaDeprecationStats } from './kibana_stats'; import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; const i18nTexts = { @@ -114,21 +115,25 @@ export const DeprecationsOverview: FunctionComponent = ({ history }) => { - + + - - - - - + + + + + + + + diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/count_summary.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_list_bar/count_summary.tsx similarity index 68% rename from x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/count_summary.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_list_bar/count_summary.tsx index db176ba43d8e..709ef7224870 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/count_summary.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_list_bar/count_summary.tsx @@ -5,23 +5,21 @@ * 2.0. */ -import React, { Fragment, FunctionComponent } from 'react'; +import React, { FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EnrichedDeprecationInfo } from '../../../../../common/types'; - export const DeprecationCountSummary: FunctionComponent<{ - deprecations: EnrichedDeprecationInfo[]; - allDeprecations: EnrichedDeprecationInfo[]; -}> = ({ deprecations, allDeprecations }) => ( + allDeprecationsCount: number; + filteredDeprecationsCount: number; +}> = ({ filteredDeprecationsCount, allDeprecationsCount }) => ( - {allDeprecations.length ? ( + {allDeprecationsCount > 0 ? ( ) : ( )} - {deprecations.length !== allDeprecations.length && ( - + {filteredDeprecationsCount !== allDeprecationsCount && ( + <> {'. '} - + )} ); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_list_bar/deprecation_list_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_list_bar/deprecation_list_bar.tsx new file mode 100644 index 000000000000..6cb5ae3675c4 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_list_bar/deprecation_list_bar.tsx @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { DeprecationCountSummary } from './count_summary'; + +const i18nTexts = { + expandAllButton: i18n.translate( + 'xpack.upgradeAssistant.deprecationListBar.expandAllButtonLabel', + { + defaultMessage: 'Expand all', + } + ), + collapseAllButton: i18n.translate( + 'xpack.upgradeAssistant.deprecationListBar.collapseAllButtonLabel', + { + defaultMessage: 'Collapse all', + } + ), +}; + +export const DeprecationListBar: FunctionComponent<{ + allDeprecationsCount: number; + filteredDeprecationsCount: number; + setExpandAll: (shouldExpandAll: boolean) => void; +}> = ({ allDeprecationsCount, filteredDeprecationsCount, setExpandAll }) => { + return ( + + + + + + + + + setExpandAll(true)} + data-test-subj="expandAll" + > + {i18nTexts.expandAllButton} + + + + setExpandAll(false)} + data-test-subj="collapseAll" + > + {i18nTexts.collapseAllButton} + + + + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_list_bar/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_list_bar/index.ts new file mode 100644 index 000000000000..cbc04fd86bfb --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_list_bar/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { DeprecationListBar } from './deprecation_list_bar'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_pagination.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_pagination.tsx new file mode 100644 index 000000000000..ae2c0ba1c487 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/deprecation_pagination.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiPagination } from '@elastic/eui'; + +export const DeprecationPagination: FunctionComponent<{ + pageCount: number; + activePage: number; + setPage: (page: number) => void; +}> = ({ pageCount, activePage, setPage }) => { + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/health.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/health.tsx similarity index 86% rename from x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/health.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/shared/health.tsx index c489824b1059..362b2af684e2 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecations/health.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/health.tsx @@ -11,8 +11,8 @@ import React, { FunctionComponent } from 'react'; import { EuiBadge, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DeprecationInfo } from '../../../../../common/types'; -import { COLOR_MAP, LEVEL_MAP, REVERSE_LEVEL_MAP } from '../constants'; +import { DeprecationInfo } from '../../../../common/types'; +import { COLOR_MAP, REVERSE_LEVEL_MAP } from '../constants'; const LocalizedLevels: { [level: string]: string } = { warning: i18n.translate('xpack.upgradeAssistant.checkupTab.deprecations.warningLabel', { @@ -33,7 +33,7 @@ export const LocalizedActions: { [level: string]: string } = { }; interface DeprecationHealthProps { - deprecations: DeprecationInfo[]; + deprecationLevels: number[]; single?: boolean; } @@ -54,23 +54,21 @@ const SingleHealth: FunctionComponent<{ level: DeprecationInfo['level']; label: * deprecations in the list. */ export const DeprecationHealth: FunctionComponent = ({ - deprecations, + deprecationLevels, single = false, }) => { - if (deprecations.length === 0) { + if (deprecationLevels.length === 0) { return ; } - const levels = deprecations.map((d) => LEVEL_MAP[d.level]); - if (single) { - const highest = Math.max(...levels); + const highest = Math.max(...deprecationLevels); const highestLevel = REVERSE_LEVEL_MAP[highest]; return ; } - const countByLevel = countBy(levels); + const countByLevel = countBy(deprecationLevels); return ( diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/shared/index.ts new file mode 100644 index 000000000000..c79d8247a93f --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/index.ts @@ -0,0 +1,12 @@ +/* + * 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 { NoDeprecationsPrompt } from './no_deprecations'; +export { DeprecationHealth } from './health'; +export { SearchBar } from './search_bar'; +export { DeprecationPagination } from './deprecation_pagination'; +export { DeprecationListBar } from './deprecation_list_bar'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/no_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/no_deprecations.tsx new file mode 100644 index 000000000000..3626151b63bb --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/no_deprecations.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; + +import { EuiLink, EuiEmptyPrompt } from '@elastic/eui'; +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) => + i18n.translate('xpack.upgradeAssistant.noDeprecationsPrompt.description', { + defaultMessage: 'Your configuration is up to date.', + }), + getEmptyPromptNextStepsDescription: (navigateToOverviewPage: () => void) => ( + + {i18n.translate('xpack.upgradeAssistant.noDeprecationsPrompt.overviewLinkText', { + defaultMessage: 'Overview page', + })} + + ), + }} + /> + ), +}; + +interface Props { + deprecationType: string; + navigateToOverviewPage: () => void; +} + +export const NoDeprecationsPrompt: FunctionComponent = ({ + deprecationType, + navigateToOverviewPage, +}) => { + return ( + {i18nTexts.emptyPromptTitle}} + body={ + <> +

+ {i18nTexts.getEmptyPromptDescription(deprecationType)} +

+

{i18nTexts.getEmptyPromptNextStepsDescription(navigateToOverviewPage)}

+ + } + /> + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/group_by_bar.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/__snapshots__/group_by_filter.test.tsx.snap similarity index 91% rename from x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/group_by_bar.test.tsx.snap rename to x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/__snapshots__/group_by_filter.test.tsx.snap index dfc69c57cfff..64def47db135 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/group_by_bar.test.tsx.snap +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/__snapshots__/group_by_filter.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`GroupByBar renders 1`] = ` +exports[`GroupByFilter renders 1`] = ` diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/filter_bar.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/__snapshots__/level_filter.test.tsx.snap similarity index 54% rename from x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/filter_bar.test.tsx.snap rename to x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/__snapshots__/level_filter.test.tsx.snap index b88886b36416..4865c5fa8eb5 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/filter_bar.test.tsx.snap +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/__snapshots__/level_filter.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FilterBar renders 1`] = ` +exports[`DeprecationLevelFilter renders 1`] = ` @@ -9,20 +9,11 @@ exports[`FilterBar renders 1`] = ` data-test-subj="criticalLevelFilter" hasActiveFilters={false} key="critical" - numFilters={2} + numFilters={1} onClick={[Function]} - withNext={true} > critical - - warning - `; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/group_by_filter.test.tsx similarity index 75% rename from x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/group_by_filter.test.tsx index 53f76d6d0f98..fa863e4935c0 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/group_by_filter.test.tsx @@ -8,8 +8,8 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { GroupByOption } from '../types'; -import { GroupByBar } from './group_by_bar'; +import { GroupByOption } from '../../types'; +import { GroupByFilter } from './group_by_filter'; const defaultProps = { availableGroupByOptions: [GroupByOption.message, GroupByOption.index], @@ -17,13 +17,13 @@ const defaultProps = { onGroupByChange: jest.fn(), }; -describe('GroupByBar', () => { +describe('GroupByFilter', () => { test('renders', () => { - expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); }); test('clicking button calls onGroupByChange', () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.find('button.euiFilterButton-hasActiveFilters').simulate('click'); expect(defaultProps.onGroupByChange).toHaveBeenCalledTimes(1); expect(defaultProps.onGroupByChange.mock.calls[0][0]).toEqual(GroupByOption.message); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/group_by_filter.tsx similarity index 90% rename from x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/group_by_filter.tsx index a80fe664ced2..d6a3cab9ba16 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/group_by_bar.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/group_by_filter.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiFilterButton, EuiFilterGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { GroupByOption } from '../types'; +import { GroupByOption } from '../../types'; const LocalizedOptions: { [option: string]: string } = { message: i18n.translate('xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIssueLabel', { @@ -21,13 +21,13 @@ const LocalizedOptions: { [option: string]: string } = { }), }; -interface GroupByBarProps { +interface GroupByFilterProps { availableGroupByOptions: GroupByOption[]; currentGroupBy: GroupByOption; onGroupByChange: (groupBy: GroupByOption) => void; } -export const GroupByBar: React.FunctionComponent = ({ +export const GroupByFilter: React.FunctionComponent = ({ availableGroupByOptions, currentGroupBy, onGroupByChange, diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/index.ts new file mode 100644 index 000000000000..31ad78cf572f --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { SearchBar } from './search_bar'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/level_filter.test.tsx similarity index 57% rename from x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx rename to x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/level_filter.test.tsx index 4888efda97bd..c778e56e8df1 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/level_filter.test.tsx @@ -7,29 +7,28 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { DeprecationInfo } from '../../../../common/types'; +import { LevelFilterOption } from '../../types'; -import { LevelFilterOption } from '../types'; -import { FilterBar } from './filter_bar'; +import { DeprecationLevelFilter } from './level_filter'; const defaultProps = { - allDeprecations: [ - { level: LevelFilterOption.critical }, - { level: LevelFilterOption.critical }, - ] as DeprecationInfo[], - currentFilter: LevelFilterOption.all, + levelsCount: { + warning: 4, + critical: 1, + }, + currentFilter: 'all' as LevelFilterOption, onFilterChange: jest.fn(), }; -describe('FilterBar', () => { +describe('DeprecationLevelFilter', () => { test('renders', () => { - expect(shallow()).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); }); test('clicking button calls onFilterChange', () => { - const wrapper = mount(); + const wrapper = mount(); wrapper.find('button[data-test-subj="criticalLevelFilter"]').simulate('click'); expect(defaultProps.onFilterChange).toHaveBeenCalledTimes(1); - expect(defaultProps.onFilterChange.mock.calls[0][0]).toEqual(LevelFilterOption.critical); + expect(defaultProps.onFilterChange.mock.calls[0][0]).toEqual('critical'); }); }); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/level_filter.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/level_filter.tsx new file mode 100644 index 000000000000..108087e2ae99 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/level_filter.tsx @@ -0,0 +1,57 @@ +/* + * 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 { EuiFilterButton, EuiFilterGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { LevelFilterOption } from '../../types'; + +const LocalizedOptions: { [option: string]: string } = { + warning: i18n.translate( + 'xpack.upgradeAssistant.checkupTab.controls.filterBar.warningButtonLabel', + { + defaultMessage: 'warning', + } + ), + critical: i18n.translate( + 'xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel', + { defaultMessage: 'critical' } + ), +}; +interface DeprecationLevelProps { + levelsCount: { + [key: string]: number; + }; + currentFilter: LevelFilterOption; + onFilterChange(level: LevelFilterOption): void; +} + +export const DeprecationLevelFilter: React.FunctionComponent = ({ + levelsCount, + currentFilter, + onFilterChange, +}) => { + return ( + + + { + onFilterChange(currentFilter !== 'critical' ? 'critical' : 'all'); + }} + hasActiveFilters={currentFilter === 'critical'} + numFilters={levelsCount.critical || undefined} + data-test-subj="criticalLevelFilter" + > + {LocalizedOptions.critical} + + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/search_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/search_bar.tsx new file mode 100644 index 000000000000..7c805398a6b4 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/shared/search_bar/search_bar.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiButton, + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiCallOut, + EuiSpacer, +} from '@elastic/eui'; + +import type { DomainDeprecationDetails } from 'kibana/public'; +import { DeprecationInfo } from '../../../../../common/types'; +import { validateRegExpString } from '../../../lib/utils'; +import { GroupByOption, LevelFilterOption } from '../../types'; +import { DeprecationLevelFilter } from './level_filter'; +import { GroupByFilter } from './group_by_filter'; + +interface SearchBarProps { + allDeprecations?: DeprecationInfo[] | DomainDeprecationDetails; + isLoading: boolean; + loadData: () => void; + currentFilter: LevelFilterOption; + onFilterChange: (filter: LevelFilterOption) => void; + onSearchChange: (filter: string) => void; + totalDeprecationsCount: number; + levelToDeprecationCountMap: { + [key: string]: number; + }; + groupByFilterProps?: { + availableGroupByOptions: GroupByOption[]; + currentGroupBy: GroupByOption; + onGroupByChange: (groupBy: GroupByOption) => void; + }; +} + +const i18nTexts = { + searchAriaLabel: i18n.translate( + 'xpack.upgradeAssistant.deprecationListSearchBar.placeholderAriaLabel', + { defaultMessage: 'Filter' } + ), + searchPlaceholderLabel: i18n.translate( + 'xpack.upgradeAssistant.deprecationListSearchBar.placeholderLabel', + { + defaultMessage: 'Filter', + } + ), + reloadButtonLabel: i18n.translate( + 'xpack.upgradeAssistant.deprecationListSearchBar.reloadButtonLabel', + { + defaultMessage: 'Reload', + } + ), + getInvalidSearchMessage: (searchTermError: string) => + i18n.translate('xpack.upgradeAssistant.deprecationListSearchBar.filterErrorMessageLabel', { + defaultMessage: 'Filter invalid: {searchTermError}', + values: { searchTermError }, + }), +}; + +export const SearchBar: FunctionComponent = ({ + totalDeprecationsCount, + levelToDeprecationCountMap, + isLoading, + loadData, + currentFilter, + onFilterChange, + onSearchChange, + groupByFilterProps, +}) => { + const [searchTermError, setSearchTermError] = useState(null); + const filterInvalid = Boolean(searchTermError); + return ( + <> + + + + + { + const string = e.target.value; + const errorMessage = validateRegExpString(string); + if (errorMessage) { + // Emit an empty search term to listeners if search term is invalid. + onSearchChange(''); + setSearchTermError(errorMessage); + } else { + onSearchChange(e.target.value); + if (searchTermError) { + setSearchTermError(null); + } + } + }} + /> + + + {/* These two components provide their own EuiFlexItem wrappers */} + + {groupByFilterProps && } + + + + + {i18nTexts.reloadButtonLabel} + + + + + {filterInvalid && ( + <> + + + + + )} + + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/types.ts b/x-pack/plugins/upgrade_assistant/public/application/components/types.ts index d82b779110a8..8e2bf20b845a 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/types.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/types.ts @@ -32,11 +32,7 @@ export enum LoadingState { Error, } -export enum LevelFilterOption { - all = 'all', - critical = 'critical', - warning = 'warning', -} +export type LevelFilterOption = 'all' | 'critical'; export enum GroupByOption { message = 'message', diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts index 3f2ee4fa3365..00359988d5e2 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts @@ -18,6 +18,12 @@ const i18nTexts = { esDeprecations: i18n.translate('xpack.upgradeAssistant.breadcrumb.esDeprecationsLabel', { defaultMessage: 'Elasticsearch deprecations', }), + kibanaDeprecations: i18n.translate( + 'xpack.upgradeAssistant.breadcrumb.kibanaDeprecationsLabel', + { + defaultMessage: 'Kibana deprecations', + } + ), }, }; @@ -42,6 +48,15 @@ export class BreadcrumbService { text: i18nTexts.breadcrumbs.esDeprecations, }, ], + kibanaDeprecations: [ + { + text: i18nTexts.breadcrumbs.overview, + href: '/', + }, + { + text: i18nTexts.breadcrumbs.kibanaDeprecations, + }, + ], }; private setBreadcrumbsHandler?: SetBreadcrumbs; @@ -50,7 +65,7 @@ export class BreadcrumbService { this.setBreadcrumbsHandler = setBreadcrumbsHandler; } - public setBreadcrumbs(type: 'overview' | 'esDeprecations'): void { + public setBreadcrumbs(type: 'overview' | 'esDeprecations' | 'kibanaDeprecations'): void { if (!this.setBreadcrumbsHandler) { throw new Error('Breadcrumb service has not been initialized'); } diff --git a/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts b/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts index 575c85bb33ec..b17c1301f83f 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts @@ -19,7 +19,10 @@ export async function mountManagementSection( params: ManagementAppMountParams, kibanaVersionInfo: KibanaVersionContext ) { - const [{ i18n, docLinks, notifications, application }] = await coreSetup.getStartServices(); + const [ + { i18n, docLinks, notifications, application, deprecations }, + ] = await coreSetup.getStartServices(); + const { element, history, setBreadcrumbs } = params; const { http } = coreSetup; @@ -39,5 +42,6 @@ export async function mountManagementSection( api: apiService, breadcrumbs: breadcrumbService, getUrlForApp: application.getUrlForApp, + deprecations, }); } diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts index 05db5ebdaa54..a911c5810dd0 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.test.ts @@ -24,24 +24,30 @@ describe('Upgrade Assistant Telemetry SavedObject UIOpen', () => { overview: true, cluster: true, indices: true, + kibana: true, savedObjects: { createInternalRepository: () => internalRepo } as any, }); - expect(internalRepo.incrementCounter).toHaveBeenCalledTimes(3); + expect(internalRepo.incrementCounter).toHaveBeenCalledTimes(4); expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, - [`ui_open.overview`] + ['ui_open.overview'] ); expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, - [`ui_open.cluster`] + ['ui_open.cluster'] ); expect(internalRepo.incrementCounter).toHaveBeenCalledWith( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID, - [`ui_open.indices`] + ['ui_open.indices'] + ); + expect(internalRepo.incrementCounter).toHaveBeenCalledWith( + UPGRADE_ASSISTANT_TYPE, + UPGRADE_ASSISTANT_DOC_ID, + ['ui_open.kibana'] ); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts index 19f4641b2136..ab876828a343 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/es_ui_open_apis.ts @@ -36,6 +36,7 @@ export async function upsertUIOpenOption({ cluster, indices, savedObjects, + kibana, }: UpsertUIOpenOptionDependencies): Promise { if (overview) { await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'overview' }); @@ -49,9 +50,14 @@ export async function upsertUIOpenOption({ await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'indices' }); } + if (kibana) { + await incrementUIOpenOptionCounter({ savedObjects, uiOpenOptionCounter: 'kibana' }); + } + return { overview, cluster, indices, + kibana, }; } diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts index 46208a6a2c7b..30195f6652fb 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts @@ -51,6 +51,7 @@ describe('Upgrade Assistant Usage Collector', () => { 'ui_open.overview': 10, 'ui_open.cluster': 20, 'ui_open.indices': 30, + 'ui_open.kibana': 15, 'ui_reindex.close': 1, 'ui_reindex.open': 4, 'ui_reindex.start': 2, @@ -90,6 +91,7 @@ describe('Upgrade Assistant Usage Collector', () => { overview: 10, cluster: 20, indices: 30, + kibana: 15, }, ui_reindex: { close: 1, diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts index 9d4889bb7bce..564cd69c042b 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts @@ -73,6 +73,7 @@ export async function fetchUpgradeAssistantMetrics( overview: 0, cluster: 0, indices: 0, + kibana: 0, }, ui_reindex: { close: 0, @@ -91,6 +92,7 @@ export async function fetchUpgradeAssistantMetrics( overview: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.overview', 0), cluster: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.cluster', 0), indices: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.indices', 0), + kibana: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.kibana', 0), }, ui_reindex: { close: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_reindex.close', 0), @@ -129,13 +131,41 @@ export function registerUpgradeAssistantUsageCollector({ schema: { features: { deprecation_logging: { - enabled: { type: 'boolean' }, + enabled: { + type: 'boolean', + _meta: { + description: 'Whether user has enabled Elasticsearch deprecation logging', + }, + }, }, }, ui_open: { - cluster: { type: 'long' }, - indices: { type: 'long' }, - overview: { type: 'long' }, + cluster: { + 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.', + }, + }, + overview: { + type: 'long', + _meta: { + description: 'Number of times a user viewed the Overview page.', + }, + }, + kibana: { + type: 'long', + _meta: { + description: 'Number of times a user viewed the list of Kibana deprecations', + }, + }, }, ui_reindex: { close: { type: 'long' }, diff --git a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts index 040e54bb9f06..4e9b4b9a472a 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/telemetry.ts @@ -20,17 +20,19 @@ export function registerTelemetryRoutes({ router, getSavedObjectsService }: Rout overview: schema.boolean({ defaultValue: false }), cluster: schema.boolean({ defaultValue: false }), indices: schema.boolean({ defaultValue: false }), + kibana: schema.boolean({ defaultValue: false }), }), }, }, async (ctx, request, response) => { - const { cluster, indices, overview } = request.body; + const { cluster, indices, overview, kibana } = request.body; return response.ok({ body: await upsertUIOpenOption({ savedObjects: getSavedObjectsService(), cluster, indices, overview, + kibana, }), }); } diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts index 4bb690b31824..f76c07da678d 100644 --- a/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts +++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts @@ -29,6 +29,10 @@ export const telemetrySavedObjectType: SavedObjectsType = { type: 'long', null_value: 0, }, + kibana: { + type: 'long', + null_value: 0, + }, }, }, ui_reindex: { diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/http_requests.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/http_requests.ts index 76ed94c7bf68..9abd981bd85c 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/http_requests.ts @@ -12,7 +12,10 @@ import { ResponseError } from '../../public/application/lib/api'; // Register helpers to mock HTTP Requests const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { - const setLoadStatusResponse = (response?: UpgradeAssistantStatus, error?: ResponseError) => { + const setLoadEsDeprecationsResponse = ( + response?: UpgradeAssistantStatus, + error?: ResponseError + ) => { const status = error ? error.statusCode || 400 : 200; const body = error ? error : response; @@ -60,7 +63,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { }; return { - setLoadStatusResponse, + setLoadEsDeprecationsResponse, setLoadDeprecationLoggingResponse, setUpdateDeprecationLoggingResponse, setUpdateIndexSettingsResponse, diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/index.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/index.ts index 74aa173866b7..ddf5787af103 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/index.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/index.ts @@ -7,5 +7,6 @@ export { setup as setupOverviewPage, OverviewTestBed } from './overview.helpers'; export { setup as setupIndicesPage, IndicesTestBed } from './indices.helpers'; +export { setup as setupKibanaPage, KibanaTestBed } from './kibana.helpers'; export { setupEnvironment } from './setup_environment'; diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/kibana.helpers.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/kibana.helpers.ts new file mode 100644 index 000000000000..0a800771e265 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/kibana.helpers.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { KibanaDeprecationsContent } from '../../public/application/components/kibana_deprecations'; +import { WithAppDependencies } from './setup_environment'; + +const testBedConfig: TestBedConfig = { + memoryRouter: { + initialEntries: ['/kibana_deprecations'], + componentRoutePath: '/kibana_deprecations', + }, + doMountAsync: true, +}; + +export type KibanaTestBed = TestBed & { + actions: ReturnType; +}; + +const createActions = (testBed: TestBed) => { + /** + * User Actions + */ + + const clickExpandAll = () => { + const { find } = testBed; + find('expandAll').simulate('click'); + }; + + return { + clickExpandAll, + }; +}; + +export const setup = async (overrides?: Record): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(KibanaDeprecationsContent, overrides), + testBedConfig + ); + const testBed = await initTestBed(); + + return { + ...testBed, + actions: createActions(testBed), + }; +}; + +export type KibanaTestSubjects = + | 'expandAll' + | 'noDeprecationsPrompt' + | 'kibanaPluginError' + | 'kibanaDeprecationsContent' + | 'kibanaDeprecationItem' + | 'kibanaRequestError' + | string; diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts index 161364f6d45c..52346c94ef46 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts @@ -34,6 +34,9 @@ export type OverviewTestSubjects = | 'esStatsPanel' | 'esStatsPanel.totalDeprecations' | 'esStatsPanel.criticalDeprecations' + | 'kibanaStatsPanel' + | 'kibanaStatsPanel.totalDeprecations' + | 'kibanaStatsPanel.criticalDeprecations' | 'deprecationLoggingFormRow' | 'requestErrorIconTip' | 'partiallyUpgradedErrorIconTip' diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/setup_environment.tsx b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/setup_environment.tsx index 7ee6114cd86a..9ea5c15e9d03 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/setup_environment.tsx @@ -10,7 +10,11 @@ import axios from 'axios'; // @ts-ignore import axiosXhrAdapter from 'axios/lib/adapters/xhr'; -import { docLinksServiceMock, notificationServiceMock } from '../../../../../src/core/public/mocks'; +import { + deprecationsServiceMock, + docLinksServiceMock, + notificationServiceMock, +} from '../../../../../src/core/public/mocks'; import { HttpSetup } from '../../../../../src/core/public'; import { mockKibanaSemverVersion, UA_READONLY_MODE } from '../../common/constants'; @@ -41,6 +45,7 @@ export const WithAppDependencies = (Comp: any, overrides: Record '', + deprecations: deprecationsServiceMock.createStartContract(), }; return ( diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/indices.test.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/indices.test.ts index 6363e57903c2..51526698effc 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/indices.test.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/indices.test.ts @@ -35,7 +35,7 @@ describe('Indices tab', () => { }; beforeEach(async () => { - httpRequestsMockHelpers.setLoadStatusResponse(upgradeStatusMockResponse); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(upgradeStatusMockResponse); httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ isEnabled: true }); await act(async () => { @@ -118,7 +118,7 @@ describe('Indices tab', () => { indices: [], }; - httpRequestsMockHelpers.setLoadStatusResponse(noDeprecationsResponse); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(noDeprecationsResponse); await act(async () => { testBed = await setupIndicesPage({ isReadOnlyMode: false }); @@ -144,7 +144,7 @@ describe('Indices tab', () => { message: 'Forbidden', }; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); await act(async () => { testBed = await setupIndicesPage({ isReadOnlyMode: false }); @@ -170,7 +170,7 @@ describe('Indices tab', () => { }, }; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); await act(async () => { testBed = await setupIndicesPage({ isReadOnlyMode: false }); @@ -196,7 +196,7 @@ describe('Indices tab', () => { }, }; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); await act(async () => { testBed = await setupIndicesPage({ isReadOnlyMode: false }); @@ -219,7 +219,7 @@ describe('Indices tab', () => { message: 'Internal server error', }; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); await act(async () => { testBed = await setupIndicesPage({ isReadOnlyMode: false }); diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/kibana.test.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/kibana.test.ts new file mode 100644 index 000000000000..fef0fedf4cce --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/kibana.test.ts @@ -0,0 +1,230 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DomainDeprecationDetails } from 'kibana/public'; +import { act } from 'react-dom/test-utils'; +import { deprecationsServiceMock } from 'src/core/public/mocks'; + +import { KibanaTestBed, setupKibanaPage, setupEnvironment } from './helpers'; + +describe('Kibana deprecations', () => { + let testBed: KibanaTestBed; + const { server } = setupEnvironment(); + + afterAll(() => { + server.restore(); + }); + + describe('With deprecations', () => { + const kibanaDeprecationsMockResponse: DomainDeprecationDetails[] = [ + { + correctiveActions: { + manualSteps: ['Step 1', 'Step 2', 'Step 3'], + api: { + method: 'POST', + path: '/test', + }, + }, + domainId: 'test_domain', + level: 'critical', + message: 'Test deprecation message', + }, + ]; + + beforeEach(async () => { + await act(async () => { + const deprecationService = deprecationsServiceMock.createStartContract(); + deprecationService.getAllDeprecations = jest + .fn() + .mockReturnValue(kibanaDeprecationsMockResponse); + + testBed = await setupKibanaPage({ + deprecations: deprecationService, + }); + }); + + testBed.component.update(); + }); + + test('renders deprecations', () => { + const { exists, find } = testBed; + expect(exists('kibanaDeprecationsContent')).toBe(true); + expect(find('kibanaDeprecationItem').length).toEqual(1); + }); + + describe('manual steps modal', () => { + test('renders modal with a list of steps to fix a deprecation', async () => { + const { component, actions, exists, find } = testBed; + const deprecation = kibanaDeprecationsMockResponse[0]; + + expect(exists('kibanaDeprecationsContent')).toBe(true); + + // Open all deprecations + actions.clickExpandAll(); + + const accordionTestSubj = `${deprecation.domainId}Deprecation`; + + await act(async () => { + find(`${accordionTestSubj}.stepsButton`).simulate('click'); + }); + + component.update(); + + // We need to read the document "body" as the modal is added there and not inside + // the component DOM tree. + let modal = document.body.querySelector('[data-test-subj="stepsModal"]'); + + expect(modal).not.toBe(null); + expect(modal!.textContent).toContain(`Fix '${deprecation.domainId}'`); + + const steps: NodeListOf | null = modal!.querySelectorAll( + '[data-test-subj="fixDeprecationSteps"] .euiStep' + ); + + expect(steps).not.toBe(null); + expect(steps.length).toEqual(deprecation!.correctiveActions!.manualSteps!.length); + + await act(async () => { + const closeButton: HTMLButtonElement | null = modal!.querySelector( + '[data-test-subj="closeButton"]' + ); + + closeButton!.click(); + }); + + component.update(); + + // Confirm modal closed and no longer appears in the DOM + modal = document.body.querySelector('[data-test-subj="stepsModal"]'); + expect(modal).toBe(null); + }); + }); + + describe('resolve modal', () => { + test('renders confirmation modal to resolve a deprecation', async () => { + const { component, actions, exists, find } = testBed; + const deprecation = kibanaDeprecationsMockResponse[0]; + + expect(exists('kibanaDeprecationsContent')).toBe(true); + + // Open all deprecations + actions.clickExpandAll(); + + const accordionTestSubj = `${deprecation.domainId}Deprecation`; + + await act(async () => { + find(`${accordionTestSubj}.resolveButton`).simulate('click'); + }); + + component.update(); + + // We need to read the document "body" as the modal is added there and not inside + // the component DOM tree. + let modal = document.body.querySelector('[data-test-subj="resolveModal"]'); + + expect(modal).not.toBe(null); + expect(modal!.textContent).toContain(`Resolve '${deprecation.domainId}'`); + + const confirmButton: HTMLButtonElement | null = modal!.querySelector( + '[data-test-subj="confirmModalConfirmButton"]' + ); + + await act(async () => { + confirmButton!.click(); + }); + + component.update(); + + // Confirm modal should close and no longer appears in the DOM + modal = document.body.querySelector('[data-test-subj="resolveModal"]'); + expect(modal).toBe(null); + }); + }); + }); + + describe('No deprecations', () => { + beforeEach(async () => { + await act(async () => { + testBed = await setupKibanaPage({ 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 request error', async () => { + await act(async () => { + const deprecationService = deprecationsServiceMock.createStartContract(); + deprecationService.getAllDeprecations = jest + .fn() + .mockRejectedValue(new Error('Internal Server Error')); + + testBed = await setupKibanaPage({ + deprecations: deprecationService, + }); + }); + + const { component, exists, find } = testBed; + + component.update(); + + expect(exists('kibanaRequestError')).toBe(true); + expect(find('kibanaRequestError').text()).toContain( + 'Could not retrieve Kibana deprecations.' + ); + }); + + test('handles deprecation service error', async () => { + const domainId = 'test'; + const kibanaDeprecationsMockResponse: DomainDeprecationDetails[] = [ + { + domainId, + message: `Failed to get deprecations info for plugin "${domainId}".`, + level: 'fetch_error', + correctiveActions: { + manualSteps: ['Check Kibana server logs for error message.'], + }, + }, + ]; + + await act(async () => { + const deprecationService = deprecationsServiceMock.createStartContract(); + deprecationService.getAllDeprecations = jest + .fn() + .mockReturnValue(kibanaDeprecationsMockResponse); + + testBed = await setupKibanaPage({ + deprecations: deprecationService, + }); + }); + + const { component, exists, find, actions } = testBed; + component.update(); + + // Verify top-level callout renders + expect(exists('kibanaPluginError')).toBe(true); + expect(find('kibanaPluginError').text()).toContain( + 'Not all Kibana deprecations were retrieved successfully.' + ); + + // Open all deprecations + actions.clickExpandAll(); + + // Verify callout also displays for deprecation with error + expect(exists(`${domainId}Error`)).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/overview.test.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/overview.test.ts index cdbbd0a36cbd..5459fb494502 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/overview.test.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/overview.test.ts @@ -5,7 +5,10 @@ * 2.0. */ +import type { DomainDeprecationDetails } from 'kibana/public'; import { act } from 'react-dom/test-utils'; +import { deprecationsServiceMock } from 'src/core/public/mocks'; +import { UpgradeAssistantStatus } from '../common/types'; import { OverviewTestBed, setupOverviewPage, setupEnvironment } from './helpers'; @@ -14,17 +17,54 @@ describe('Overview page', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); beforeEach(async () => { - const upgradeStatusMockResponse = { + const esDeprecationsMockResponse: UpgradeAssistantStatus = { readyForUpgrade: false, - cluster: [], - indices: [], + cluster: [ + { + level: 'critical', + message: 'Index Lifecycle Management poll interval is set too low', + url: + 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-8.0.html#ilm-poll-interval-limit', + details: + 'The Index Lifecycle Management poll interval setting [indices.lifecycle.poll_interval] is currently set to [500ms], but must be 1s or greater', + }, + ], + indices: [ + { + level: 'warning', + message: 'translog retention settings are ignored', + url: + 'https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html', + details: + 'translog retention settings [index.translog.retention.size] and [index.translog.retention.age] are ignored because translog is no longer used in peer recoveries with soft-deletes enabled (default in 7.0 or later)', + index: 'settings', + reindex: false, + }, + ], }; - httpRequestsMockHelpers.setLoadStatusResponse(upgradeStatusMockResponse); + const kibanaDeprecationsMockResponse: DomainDeprecationDetails[] = [ + { + correctiveActions: {}, + domainId: 'xpack.spaces', + level: 'critical', + message: + 'Disabling the spaces plugin (xpack.spaces.enabled) will not be supported in the next major version (8.0)', + }, + ]; + + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse); httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ isEnabled: true }); await act(async () => { - testBed = await setupOverviewPage(); + const deprecationService = deprecationsServiceMock.createStartContract(); + deprecationService.getAllDeprecations = jest + .fn() + .mockReturnValue(kibanaDeprecationsMockResponse); + + testBed = await setupOverviewPage({ + deprecations: deprecationService, + }); }); const { component } = testBed; @@ -39,10 +79,16 @@ describe('Overview page', () => { const { exists, find } = testBed; expect(exists('overviewPageContent')).toBe(true); + // Verify ES stats expect(exists('esStatsPanel')).toBe(true); - expect(find('esStatsPanel.totalDeprecations').text()).toContain('0'); - expect(find('esStatsPanel.criticalDeprecations').text()).toContain('0'); + expect(find('esStatsPanel.totalDeprecations').text()).toContain('2'); + expect(find('esStatsPanel.criticalDeprecations').text()).toContain('1'); + + // Verify Kibana stats + expect(exists('kibanaStatsPanel')).toBe(true); + expect(find('kibanaStatsPanel.totalDeprecations').text()).toContain('1'); + expect(find('kibanaStatsPanel.criticalDeprecations').text()).toContain('1'); }); describe('Deprecation logging', () => { @@ -96,90 +142,113 @@ describe('Overview page', () => { }); describe('Error handling', () => { - test('handles network failure', async () => { - const error = { - statusCode: 500, - error: 'Internal server error', - message: 'Internal server error', - }; + describe('Kibana deprecations', () => { + test('handles network failure', async () => { + await act(async () => { + const deprecationService = deprecationsServiceMock.createStartContract(); + deprecationService.getAllDeprecations = jest + .fn() + .mockRejectedValue(new Error('Internal Server Error')); - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + testBed = await setupOverviewPage({ + deprecations: deprecationService, + }); + }); - await act(async () => { - testBed = await setupOverviewPage(); + const { component, exists } = testBed; + + component.update(); + + expect(exists('requestErrorIconTip')).toBe(true); }); - - const { component, exists } = testBed; - - component.update(); - - expect(exists('requestErrorIconTip')).toBe(true); }); - test('handles unauthorized error', async () => { - const error = { - statusCode: 403, - error: 'Forbidden', - message: 'Forbidden', - }; + describe('Elasticsearch deprecations', () => { + test('handles network failure', async () => { + const error = { + statusCode: 500, + error: 'Internal server error', + message: 'Internal server error', + }; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); - await act(async () => { - testBed = await setupOverviewPage(); + await act(async () => { + testBed = await setupOverviewPage(); + }); + + const { component, exists } = testBed; + + component.update(); + + expect(exists('requestErrorIconTip')).toBe(true); }); - const { component, exists } = testBed; + test('handles unauthorized error', async () => { + const error = { + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden', + }; - component.update(); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); - expect(exists('unauthorizedErrorIconTip')).toBe(true); - }); + await act(async () => { + testBed = await setupOverviewPage(); + }); - test('handles partially upgraded error', async () => { - const error = { - statusCode: 426, - error: 'Upgrade required', - message: 'There are some nodes running a different version of Elasticsearch', - attributes: { - allNodesUpgraded: false, - }, - }; + const { component, exists } = testBed; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + component.update(); - await act(async () => { - testBed = await setupOverviewPage({ isReadOnlyMode: false }); + expect(exists('unauthorizedErrorIconTip')).toBe(true); }); - const { component, exists } = testBed; + test('handles partially upgraded error', async () => { + const error = { + statusCode: 426, + error: 'Upgrade required', + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + allNodesUpgraded: false, + }, + }; - component.update(); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); - expect(exists('partiallyUpgradedErrorIconTip')).toBe(true); - }); + await act(async () => { + testBed = await setupOverviewPage({ isReadOnlyMode: false }); + }); - 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, - }, - }; + const { component, exists } = testBed; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + component.update(); - await act(async () => { - testBed = await setupOverviewPage({ isReadOnlyMode: false }); + expect(exists('partiallyUpgradedErrorIconTip')).toBe(true); }); - const { component, exists } = testBed; + 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, + }, + }; - component.update(); + httpRequestsMockHelpers.setLoadEsDeprecationsResponse(undefined, error); - expect(exists('upgradedErrorIconTip')).toBe(true); + await act(async () => { + testBed = await setupOverviewPage({ isReadOnlyMode: false }); + }); + + const { component, exists } = testBed; + + component.update(); + + expect(exists('upgradedErrorIconTip')).toBe(true); + }); }); }); }); diff --git a/x-pack/test/accessibility/apps/upgrade_assistant.ts b/x-pack/test/accessibility/apps/upgrade_assistant.ts index 8d2774c000b2..c96b21ba2182 100644 --- a/x-pack/test/accessibility/apps/upgrade_assistant.ts +++ b/x-pack/test/accessibility/apps/upgrade_assistant.ts @@ -34,19 +34,57 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await a11y.testAppSnapshot(); }); - it('Elasticsearch cluster tab', async () => { - await testSubjects.click('esDeprecationsLink'); - await retry.waitFor('Upgrade Assistant Cluster tab to be visible', async () => { + it('Elasticsearch cluster deprecations', async () => { + await PageObjects.common.navigateToUrl( + 'management', + 'stack/upgrade_assistant/es_deprecations/cluster', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + } + ); + + await retry.waitFor('Cluster tab to be visible', async () => { return testSubjects.exists('clusterTabContent'); }); + await a11y.testAppSnapshot(); }); - it('Elasticsearch indices tab', async () => { - await testSubjects.click('upgradeAssistantIndicesTab'); - await retry.waitFor('Upgrade Assistant Indices tab to be visible', async () => { + it('Elasticsearch index deprecations', async () => { + await PageObjects.common.navigateToUrl( + 'management', + 'stack/upgrade_assistant/es_deprecations/indices', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + } + ); + + await retry.waitFor('Indices tab to be visible', async () => { return testSubjects.exists('indexTabContent'); }); + + await a11y.testAppSnapshot(); + }); + + it('Kibana deprecations', async () => { + await PageObjects.common.navigateToUrl( + 'management', + 'stack/upgrade_assistant/kibana_deprecations', + { + ensureCurrentUrl: false, + shouldLoginIfPrompted: false, + shouldUseHashForSubUrl: false, + } + ); + + await retry.waitFor('Kibana deprecations to be visible', async () => { + return testSubjects.exists('kibanaDeprecationsContent'); + }); + await a11y.testAppSnapshot(); }); });