[Upgrade Assistant] Support Kibana deprecations (#97159)
This commit is contained in:
parent
36ee2a7582
commit
577948bea3
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -22114,15 +22114,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": "ドキュメント",
|
||||
|
@ -22131,9 +22125,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": "閉じる",
|
||||
|
|
|
@ -22466,15 +22466,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": "文档",
|
||||
|
@ -22484,9 +22478,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": "关闭",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = () => {
|
|||
<Switch>
|
||||
<Route exact path="/overview" component={DeprecationsOverview} />
|
||||
<Route exact path="/es_deprecations/:tabName" component={EsDeprecationsContent} />
|
||||
<Route exact path="/kibana_deprecations" component={KibanaDeprecationsContent} />
|
||||
<Redirect from="/" to="/overview" />
|
||||
</Switch>
|
||||
);
|
||||
|
|
|
@ -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<ContextValue>({} as any);
|
||||
|
|
|
@ -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;
|
|
@ -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<CheckupControlsProps> = ({
|
||||
allDeprecations,
|
||||
isLoading,
|
||||
loadData,
|
||||
currentFilter,
|
||||
onFilterChange,
|
||||
onSearchChange,
|
||||
availableGroupByOptions,
|
||||
currentGroupBy,
|
||||
onGroupByChange,
|
||||
}) => {
|
||||
const [searchTermError, setSearchTermError] = useState<null | string>(null);
|
||||
const filterInvalid = Boolean(searchTermError);
|
||||
return (
|
||||
<EuiFlexGroup direction="column" responsive={false}>
|
||||
<EuiFlexItem grow={true}>
|
||||
<EuiFlexGroup alignItems="center" wrap={true} responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiFieldSearch
|
||||
isInvalid={filterInvalid}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.upgradeAssistant.checkupTab.controls.searchBarPlaceholderAriaLabel',
|
||||
{ defaultMessage: 'Filter' }
|
||||
)}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.upgradeAssistant.checkupTab.controls.searchBarPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Filter',
|
||||
}
|
||||
)}
|
||||
onChange={(e) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* These two components provide their own EuiFlexItem wrappers */}
|
||||
<FilterBar {...{ allDeprecations, currentFilter, onFilterChange }} />
|
||||
<GroupByBar {...{ availableGroupByOptions, currentGroupBy, onGroupByChange }} />
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill onClick={loadData} iconType="refresh" isLoading={isLoading}>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.controls.refreshButtonLabel"
|
||||
defaultMessage="Refresh"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
{filterInvalid && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
title={i18n.translate(
|
||||
'xpack.upgradeAssistant.checkupTab.controls.filterErrorMessageLabel',
|
||||
{
|
||||
defaultMessage: 'Filter invalid: {searchTermError}',
|
||||
values: { searchTermError },
|
||||
}
|
||||
)}
|
||||
iconType="faceSad"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -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<CheckupTabProps> = ({
|
|||
refreshCheckupData,
|
||||
navigateToOverviewPage,
|
||||
}) => {
|
||||
const [currentFilter, setCurrentFilter] = useState<LevelFilterOption>(LevelFilterOption.all);
|
||||
const [currentFilter, setCurrentFilter] = useState<LevelFilterOption>('all');
|
||||
const [search, setSearch] = useState<string>('');
|
||||
const [currentGroupBy, setCurrentGroupBy] = useState<GroupByOption>(GroupByOption.message);
|
||||
const [expandState, setExpandState] = useState({
|
||||
forceExpand: false,
|
||||
expandNumber: 0,
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
|
||||
const availableGroupByOptions = () => {
|
||||
const getAvailableGroupByOptions = () => {
|
||||
if (!deprecations) {
|
||||
return [];
|
||||
}
|
||||
|
@ -52,46 +111,28 @@ export const DeprecationTabContent: FunctionComponent<CheckupTabProps> = ({
|
|||
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 (
|
||||
<EuiEmptyPrompt
|
||||
iconType="faceHappy"
|
||||
data-test-subj="noDeprecationsPrompt"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.noIssues.noIssuesTitle"
|
||||
defaultMessage="Ready to upgrade!"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<>
|
||||
<p data-test-subj="upgradeAssistantIssueSummary">
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.noIssues.noIssuesLabel"
|
||||
defaultMessage="Your configuration is up to date."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail"
|
||||
defaultMessage="Check the {overviewTabButton} for other Stack deprecations."
|
||||
values={{
|
||||
overviewTabButton: (
|
||||
<EuiLink onClick={navigateToOverviewPage}>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail.overviewTabButtonLabel"
|
||||
defaultMessage="Overview page"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div data-test-subj={`${checkupLabel}TabContent`}>
|
||||
<NoDeprecationsPrompt
|
||||
deprecationType={checkupLabel}
|
||||
navigateToOverviewPage={navigateToOverviewPage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -100,28 +141,77 @@ export const DeprecationTabContent: FunctionComponent<CheckupTabProps> = ({
|
|||
if (isLoading) {
|
||||
content = <SectionLoading>{i18nTexts.isLoading}</SectionLoading>;
|
||||
} else if (deprecations?.length) {
|
||||
const levelGroups = groupBy(deprecations, 'level');
|
||||
const levelToDeprecationCountMap = Object.keys(levelGroups).reduce((counts, level) => {
|
||||
counts[level] = levelGroups[level].length;
|
||||
return counts;
|
||||
}, {} as Record<string, number>);
|
||||
|
||||
const filteredDeprecations = filterDeprecations(deprecations, currentFilter, search);
|
||||
|
||||
const groups = groupDeprecations(deprecations, currentFilter, search, currentGroupBy);
|
||||
|
||||
content = (
|
||||
<div data-test-subj="deprecationsContainer">
|
||||
<CheckupControls
|
||||
<SearchBar
|
||||
allDeprecations={deprecations}
|
||||
isLoading={isLoading}
|
||||
loadData={refreshCheckupData}
|
||||
currentFilter={currentFilter}
|
||||
onFilterChange={setCurrentFilter}
|
||||
onSearchChange={setSearch}
|
||||
availableGroupByOptions={availableGroupByOptions()}
|
||||
currentGroupBy={currentGroupBy}
|
||||
onGroupByChange={setCurrentGroupBy}
|
||||
totalDeprecationsCount={deprecations.length}
|
||||
levelToDeprecationCountMap={levelToDeprecationCountMap}
|
||||
groupByFilterProps={{
|
||||
availableGroupByOptions: getAvailableGroupByOptions(),
|
||||
currentGroupBy,
|
||||
onGroupByChange: setCurrentGroupBy,
|
||||
}}
|
||||
/>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<GroupedDeprecations
|
||||
currentGroupBy={currentGroupBy}
|
||||
currentFilter={currentFilter}
|
||||
search={search}
|
||||
allDeprecations={deprecations}
|
||||
<DeprecationListBar
|
||||
allDeprecationsCount={deprecations.length}
|
||||
filteredDeprecationsCount={filteredDeprecations.length}
|
||||
setExpandAll={setExpandAll}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
||||
<>
|
||||
{Object.keys(groups)
|
||||
.sort()
|
||||
// Apply pagination
|
||||
.slice(currentPage * DEPRECATIONS_PER_PAGE, (currentPage + 1) * DEPRECATIONS_PER_PAGE)
|
||||
.map((groupName, index) => [
|
||||
<div key={`es-deprecation-${index}`}>
|
||||
<EsDeprecationAccordion
|
||||
{...{
|
||||
key: expandState.expandNumber,
|
||||
id: `depgroup-${groupName}`,
|
||||
dataTestSubj: `depgroup_${groupName.split(' ').join('_')}`,
|
||||
title: groupName,
|
||||
deprecations: groups[groupName],
|
||||
currentGroupBy,
|
||||
forceExpand: expandState.forceExpand,
|
||||
}}
|
||||
/>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
</div>,
|
||||
])}
|
||||
|
||||
{/* Only show pagination if we have more than DEPRECATIONS_PER_PAGE. */}
|
||||
{Object.keys(groups).length > DEPRECATIONS_PER_PAGE && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<DeprecationPagination
|
||||
pageCount={getPageCount(deprecations, currentFilter, search, currentGroupBy)}
|
||||
activePage={currentPage}
|
||||
setPage={setCurrentPage}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
} else if (error) {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,3 +1,2 @@
|
|||
@import 'cell';
|
||||
@import 'deprecations';
|
||||
@import 'reindex/index';
|
||||
|
|
|
@ -61,26 +61,25 @@ export const DeprecationCell: FunctionComponent<DeprecationCellProps> = ({
|
|||
</EuiTitle>
|
||||
)}
|
||||
|
||||
{items.map((item, index) => (
|
||||
<EuiText key={`deprecation-item-${index}`}>
|
||||
{item.title && <h6>{item.title}</h6>}
|
||||
<p>{item.body}</p>
|
||||
</EuiText>
|
||||
))}
|
||||
|
||||
{docUrl && (
|
||||
<div>
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiLink href={docUrl} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.deprecations.documentationButtonLabel"
|
||||
defaultMessage="Documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
<EuiSpacer size="s" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{items.map((item) => (
|
||||
<div key={item.title || item.body}>
|
||||
<EuiText>
|
||||
{item.title && <h6>{item.title}</h6>}
|
||||
<p>{item.body}</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
))}
|
||||
</EuiFlexItem>
|
||||
|
||||
{reindexIndexName && (
|
||||
|
|
|
@ -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<Props> = ({
|
||||
id,
|
||||
deprecations,
|
||||
title,
|
||||
currentGroupBy,
|
||||
forceExpand,
|
||||
dataTestSubj,
|
||||
}) => {
|
||||
const hasIndices = Boolean(
|
||||
currentGroupBy === GroupByOption.message &&
|
||||
(deprecations as EnrichedDeprecationInfo[]).filter((d) => d.index).length
|
||||
);
|
||||
const numIndices = hasIndices ? deprecations.length : null;
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
id={id}
|
||||
key={id}
|
||||
data-test-subj={dataTestSubj}
|
||||
initialIsOpen={forceExpand}
|
||||
buttonContent={title}
|
||||
extraAction={
|
||||
<div>
|
||||
{hasIndices && (
|
||||
<>
|
||||
<EuiBadge color="hollow">
|
||||
<span data-test-subj="indexCount">{numIndices}</span>{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.indicesBadgeLabel"
|
||||
defaultMessage="{numIndices, plural, one {index} other {indices}}"
|
||||
values={{ numIndices }}
|
||||
/>
|
||||
</EuiBadge>
|
||||
 
|
||||
</>
|
||||
)}
|
||||
<DeprecationHealth
|
||||
single={currentGroupBy === GroupByOption.message}
|
||||
deprecationLevels={deprecations.map((d) => LEVEL_MAP[d.level])}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<EsDeprecationList deprecations={deprecations} currentGroupBy={currentGroupBy} />
|
||||
</EuiAccordion>
|
||||
);
|
||||
};
|
|
@ -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(<GroupedDeprecations {...defaultProps} />);
|
||||
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(<GroupedDeprecations {...defaultProps} />);
|
||||
|
||||
// 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(<GroupedDeprecations {...paginationProps} />);
|
||||
expect(wrapper.find(DeprecationAccordion)).toHaveLength(25);
|
||||
});
|
||||
|
||||
test('it displays pagination', () => {
|
||||
const wrapper = shallowWithIntl(<GroupedDeprecations {...paginationProps} />);
|
||||
expect(wrapper.find(EuiPagination).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('shows next page on click', () => {
|
||||
const wrapper = mountWithIntl(<GroupedDeprecations {...paginationProps} />);
|
||||
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(
|
||||
<GroupedDeprecations
|
||||
{...defaultProps}
|
||||
currentGroupBy={GroupByOption.message}
|
||||
allDeprecations={[
|
||||
{ message: 'Cluster error 1', url: '', level: 'warning' },
|
||||
{ message: 'Cluster error 2', url: '', level: 'warning' },
|
||||
{ message: 'Cluster error 2', url: '', level: 'warning' },
|
||||
{ message: 'Cluster error 2', url: '', level: 'warning' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
// 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(
|
||||
<GroupedDeprecations
|
||||
{...defaultProps}
|
||||
currentGroupBy={GroupByOption.index}
|
||||
allDeprecations={[
|
||||
{
|
||||
message: 'Cluster error 1',
|
||||
url: '',
|
||||
level: 'warning',
|
||||
index: 'index1',
|
||||
},
|
||||
{
|
||||
message: 'Cluster error 2',
|
||||
url: '',
|
||||
level: 'warning',
|
||||
index: 'index1',
|
||||
},
|
||||
{
|
||||
message: 'Cluster error 2',
|
||||
url: '',
|
||||
level: 'warning',
|
||||
index: 'index2',
|
||||
},
|
||||
{
|
||||
message: 'Cluster error 2',
|
||||
url: '',
|
||||
level: 'warning',
|
||||
index: 'index3',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
// 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(<DeprecationAccordion {...defaultProps} />);
|
||||
expect(wrapper.find(EuiBadge).find('[data-test-subj="indexCount"]').text()).toEqual('2');
|
||||
});
|
||||
});
|
|
@ -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 (
|
||||
<EuiAccordion
|
||||
id={id}
|
||||
data-test-subj={dataTestSubj}
|
||||
className="upgDeprecations__item"
|
||||
initialIsOpen={forceExpand}
|
||||
buttonContent={<span className="upgDeprecations__itemName">{title}</span>}
|
||||
extraAction={
|
||||
<div>
|
||||
{hasIndices && (
|
||||
<Fragment>
|
||||
<EuiBadge color="hollow">
|
||||
<span data-test-subj="indexCount">{numIndices}</span>{' '}
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.indicesBadgeLabel"
|
||||
defaultMessage="{numIndices, plural, one {index} other {indices}}"
|
||||
values={{ numIndices }}
|
||||
/>
|
||||
</EuiBadge>
|
||||
 
|
||||
</Fragment>
|
||||
)}
|
||||
<DeprecationHealth
|
||||
single={currentGroupBy === GroupByOption.message}
|
||||
deprecations={deprecations}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<DeprecationList deprecations={deprecations} currentGroupBy={currentGroupBy} />
|
||||
</EuiAccordion>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<Fragment>
|
||||
<EuiFlexGroup responsive={false} alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
size="s"
|
||||
onClick={() => this.setExpand(true)}
|
||||
data-test-subj="expandAll"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.controls.expandAllButtonLabel"
|
||||
defaultMessage="Expand all"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
size="s"
|
||||
onClick={() => this.setExpand(false)}
|
||||
data-test-subj="collapseAll"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.controls.collapseAllButtonLabel"
|
||||
defaultMessage="Collapse all"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem />
|
||||
<EuiFlexItem grow={false}>
|
||||
<DeprecationCountSummary
|
||||
allDeprecations={allDeprecations}
|
||||
deprecations={filteredDeprecations}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<div className="upgDeprecations">
|
||||
{Object.keys(groups)
|
||||
.sort()
|
||||
// Apply pagination
|
||||
.slice(currentPage * PER_PAGE, (currentPage + 1) * PER_PAGE)
|
||||
.map((groupName) => [
|
||||
<DeprecationAccordion
|
||||
key={expandNumber}
|
||||
id={`depgroup-${groupName}`}
|
||||
dataTestSubj={`depgroup_${groupName.split(' ').join('_')}`}
|
||||
title={groupName}
|
||||
deprecations={groups[groupName]}
|
||||
{...{ currentGroupBy, forceExpand }}
|
||||
/>,
|
||||
])}
|
||||
|
||||
{/* Only show pagination if we have more than PER_PAGE. */}
|
||||
{Object.keys(groups).length > PER_PAGE && (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPagination
|
||||
pageCount={CalcFields.numPages(this.props)}
|
||||
activePage={currentPage}
|
||||
onPageClick={this.setPage}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
private setExpand = (forceExpand: boolean) => {
|
||||
this.setState({ forceExpand, expandNumber: this.state.expandNumber + 1 });
|
||||
};
|
||||
|
||||
private setPage = (currentPage: number) => this.setState({ currentPage });
|
||||
}
|
|
@ -5,4 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { GroupedDeprecations } from './grouped';
|
||||
export { EsDeprecationAccordion } from './deprecation_group_item';
|
||||
|
|
|
@ -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(<DeprecationList {...defaultProps} />)).toMatchInlineSnapshot(`
|
||||
expect(shallow(<EsDeprecationList {...defaultProps} />)).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<SimpleMessageDeprecation
|
||||
deprecation={
|
||||
|
@ -33,7 +33,7 @@ describe('DeprecationList', () => {
|
|||
"url": "",
|
||||
}
|
||||
}
|
||||
key="Issue 1"
|
||||
key="Issue 1-0"
|
||||
/>
|
||||
<SimpleMessageDeprecation
|
||||
deprecation={
|
||||
|
@ -43,7 +43,7 @@ describe('DeprecationList', () => {
|
|||
"url": "",
|
||||
}
|
||||
}
|
||||
key="Issue 1"
|
||||
key="Issue 1-1"
|
||||
/>
|
||||
</div>
|
||||
`);
|
||||
|
@ -58,7 +58,7 @@ describe('DeprecationList', () => {
|
|||
index: index.toString(),
|
||||
})),
|
||||
};
|
||||
const wrapper = shallow(<DeprecationList {...props} />);
|
||||
const wrapper = shallow(<EsDeprecationList {...props} />);
|
||||
expect(wrapper).toMatchInlineSnapshot(`
|
||||
<IndexDeprecation
|
||||
deprecation={
|
||||
|
@ -102,7 +102,7 @@ describe('DeprecationList', () => {
|
|||
};
|
||||
|
||||
test('shows detailed messages', () => {
|
||||
expect(shallow(<DeprecationList {...defaultProps} />)).toMatchInlineSnapshot(`
|
||||
expect(shallow(<EsDeprecationList {...defaultProps} />)).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<MessageDeprecation
|
||||
deprecation={
|
||||
|
@ -113,7 +113,7 @@ describe('DeprecationList', () => {
|
|||
"url": "",
|
||||
}
|
||||
}
|
||||
key="Issue 1"
|
||||
key="Issue 1-0"
|
||||
/>
|
||||
<MessageDeprecation
|
||||
deprecation={
|
||||
|
@ -124,7 +124,7 @@ describe('DeprecationList', () => {
|
|||
"url": "",
|
||||
}
|
||||
}
|
||||
key="Issue 2"
|
||||
key="Issue 2-1"
|
||||
/>
|
||||
</div>
|
||||
`);
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -85,7 +85,7 @@ const IndexDeprecation: FunctionComponent<IndexDeprecationProps> = ({ 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 }) => {
|
||||
|
@ -105,16 +105,16 @@ export const DeprecationList: FunctionComponent<{
|
|||
} else if (currentGroupBy === GroupByOption.index) {
|
||||
return (
|
||||
<div>
|
||||
{deprecations.sort(sortByLevelDesc).map((dep) => (
|
||||
<MessageDeprecation deprecation={dep} key={dep.message} />
|
||||
{deprecations.sort(sortByLevelDesc).map((dep, index) => (
|
||||
<MessageDeprecation deprecation={dep} key={`${dep.message}-${index}`} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
{deprecations.sort(sortByLevelDesc).map((dep) => (
|
||||
<SimpleMessageDeprecation deprecation={dep} key={dep.message} />
|
||||
{deprecations.sort(sortByLevelDesc).map((dep, index) => (
|
||||
<SimpleMessageDeprecation deprecation={dep} key={`${dep.message}-${index}`} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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<FilterBarProps> = ({
|
||||
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 (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFilterGroup>
|
||||
<EuiFilterButton
|
||||
withNext
|
||||
key={LevelFilterOption.critical}
|
||||
onClick={() => {
|
||||
onFilterChange(
|
||||
currentFilter !== LevelFilterOption.critical
|
||||
? LevelFilterOption.critical
|
||||
: LevelFilterOption.all
|
||||
);
|
||||
}}
|
||||
hasActiveFilters={currentFilter === LevelFilterOption.critical}
|
||||
numFilters={levelCounts[LevelFilterOption.critical] || undefined}
|
||||
data-test-subj="criticalLevelFilter"
|
||||
>
|
||||
{LocalizedOptions[LevelFilterOption.critical]}
|
||||
</EuiFilterButton>
|
||||
<EuiFilterButton
|
||||
key={LevelFilterOption.warning}
|
||||
onClick={() => {
|
||||
onFilterChange(
|
||||
currentFilter !== LevelFilterOption.warning
|
||||
? LevelFilterOption.warning
|
||||
: LevelFilterOption.all
|
||||
);
|
||||
}}
|
||||
hasActiveFilters={currentFilter === LevelFilterOption.warning}
|
||||
numFilters={levelCounts[LevelFilterOption.warning] || undefined}
|
||||
data-test-subj="warningLevelFilter"
|
||||
>
|
||||
{LocalizedOptions[LevelFilterOption.warning]}
|
||||
</EuiFilterButton>
|
||||
</EuiFilterGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -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<Props> = ({
|
||||
deprecation,
|
||||
forceExpand,
|
||||
index,
|
||||
showStepsModal,
|
||||
showResolveModal,
|
||||
}) => {
|
||||
const { domainId, level, message, documentationUrl, correctiveActions } = deprecation;
|
||||
|
||||
return (
|
||||
<EuiAccordion
|
||||
id={`${domainId}-${index}`}
|
||||
data-test-subj={`${domainId}Deprecation`}
|
||||
initialIsOpen={forceExpand}
|
||||
buttonContent={i18nTexts.getDeprecationTitle(domainId)}
|
||||
paddingSize="m"
|
||||
extraAction={<DeprecationHealth single deprecationLevels={[LEVEL_MAP[level]]} />}
|
||||
>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
{level === 'fetch_error' ? (
|
||||
<EuiCallOut
|
||||
title={message}
|
||||
color="warning"
|
||||
iconType="alert"
|
||||
data-test-subj={`${domainId}Error`}
|
||||
size="s"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<p>{message}</p>
|
||||
|
||||
{(documentationUrl || correctiveActions?.manualSteps) && (
|
||||
<EuiFlexGroup>
|
||||
{correctiveActions?.api && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
size="s"
|
||||
data-test-subj="resolveButton"
|
||||
onClick={() => showResolveModal(deprecation)}
|
||||
>
|
||||
{i18nTexts.resolveButtonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
{correctiveActions?.manualSteps && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
data-test-subj="stepsButton"
|
||||
onClick={() =>
|
||||
showStepsModal({
|
||||
domainId,
|
||||
steps: correctiveActions.manualSteps!,
|
||||
documentationUrl,
|
||||
})
|
||||
}
|
||||
>
|
||||
{i18nTexts.manualFixButtonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
{documentationUrl && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
size="s"
|
||||
href={documentationUrl}
|
||||
iconType="help"
|
||||
target="_blank"
|
||||
>
|
||||
{i18nTexts.docLinkText}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiAccordion>
|
||||
);
|
||||
};
|
|
@ -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<void>;
|
||||
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<Props> = ({
|
||||
deprecations,
|
||||
showStepsModal,
|
||||
showResolveModal,
|
||||
reloadDeprecations,
|
||||
isLoading,
|
||||
}) => {
|
||||
const [currentFilter, setCurrentFilter] = useState<LevelFilterOption>('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 (
|
||||
<>
|
||||
<SearchBar
|
||||
isLoading={isLoading}
|
||||
loadData={reloadDeprecations}
|
||||
currentFilter={currentFilter}
|
||||
onFilterChange={setCurrentFilter}
|
||||
onSearchChange={setSearch}
|
||||
totalDeprecationsCount={deprecations.length}
|
||||
levelToDeprecationCountMap={levelToDeprecationCountMap}
|
||||
/>
|
||||
|
||||
{deprecationsWithErrors.length > 0 && (
|
||||
<>
|
||||
<KibanaDeprecationErrors errorType="pluginError" />
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
|
||||
<DeprecationListBar
|
||||
allDeprecationsCount={deprecations.length}
|
||||
filteredDeprecationsCount={filteredDeprecations.length}
|
||||
setExpandAll={setExpandAll}
|
||||
/>
|
||||
|
||||
<EuiHorizontalRule margin="m" />
|
||||
|
||||
<>
|
||||
{filteredDeprecations
|
||||
.slice(currentPage * DEPRECATIONS_PER_PAGE, (currentPage + 1) * DEPRECATIONS_PER_PAGE)
|
||||
.map((deprecation, index) => [
|
||||
<div key={`kibana-deprecation-${index}`} data-test-subj="kibanaDeprecationItem">
|
||||
<KibanaDeprecationAccordion
|
||||
{...{
|
||||
key: expandState.expandNumber,
|
||||
index,
|
||||
deprecation,
|
||||
forceExpand: expandState.forceExpand,
|
||||
showStepsModal,
|
||||
showResolveModal,
|
||||
}}
|
||||
/>
|
||||
<EuiHorizontalRule margin="s" />
|
||||
</div>,
|
||||
])}
|
||||
|
||||
{/* Only show pagination if we have more than DEPRECATIONS_PER_PAGE */}
|
||||
{filteredDeprecations.length > DEPRECATIONS_PER_PAGE && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<DeprecationPagination
|
||||
pageCount={Math.ceil(filteredDeprecations.length / DEPRECATIONS_PER_PAGE)}
|
||||
activePage={currentPage}
|
||||
setPage={setCurrentPage}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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';
|
|
@ -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<Props> = ({ errorType }) => {
|
||||
if (errorType === 'pluginError') {
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={i18nTexts.pluginError}
|
||||
color="warning"
|
||||
iconType="alert"
|
||||
data-test-subj="kibanaPluginError"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={i18nTexts.loadingError}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj="kibanaRequestError"
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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<Error | undefined>(undefined);
|
||||
const [stepsModalContent, setStepsModalContent] = useState<StepsModalContent | undefined>(
|
||||
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 (
|
||||
<NoDeprecationsPrompt
|
||||
deprecationType={i18nTexts.deprecationLabel}
|
||||
navigateToOverviewPage={() => history.push('/overview')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let content: React.ReactNode;
|
||||
|
||||
if (isLoading) {
|
||||
content = <SectionLoading>{i18nTexts.isLoading}</SectionLoading>;
|
||||
} else if (kibanaDeprecations?.length) {
|
||||
content = (
|
||||
<KibanaDeprecationList
|
||||
deprecations={kibanaDeprecations}
|
||||
showStepsModal={toggleStepsModal}
|
||||
showResolveModal={toggleResolveModal}
|
||||
reloadDeprecations={getAllDeprecations}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
} else if (error) {
|
||||
content = <KibanaDeprecationErrors errorType="requestError" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-test-subj="kibanaDeprecationsContent">
|
||||
<EuiSpacer />
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPageBody>
|
||||
<EuiPageContent>
|
||||
<EuiPageHeader
|
||||
pageTitle={i18nTexts.pageTitle}
|
||||
description={i18nTexts.pageDescription}
|
||||
rightSideItems={[
|
||||
<EuiButtonEmpty
|
||||
href={docLinks.links.upgradeAssistant}
|
||||
target="_blank"
|
||||
iconType="help"
|
||||
data-test-subj="documentationLink"
|
||||
>
|
||||
{i18nTexts.docLinkText}
|
||||
</EuiButtonEmpty>,
|
||||
]}
|
||||
/>
|
||||
|
||||
<EuiPageContentBody>
|
||||
{getPageContent()}
|
||||
|
||||
{stepsModalContent && (
|
||||
<StepsModal closeModal={() => toggleStepsModal()} modalContent={stepsModalContent} />
|
||||
)}
|
||||
|
||||
{resolveModalContent && (
|
||||
<ResolveDeprecationModal
|
||||
closeModal={() => toggleResolveModal()}
|
||||
resolveDeprecation={resolveDeprecation}
|
||||
isResolvingDeprecation={isResolvingDeprecation}
|
||||
deprecation={resolveModalContent}
|
||||
/>
|
||||
)}
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
);
|
||||
});
|
|
@ -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<void>;
|
||||
}
|
||||
|
||||
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<Props> = ({
|
||||
closeModal,
|
||||
deprecation,
|
||||
isResolvingDeprecation,
|
||||
resolveDeprecation,
|
||||
}) => {
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
data-test-subj="resolveModal"
|
||||
title={i18nTexts.getModalTitle(deprecation.domainId)}
|
||||
onCancel={closeModal}
|
||||
onConfirm={() => resolveDeprecation(deprecation)}
|
||||
cancelButtonText={i18nTexts.cancelButtonLabel}
|
||||
confirmButtonText={i18nTexts.resolveButtonLabel}
|
||||
defaultFocusedButton="confirm"
|
||||
isLoading={isResolvingDeprecation}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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<Props> = ({ closeModal, modalContent }) => {
|
||||
const { domainId, steps, documentationUrl } = modalContent;
|
||||
|
||||
return (
|
||||
<EuiModal onClose={closeModal} data-test-subj="stepsModal">
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
<EuiTitle size="m">
|
||||
<h2>{i18nTexts.getModalTitle(domainId)}</h2>
|
||||
</EuiTitle>
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
|
||||
<EuiModalBody>
|
||||
<>
|
||||
<EuiText>
|
||||
<p>{i18nTexts.modalDescription}</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiSteps
|
||||
titleSize="xs"
|
||||
data-test-subj="fixDeprecationSteps"
|
||||
steps={steps.map((step, index) => {
|
||||
return {
|
||||
title: i18nTexts.getStepTitle(index + 1),
|
||||
children: (
|
||||
<EuiText>
|
||||
<p>{step}</p>
|
||||
</EuiText>
|
||||
),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
</EuiModalBody>
|
||||
|
||||
<EuiModalFooter>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
{documentationUrl && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="help" target="_blank" href={documentationUrl}>
|
||||
{i18nTexts.docLinkLabel}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={closeModal} fill data-test-subj="closeButton">
|
||||
{i18nTexts.closeButtonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
};
|
|
@ -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<Props> = ({ history }) => {
|
|||
esDeprecations?.indices.length ?? 0
|
||||
)}
|
||||
position="right"
|
||||
iconProps={{
|
||||
tabIndex: -1,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
>
|
||||
{error === null && (
|
||||
<EuiScreenReaderOnly>
|
||||
<p>
|
||||
{isLoading
|
||||
? i18nTexts.loadingText
|
||||
: i18nTexts.getTotalDeprecationsTooltip(
|
||||
esDeprecations?.cluster.length ?? 0,
|
||||
esDeprecations?.indices.length ?? 0
|
||||
)}
|
||||
</p>
|
||||
</EuiScreenReaderOnly>
|
||||
)}
|
||||
</EuiStat>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
|
@ -120,6 +147,16 @@ export const ESDeprecationStats: FunctionComponent<Props> = ({ history }) => {
|
|||
titleColor="danger"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{error === null && (
|
||||
<EuiScreenReaderOnly>
|
||||
<p>
|
||||
{isLoading
|
||||
? i18nTexts.loadingText
|
||||
: i18nTexts.getCriticalDeprecationsMessage(criticalDeprecations.length)}
|
||||
</p>
|
||||
</EuiScreenReaderOnly>
|
||||
)}
|
||||
|
||||
{error && <EsStatsErrors error={error} />}
|
||||
</EuiStat>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -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<Props> = ({ history }) => {
|
||||
const { deprecations } = useAppContext();
|
||||
|
||||
const [kibanaDeprecations, setKibanaDeprecations] = useState<
|
||||
DomainDeprecationDetails[] | undefined
|
||||
>(undefined);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<Error | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
async function getAllDeprecations() {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const response = await deprecations.getAllDeprecations();
|
||||
setKibanaDeprecations(response);
|
||||
} catch (e) {
|
||||
setError(e);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
getAllDeprecations();
|
||||
}, [deprecations]);
|
||||
|
||||
return (
|
||||
<EuiPanel data-test-subj="kibanaStatsPanel" hasShadow={false} hasBorder={true}>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="baseline">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
<h2>{i18nTexts.statsTitle}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
{...reactRouterNavigate(history, '/kibana_deprecations')}
|
||||
data-test-subj="kibanaDeprecationsLink"
|
||||
>
|
||||
{i18nTexts.viewDeprecationsLink}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiStat
|
||||
data-test-subj="totalDeprecations"
|
||||
title={error ? '--' : kibanaDeprecations?.length ?? '0'}
|
||||
description={i18nTexts.totalDeprecationsTitle}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{error === undefined && (
|
||||
<EuiScreenReaderOnly>
|
||||
<p>
|
||||
{isLoading
|
||||
? i18nTexts.loadingText
|
||||
: i18nTexts.getTotalDeprecationsMessage(kibanaDeprecations?.length ?? 0)}
|
||||
</p>
|
||||
</EuiScreenReaderOnly>
|
||||
)}
|
||||
</EuiStat>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiStat
|
||||
data-test-subj="criticalDeprecations"
|
||||
title={
|
||||
kibanaDeprecations
|
||||
? kibanaDeprecations.filter((deprecation) => deprecation.level === 'critical')
|
||||
?.length ?? '0'
|
||||
: '--'
|
||||
}
|
||||
description={i18nTexts.criticalDeprecationsTitle}
|
||||
titleColor="danger"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{error === undefined && (
|
||||
<EuiScreenReaderOnly>
|
||||
<p>
|
||||
{isLoading
|
||||
? i18nTexts.loadingText
|
||||
: i18nTexts.getCriticalDeprecationsMessage(
|
||||
kibanaDeprecations
|
||||
? kibanaDeprecations.filter(
|
||||
(deprecation) => deprecation.level === 'critical'
|
||||
)?.length ?? 0
|
||||
: 0
|
||||
)}
|
||||
</p>
|
||||
</EuiScreenReaderOnly>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiIconTip
|
||||
type="alert"
|
||||
color="danger"
|
||||
size="l"
|
||||
content={i18nTexts.loadingError}
|
||||
iconProps={{
|
||||
'data-test-subj': 'requestErrorIconTip',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</EuiStat>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
|
@ -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<Props> = ({ history }) => {
|
|||
<EuiSpacer size="xl" />
|
||||
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false} style={{ minWidth: 400 }}>
|
||||
<EuiFlexItem>
|
||||
<ESDeprecationStats history={history} />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFormRow
|
||||
helpText={i18nTexts.getDeprecationLoggingLabel(
|
||||
docLinks.links.elasticsearch.deprecationLogging
|
||||
)}
|
||||
data-test-subj="deprecationLoggingFormRow"
|
||||
>
|
||||
<DeprecationLoggingToggle />
|
||||
</EuiFormRow>
|
||||
<EuiFlexItem>
|
||||
<KibanaDeprecationStats history={history} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiFormRow
|
||||
helpText={i18nTexts.getDeprecationLoggingLabel(
|
||||
docLinks.links.elasticsearch.deprecationLogging
|
||||
)}
|
||||
data-test-subj="deprecationLoggingFormRow"
|
||||
>
|
||||
<DeprecationLoggingToggle />
|
||||
</EuiFormRow>
|
||||
</>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
|
|
|
@ -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 }) => (
|
||||
<EuiText size="s">
|
||||
{allDeprecations.length ? (
|
||||
{allDeprecationsCount > 0 ? (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.numDeprecationsShownLabel"
|
||||
defaultMessage="Showing {numShown} of {total}"
|
||||
values={{ numShown: deprecations.length, total: allDeprecations.length }}
|
||||
values={{ numShown: filteredDeprecationsCount, total: allDeprecationsCount }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
|
@ -29,15 +27,15 @@ export const DeprecationCountSummary: FunctionComponent<{
|
|||
defaultMessage="No deprecations"
|
||||
/>
|
||||
)}
|
||||
{deprecations.length !== allDeprecations.length && (
|
||||
<Fragment>
|
||||
{filteredDeprecationsCount !== allDeprecationsCount && (
|
||||
<>
|
||||
{'. '}
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.checkupTab.changeFiltersShowMoreLabel"
|
||||
description="Explains how to show all deprecations if there are more available."
|
||||
defaultMessage="Change filter to show more."
|
||||
/>
|
||||
</Fragment>
|
||||
</>
|
||||
)}
|
||||
</EuiText>
|
||||
);
|
|
@ -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 (
|
||||
<EuiFlexGroup responsive={false} justifyContent="spaceBetween" alignItems="baseline">
|
||||
<EuiFlexItem>
|
||||
<DeprecationCountSummary
|
||||
allDeprecationsCount={allDeprecationsCount}
|
||||
filteredDeprecationsCount={filteredDeprecationsCount}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
size="s"
|
||||
onClick={() => setExpandAll(true)}
|
||||
data-test-subj="expandAll"
|
||||
>
|
||||
{i18nTexts.expandAllButton}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
flush="left"
|
||||
size="s"
|
||||
onClick={() => setExpandAll(false)}
|
||||
data-test-subj="collapseAll"
|
||||
>
|
||||
{i18nTexts.collapseAllButton}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -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';
|
|
@ -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 (
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPagination pageCount={pageCount} activePage={activePage} onPageClick={setPage} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -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<DeprecationHealthProps> = ({
|
||||
deprecations,
|
||||
deprecationLevels,
|
||||
single = false,
|
||||
}) => {
|
||||
if (deprecations.length === 0) {
|
||||
if (deprecationLevels.length === 0) {
|
||||
return <span />;
|
||||
}
|
||||
|
||||
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 <SingleHealth level={highestLevel} label={LocalizedLevels[highestLevel]} />;
|
||||
}
|
||||
|
||||
const countByLevel = countBy(levels);
|
||||
const countByLevel = countBy(deprecationLevels);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
|
@ -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';
|
|
@ -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) => (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.noDeprecationsPrompt.nextStepsDescription"
|
||||
defaultMessage="Check the {overviewButton} for other Stack deprecations."
|
||||
values={{
|
||||
overviewButton: (
|
||||
<EuiLink onClick={navigateToOverviewPage}>
|
||||
{i18n.translate('xpack.upgradeAssistant.noDeprecationsPrompt.overviewLinkText', {
|
||||
defaultMessage: 'Overview page',
|
||||
})}
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
interface Props {
|
||||
deprecationType: string;
|
||||
navigateToOverviewPage: () => void;
|
||||
}
|
||||
|
||||
export const NoDeprecationsPrompt: FunctionComponent<Props> = ({
|
||||
deprecationType,
|
||||
navigateToOverviewPage,
|
||||
}) => {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
iconType="faceHappy"
|
||||
data-test-subj="noDeprecationsPrompt"
|
||||
title={<h2>{i18nTexts.emptyPromptTitle}</h2>}
|
||||
body={
|
||||
<>
|
||||
<p data-test-subj="upgradeAssistantIssueSummary">
|
||||
{i18nTexts.getEmptyPromptDescription(deprecationType)}
|
||||
</p>
|
||||
<p>{i18nTexts.getEmptyPromptNextStepsDescription(navigateToOverviewPage)}</p>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GroupByBar renders 1`] = `
|
||||
exports[`GroupByFilter renders 1`] = `
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FilterBar renders 1`] = `
|
||||
exports[`DeprecationLevelFilter renders 1`] = `
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
|
@ -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
|
||||
</EuiFilterButton>
|
||||
<EuiFilterButton
|
||||
data-test-subj="warningLevelFilter"
|
||||
hasActiveFilters={false}
|
||||
key="warning"
|
||||
onClick={[Function]}
|
||||
>
|
||||
warning
|
||||
</EuiFilterButton>
|
||||
</EuiFilterGroup>
|
||||
</EuiFlexItem>
|
||||
`;
|
|
@ -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(<GroupByBar {...defaultProps} />)).toMatchSnapshot();
|
||||
expect(shallow(<GroupByFilter {...defaultProps} />)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('clicking button calls onGroupByChange', () => {
|
||||
const wrapper = mount(<GroupByBar {...defaultProps} />);
|
||||
const wrapper = mount(<GroupByFilter {...defaultProps} />);
|
||||
wrapper.find('button.euiFilterButton-hasActiveFilters').simulate('click');
|
||||
expect(defaultProps.onGroupByChange).toHaveBeenCalledTimes(1);
|
||||
expect(defaultProps.onGroupByChange.mock.calls[0][0]).toEqual(GroupByOption.message);
|
|
@ -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<GroupByBarProps> = ({
|
||||
export const GroupByFilter: React.FunctionComponent<GroupByFilterProps> = ({
|
||||
availableGroupByOptions,
|
||||
currentGroupBy,
|
||||
onGroupByChange,
|
|
@ -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';
|
|
@ -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(<FilterBar {...defaultProps} />)).toMatchSnapshot();
|
||||
expect(shallow(<DeprecationLevelFilter {...defaultProps} />)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('clicking button calls onFilterChange', () => {
|
||||
const wrapper = mount(<FilterBar {...defaultProps} />);
|
||||
const wrapper = mount(<DeprecationLevelFilter {...defaultProps} />);
|
||||
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');
|
||||
});
|
||||
});
|
|
@ -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<DeprecationLevelProps> = ({
|
||||
levelsCount,
|
||||
currentFilter,
|
||||
onFilterChange,
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFilterGroup>
|
||||
<EuiFilterButton
|
||||
key="critical"
|
||||
onClick={() => {
|
||||
onFilterChange(currentFilter !== 'critical' ? 'critical' : 'all');
|
||||
}}
|
||||
hasActiveFilters={currentFilter === 'critical'}
|
||||
numFilters={levelsCount.critical || undefined}
|
||||
data-test-subj="criticalLevelFilter"
|
||||
>
|
||||
{LocalizedOptions.critical}
|
||||
</EuiFilterButton>
|
||||
</EuiFilterGroup>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
|
@ -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<SearchBarProps> = ({
|
||||
totalDeprecationsCount,
|
||||
levelToDeprecationCountMap,
|
||||
isLoading,
|
||||
loadData,
|
||||
currentFilter,
|
||||
onFilterChange,
|
||||
onSearchChange,
|
||||
groupByFilterProps,
|
||||
}) => {
|
||||
const [searchTermError, setSearchTermError] = useState<null | string>(null);
|
||||
const filterInvalid = Boolean(searchTermError);
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFieldSearch
|
||||
isInvalid={filterInvalid}
|
||||
aria-label={i18nTexts.searchAriaLabel}
|
||||
placeholder={i18nTexts.searchPlaceholderLabel}
|
||||
onChange={(e) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
{/* These two components provide their own EuiFlexItem wrappers */}
|
||||
<DeprecationLevelFilter
|
||||
{...{
|
||||
totalDeprecationsCount,
|
||||
levelsCount: levelToDeprecationCountMap,
|
||||
currentFilter,
|
||||
onFilterChange,
|
||||
}}
|
||||
/>
|
||||
{groupByFilterProps && <GroupByFilter {...groupByFilterProps} />}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton onClick={loadData} iconType="refresh" isLoading={isLoading}>
|
||||
{i18nTexts.reloadButtonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
{filterInvalid && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
title={i18nTexts.getInvalidSearchMessage(searchTermError!)}
|
||||
iconType="faceSad"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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',
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -36,6 +36,7 @@ export async function upsertUIOpenOption({
|
|||
cluster,
|
||||
indices,
|
||||
savedObjects,
|
||||
kibana,
|
||||
}: UpsertUIOpenOptionDependencies): Promise<UIOpen> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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' },
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ export const telemetrySavedObjectType: SavedObjectsType = {
|
|||
type: 'long',
|
||||
null_value: 0,
|
||||
},
|
||||
kibana: {
|
||||
type: 'long',
|
||||
null_value: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
ui_reindex: {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<KibanaTestSubjects> & {
|
||||
actions: ReturnType<typeof createActions>;
|
||||
};
|
||||
|
||||
const createActions = (testBed: TestBed) => {
|
||||
/**
|
||||
* User Actions
|
||||
*/
|
||||
|
||||
const clickExpandAll = () => {
|
||||
const { find } = testBed;
|
||||
find('expandAll').simulate('click');
|
||||
};
|
||||
|
||||
return {
|
||||
clickExpandAll,
|
||||
};
|
||||
};
|
||||
|
||||
export const setup = async (overrides?: Record<string, unknown>): Promise<KibanaTestBed> => {
|
||||
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;
|
|
@ -34,6 +34,9 @@ export type OverviewTestSubjects =
|
|||
| 'esStatsPanel'
|
||||
| 'esStatsPanel.totalDeprecations'
|
||||
| 'esStatsPanel.criticalDeprecations'
|
||||
| 'kibanaStatsPanel'
|
||||
| 'kibanaStatsPanel.totalDeprecations'
|
||||
| 'kibanaStatsPanel.criticalDeprecations'
|
||||
| 'deprecationLoggingFormRow'
|
||||
| 'requestErrorIconTip'
|
||||
| 'partiallyUpgradedErrorIconTip'
|
||||
|
|
|
@ -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<string, unknown
|
|||
api: apiService,
|
||||
breadcrumbs: breadcrumbService,
|
||||
getUrlForApp: () => '',
|
||||
deprecations: deprecationsServiceMock.createStartContract(),
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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<Element> | 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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue