mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `8.x` to `main`: - [[Upgrade Assistant] Data streams reindexing (#208083)](https://github.com/elastic/kibana/pull/208083) <!--- Backport version: 9.6.4 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Ahmad Bamieh","email":"ahmad.bamyeh@elastic.co"},"sourceCommit":{"committedDate":"2025-01-30T22:33:10Z","message":"[Upgrade Assistant] Data streams reindexing (#208083)\n\n## Data streams reindexing\r\n\r\nThis PR implements data stream reindexing corrective action.\r\n\r\n\r\n## Screenshots\r\nFound here\r\nhttps://docs.google.com/document/d/1QntGX5aTbjAv-VRZoKg43McZ_obddLkIPAQp_molMvw/edit?tab=t.0#heading=h.spoobki9vra3\r\n\r\n\r\n## Testing\r\n#### Handling kibana restarts:\r\nSince we dont maintain any state in Kibana for the data stream\r\nreindexing we dont worry about this scenario\r\n\r\n#### Ensuring only one node is handling the task sync with ES:\r\nSince we dont maintain any state in Kibana for the data stream\r\nreindexing we dont worry about this scenario\r\n\r\n#### ES task returns an exception:\r\nThis is a new state that was introduced in persistent tasks. We show a\r\nhuge exception banner and ask user to retry.\r\n\r\n#### ES task fails to reindex some indices but reindexing is complete:\r\nShow an error in the in progress page. This is an undesired state since\r\nthe user will still be asked to reindex the data stream again for only\r\nthose failed indices.\r\n\r\n#### ES task fails to reindex some indices but reindexing is still in\r\nprogress\r\nWe should a count of how many failed while showing a progress bar.\r\n\r\n#### Pause/resume/cancel functionality\r\ndata transform reindexing only has ‘cancel’ from ES side however it\r\nbehaves as “pause” since it will pick up where it last was cancelled.\r\nSince we do not maintain a state in kibana we only show a cancel button.\r\nOnce the reindex is cancelled the user can start it again. All the\r\ndescriptions are updated from ES with the latest incomplete countes.\r\n\r\n#### ES task stops responding\r\nWe mark the Kibana task as failed\r\n\r\n#### ES task returns 404\r\nBoth cancelled tasks and never started ones return 404 from ES side.\r\nThe only way to tell the difference is to see if the Kibana task is “in\r\nprogress” then this means it was cancelled from ES side (canelled via\r\nAPI by the user directly for example) and we mark it as cancelled for\r\nthe user.\r\n\r\n\r\n#### Other main flow tests:\r\n- Tested a small data stream reindexing job\r\n- Tested a huge data stream reindexing job\r\n- Tested pausing a job\r\n- Tested cancelling a job\r\n- Stopping ES during reindexing and then restarting\r\n- Reindexing in a cluster with 2 Kibana nodes","sha":"6925d129455ccb458e08cf33ce36c08e0d5313f5","branchLabelMapping":{"^v8.16.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport:prev-major","v8.18.0"],"title":"[Upgrade Assistant] Data streams reindexing","number":208083,"url":"https://github.com/elastic/kibana/pull/208083","mergeCommit":{"message":"[Upgrade Assistant] Data streams reindexing (#208083)\n\n## Data streams reindexing\r\n\r\nThis PR implements data stream reindexing corrective action.\r\n\r\n\r\n## Screenshots\r\nFound here\r\nhttps://docs.google.com/document/d/1QntGX5aTbjAv-VRZoKg43McZ_obddLkIPAQp_molMvw/edit?tab=t.0#heading=h.spoobki9vra3\r\n\r\n\r\n## Testing\r\n#### Handling kibana restarts:\r\nSince we dont maintain any state in Kibana for the data stream\r\nreindexing we dont worry about this scenario\r\n\r\n#### Ensuring only one node is handling the task sync with ES:\r\nSince we dont maintain any state in Kibana for the data stream\r\nreindexing we dont worry about this scenario\r\n\r\n#### ES task returns an exception:\r\nThis is a new state that was introduced in persistent tasks. We show a\r\nhuge exception banner and ask user to retry.\r\n\r\n#### ES task fails to reindex some indices but reindexing is complete:\r\nShow an error in the in progress page. This is an undesired state since\r\nthe user will still be asked to reindex the data stream again for only\r\nthose failed indices.\r\n\r\n#### ES task fails to reindex some indices but reindexing is still in\r\nprogress\r\nWe should a count of how many failed while showing a progress bar.\r\n\r\n#### Pause/resume/cancel functionality\r\ndata transform reindexing only has ‘cancel’ from ES side however it\r\nbehaves as “pause” since it will pick up where it last was cancelled.\r\nSince we do not maintain a state in kibana we only show a cancel button.\r\nOnce the reindex is cancelled the user can start it again. All the\r\ndescriptions are updated from ES with the latest incomplete countes.\r\n\r\n#### ES task stops responding\r\nWe mark the Kibana task as failed\r\n\r\n#### ES task returns 404\r\nBoth cancelled tasks and never started ones return 404 from ES side.\r\nThe only way to tell the difference is to see if the Kibana task is “in\r\nprogress” then this means it was cancelled from ES side (canelled via\r\nAPI by the user directly for example) and we mark it as cancelled for\r\nthe user.\r\n\r\n\r\n#### Other main flow tests:\r\n- Tested a small data stream reindexing job\r\n- Tested a huge data stream reindexing job\r\n- Tested pausing a job\r\n- Tested cancelling a job\r\n- Stopping ES during reindexing and then restarting\r\n- Reindexing in a cluster with 2 Kibana nodes","sha":"6925d129455ccb458e08cf33ce36c08e0d5313f5"}},"sourceBranch":"8.x","suggestedTargetBranches":["8.18"],"targetPullRequestStates":[{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
This commit is contained in:
parent
396804f0f0
commit
160c0d4682
40 changed files with 3124 additions and 7 deletions
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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 interface DataStreamsActionMetadata {
|
||||
totalBackingIndices: number;
|
||||
indicesRequiringUpgradeCount: number;
|
||||
indicesRequiringUpgrade: string[];
|
||||
ignoredIndicesRequiringUpgrade: string[];
|
||||
ignoredIndicesRequiringUpgradeCount: number;
|
||||
reindexRequired: boolean;
|
||||
}
|
||||
|
||||
export interface DataStreamsAction {
|
||||
type: 'dataStream';
|
||||
metadata: DataStreamsActionMetadata;
|
||||
}
|
||||
|
||||
export interface DataStreamMetadata {
|
||||
dataStreamName: string;
|
||||
documentationUrl: string;
|
||||
|
||||
lastIndexRequiringUpgradeCreationDate: number;
|
||||
allIndices: string[];
|
||||
allIndicesCount: number;
|
||||
indicesRequiringUpgradeCount: number;
|
||||
indicesRequiringUpgrade: string[];
|
||||
|
||||
indicesRequiringUpgradeDocsSize: number;
|
||||
indicesRequiringUpgradeDocsCount: number;
|
||||
}
|
||||
|
||||
export interface DataStreamReindexStatusResponse {
|
||||
warnings?: DataStreamReindexWarning[];
|
||||
reindexOp?: DataStreamReindexOperation;
|
||||
hasRequiredPrivileges?: boolean;
|
||||
}
|
||||
|
||||
export type DataStreamReindexWarningTypes = 'incompatibleDataStream';
|
||||
|
||||
export interface DataStreamReindexWarning {
|
||||
warningType: DataStreamReindexWarningTypes;
|
||||
meta?: {
|
||||
[key: string]: string | string[];
|
||||
};
|
||||
}
|
||||
|
||||
export enum DataStreamReindexStatus {
|
||||
notStarted,
|
||||
inProgress,
|
||||
completed,
|
||||
failed,
|
||||
cancelled,
|
||||
fetchFailed,
|
||||
}
|
||||
|
||||
export interface DataStreamProgressDetails {
|
||||
startTimeMs: number;
|
||||
successCount: number;
|
||||
pendingCount: number;
|
||||
inProgressCount: number;
|
||||
errorsCount: number;
|
||||
}
|
||||
|
||||
export interface DataStreamReindexStatusNotStarted {
|
||||
status: DataStreamReindexStatus.notStarted;
|
||||
}
|
||||
|
||||
export interface DataStreamReindexStatusInProgress {
|
||||
status: DataStreamReindexStatus.inProgress;
|
||||
reindexTaskPercComplete: number;
|
||||
progressDetails: DataStreamProgressDetails;
|
||||
}
|
||||
|
||||
export interface DataStreamReindexStatusCompleted {
|
||||
status: DataStreamReindexStatus.completed;
|
||||
reindexTaskPercComplete: number;
|
||||
progressDetails: DataStreamProgressDetails;
|
||||
}
|
||||
|
||||
export interface DataStreamReindexStatusFailed {
|
||||
status: DataStreamReindexStatus.failed;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export interface DataStreamReindexStatusCancelled {
|
||||
status: DataStreamReindexStatus.cancelled;
|
||||
}
|
||||
|
||||
export type DataStreamReindexOperation =
|
||||
| DataStreamReindexStatusNotStarted
|
||||
| DataStreamReindexStatusInProgress
|
||||
| DataStreamReindexStatusCompleted
|
||||
| DataStreamReindexStatusCancelled
|
||||
| DataStreamReindexStatusFailed;
|
||||
|
||||
/**
|
||||
* ES Requests Types (untyped in the ES Client)
|
||||
*/
|
||||
|
||||
// Merged but not into our client version yet.
|
||||
// https://github.com/elastic/elasticsearch-specification/blob/main/specification/migrate/get_reindex_status/MigrateGetReindexStatusResponse.ts
|
||||
export interface DataStreamReindexTaskStatusResponse {
|
||||
start_time?: number;
|
||||
start_time_millis: number;
|
||||
complete: boolean;
|
||||
total_indices_in_data_stream: number;
|
||||
total_indices_requiring_upgrade: number;
|
||||
successes: number;
|
||||
in_progress: Array<{
|
||||
index: string;
|
||||
total_doc_count: number;
|
||||
reindexed_doc_count: number;
|
||||
}>;
|
||||
pending: number;
|
||||
errors: Array<{
|
||||
index: string;
|
||||
message: string;
|
||||
}>;
|
||||
exception?: string;
|
||||
}
|
|
@ -8,6 +8,9 @@
|
|||
import { HealthReportImpact } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/types';
|
||||
import { SavedObject } from '@kbn/core/types';
|
||||
import type { DataStreamsAction } from './data_stream_types';
|
||||
|
||||
export * from './data_stream_types';
|
||||
|
||||
export type DeprecationSource = 'Kibana' | 'Elasticsearch';
|
||||
|
||||
|
@ -229,6 +232,7 @@ export interface EnrichedDeprecationInfo
|
|||
> {
|
||||
type:
|
||||
| keyof estypes.MigrationDeprecationsResponse
|
||||
| 'data_streams'
|
||||
| 'health_indicator'
|
||||
| 'ilm_policies'
|
||||
| 'templates';
|
||||
|
@ -240,6 +244,7 @@ export interface EnrichedDeprecationInfo
|
|||
| MlAction
|
||||
| IndexSettingAction
|
||||
| ClusterSettingAction
|
||||
| DataStreamsAction
|
||||
| HealthIndicatorAction;
|
||||
resolveDuringUpgrade: boolean;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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, { createContext, useContext } from 'react';
|
||||
|
||||
import { ApiService } from '../../../../lib/api';
|
||||
import { useReindexStatus, ReindexState } from './use_reindex_state';
|
||||
|
||||
export interface ReindexStateContext {
|
||||
reindexState: ReindexState;
|
||||
startReindex: () => Promise<void>;
|
||||
loadDataStreamMetadata: () => Promise<void>;
|
||||
cancelReindex: () => Promise<void>;
|
||||
}
|
||||
|
||||
const DataStreamReindexContext = createContext<ReindexStateContext | undefined>(undefined);
|
||||
|
||||
export const useDataStreamReindexContext = () => {
|
||||
const context = useContext(DataStreamReindexContext);
|
||||
if (context === undefined) {
|
||||
throw new Error(
|
||||
'useDataStreamReindexContext must be used within a <DataStreamReindexStatusProvider />'
|
||||
);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
api: ApiService;
|
||||
children: React.ReactNode;
|
||||
dataStreamName: string;
|
||||
}
|
||||
|
||||
export const DataStreamReindexStatusProvider: React.FunctionComponent<Props> = ({
|
||||
api,
|
||||
dataStreamName,
|
||||
children,
|
||||
}) => {
|
||||
const { reindexState, startReindex, loadDataStreamMetadata, cancelReindex } = useReindexStatus({
|
||||
dataStreamName,
|
||||
api,
|
||||
});
|
||||
|
||||
return (
|
||||
<DataStreamReindexContext.Provider
|
||||
value={{
|
||||
reindexState,
|
||||
startReindex,
|
||||
cancelReindex,
|
||||
loadDataStreamMetadata,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</DataStreamReindexContext.Provider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* 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, { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiDescriptionList,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutHeader,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
|
||||
import moment from 'moment';
|
||||
import numeral from '@elastic/numeral';
|
||||
import {
|
||||
DataStreamReindexStatus,
|
||||
EnrichedDeprecationInfo,
|
||||
} from '../../../../../../../common/types';
|
||||
|
||||
import { ReindexStateContext } from '../context';
|
||||
|
||||
import { DeprecationBadge } from '../../../../shared';
|
||||
import {
|
||||
UIM_DATA_STREAM_REINDEX_START_CLICK,
|
||||
UIM_DATA_STREAM_REINDEX_STOP_CLICK,
|
||||
uiMetricService,
|
||||
} from '../../../../../lib/ui_metric';
|
||||
|
||||
import { containerMessages } from './messages';
|
||||
import type { FlyoutStep } from './steps/types';
|
||||
import { InitializingFlyoutStep } from './steps/initializing';
|
||||
import { ConfirmReindexingFlyoutStep } from './steps/confirm';
|
||||
import { DataStreamDetailsFlyoutStep } from './steps/details';
|
||||
import { ChecklistFlyoutStep } from './steps/checklist';
|
||||
import { ReindexingCompletedFlyoutStep } from './steps/completed';
|
||||
|
||||
interface Props extends ReindexStateContext {
|
||||
deprecation: EnrichedDeprecationInfo;
|
||||
closeFlyout: () => void;
|
||||
}
|
||||
|
||||
const DATE_FORMAT = 'dddd, MMMM Do YYYY, h:mm:ss a';
|
||||
const FILE_SIZE_DISPLAY_FORMAT = '0,0.[0] b';
|
||||
|
||||
export const DataStreamReindexFlyout: React.FunctionComponent<Props> = ({
|
||||
cancelReindex,
|
||||
loadDataStreamMetadata,
|
||||
reindexState,
|
||||
startReindex,
|
||||
closeFlyout,
|
||||
deprecation,
|
||||
}) => {
|
||||
const { status, reindexWarnings, errorMessage, meta } = reindexState;
|
||||
const { index } = deprecation;
|
||||
|
||||
const [flyoutStep, setFlyoutStep] = useState<FlyoutStep>('initializing');
|
||||
|
||||
const switchFlyoutStep = useCallback(() => {
|
||||
switch (status) {
|
||||
case DataStreamReindexStatus.notStarted: {
|
||||
setFlyoutStep('notStarted');
|
||||
return;
|
||||
}
|
||||
case DataStreamReindexStatus.failed:
|
||||
case DataStreamReindexStatus.fetchFailed:
|
||||
case DataStreamReindexStatus.cancelled:
|
||||
case DataStreamReindexStatus.inProgress: {
|
||||
setFlyoutStep('inProgress');
|
||||
return;
|
||||
}
|
||||
case DataStreamReindexStatus.completed: {
|
||||
setTimeout(() => {
|
||||
// wait for 1.5 more seconds fur the UI to visually get to 100%
|
||||
setFlyoutStep('completed');
|
||||
}, 1500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
useMemo(async () => {
|
||||
if (flyoutStep === 'initializing') {
|
||||
await loadDataStreamMetadata();
|
||||
switchFlyoutStep();
|
||||
}
|
||||
}, [loadDataStreamMetadata, switchFlyoutStep, flyoutStep]);
|
||||
useMemo(() => switchFlyoutStep(), [switchFlyoutStep]);
|
||||
|
||||
const onStartReindex = useCallback(async () => {
|
||||
uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_DATA_STREAM_REINDEX_START_CLICK);
|
||||
await startReindex();
|
||||
}, [startReindex]);
|
||||
|
||||
const onStopReindex = useCallback(async () => {
|
||||
uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_DATA_STREAM_REINDEX_STOP_CLICK);
|
||||
await cancelReindex();
|
||||
}, [cancelReindex]);
|
||||
|
||||
const { docsSizeFormatted, indicesRequiringUpgradeDocsCount, lastIndexCreationDateFormatted } =
|
||||
useMemo(() => {
|
||||
if (!meta) {
|
||||
return {
|
||||
indicesRequiringUpgradeDocsCount: containerMessages.unknownMessage,
|
||||
docsSizeFormatted: containerMessages.unknownMessage,
|
||||
lastIndexCreationDateFormatted: containerMessages.unknownMessage,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
indicesRequiringUpgradeDocsCount:
|
||||
typeof meta.indicesRequiringUpgradeDocsCount === 'number'
|
||||
? `${meta.indicesRequiringUpgradeDocsCount}`
|
||||
: 'Unknown',
|
||||
docsSizeFormatted:
|
||||
typeof meta.indicesRequiringUpgradeDocsSize === 'number'
|
||||
? numeral(meta.indicesRequiringUpgradeDocsSize).format(FILE_SIZE_DISPLAY_FORMAT)
|
||||
: 'Unknown',
|
||||
lastIndexCreationDateFormatted:
|
||||
typeof meta.lastIndexRequiringUpgradeCreationDate === 'number'
|
||||
? `${moment(meta.lastIndexRequiringUpgradeCreationDate).format(DATE_FORMAT)}`
|
||||
: 'Unknown',
|
||||
};
|
||||
}, [meta]);
|
||||
|
||||
const flyoutContents = useMemo(() => {
|
||||
switch (flyoutStep) {
|
||||
case 'initializing':
|
||||
return <InitializingFlyoutStep errorMessage={errorMessage} />;
|
||||
case 'notStarted': {
|
||||
if (!meta) {
|
||||
return (
|
||||
<InitializingFlyoutStep
|
||||
errorMessage={errorMessage || containerMessages.errorLoadingDataStreamInfo}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DataStreamDetailsFlyoutStep
|
||||
closeFlyout={closeFlyout}
|
||||
lastIndexCreationDateFormatted={lastIndexCreationDateFormatted}
|
||||
meta={meta}
|
||||
startReindex={() => {
|
||||
setFlyoutStep('confirm');
|
||||
}}
|
||||
reindexState={reindexState}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'confirm': {
|
||||
if (!meta) {
|
||||
return (
|
||||
<InitializingFlyoutStep
|
||||
errorMessage={errorMessage || containerMessages.errorLoadingDataStreamInfo}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ConfirmReindexingFlyoutStep
|
||||
warnings={reindexWarnings ?? []}
|
||||
meta={meta}
|
||||
hideWarningsStep={() => {
|
||||
setFlyoutStep('notStarted');
|
||||
}}
|
||||
continueReindex={() => {
|
||||
onStartReindex();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'inProgress': {
|
||||
if (!meta) {
|
||||
return (
|
||||
<InitializingFlyoutStep
|
||||
errorMessage={errorMessage || containerMessages.errorLoadingDataStreamInfo}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ChecklistFlyoutStep
|
||||
closeFlyout={closeFlyout}
|
||||
startReindex={() => {
|
||||
setFlyoutStep('confirm');
|
||||
}}
|
||||
reindexState={reindexState}
|
||||
cancelReindex={onStopReindex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
case 'completed': {
|
||||
if (!meta) {
|
||||
return (
|
||||
<InitializingFlyoutStep
|
||||
errorMessage={errorMessage || containerMessages.errorLoadingDataStreamInfo}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <ReindexingCompletedFlyoutStep meta={meta} />;
|
||||
}
|
||||
}
|
||||
}, [
|
||||
flyoutStep,
|
||||
reindexState,
|
||||
closeFlyout,
|
||||
onStartReindex,
|
||||
onStopReindex,
|
||||
lastIndexCreationDateFormatted,
|
||||
reindexWarnings,
|
||||
meta,
|
||||
errorMessage,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{flyoutStep !== 'initializing' && (
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<DeprecationBadge
|
||||
isCritical={deprecation.isCritical}
|
||||
isResolved={status === DataStreamReindexStatus.completed}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiTitle size="s" data-test-subj="flyoutTitle">
|
||||
<h2 id="reindexDetailsFlyoutTitle">{index}</h2>
|
||||
</EuiTitle>
|
||||
{meta && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList
|
||||
textStyle="reverse"
|
||||
listItems={[
|
||||
{
|
||||
title: 'Reindexing required for indices created on or before',
|
||||
description: lastIndexCreationDateFormatted,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList
|
||||
textStyle="reverse"
|
||||
listItems={[
|
||||
{
|
||||
title: 'Size',
|
||||
description: docsSizeFormatted,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiDescriptionList
|
||||
textStyle="reverse"
|
||||
listItems={[
|
||||
{
|
||||
title: 'Document Count',
|
||||
description: indicesRequiringUpgradeDocsCount,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
)}
|
||||
</EuiFlyoutHeader>
|
||||
)}
|
||||
|
||||
{flyoutContents}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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 { DataStreamReindexFlyout } from './container';
|
|
@ -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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DataStreamReindexStatus } from '../../../../../../../common/types';
|
||||
|
||||
export const getPrimaryButtonLabel = (status?: DataStreamReindexStatus) => {
|
||||
switch (status) {
|
||||
case DataStreamReindexStatus.fetchFailed:
|
||||
case DataStreamReindexStatus.failed:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.reindexButton.tryAgainLabel"
|
||||
defaultMessage="Try again"
|
||||
/>
|
||||
);
|
||||
case DataStreamReindexStatus.inProgress:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.reindexButton.reindexingLabel"
|
||||
defaultMessage="Reindexing…"
|
||||
/>
|
||||
);
|
||||
case DataStreamReindexStatus.cancelled:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.reindexButton.restartLabel"
|
||||
defaultMessage="Restart reindexing"
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.reindexButton.runReindexLabel"
|
||||
defaultMessage="Start reindexing"
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const containerMessages = {
|
||||
unknownMessage: i18n.translate('xpack.upgradeAssistant.dataStream.flyout.unknownMessage', {
|
||||
defaultMessage: 'Unknown',
|
||||
}),
|
||||
errorLoadingDataStreamInfo: i18n.translate(
|
||||
'xpack.upgradeAssistant.dataStream.flyout.errorLoadingDataStreamInfo',
|
||||
{
|
||||
defaultMessage: 'Error loading data stream info',
|
||||
}
|
||||
),
|
||||
};
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* 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, { Fragment } from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { DataStreamReindexStatus } from '../../../../../../../../../common/types';
|
||||
import { LoadingState } from '../../../../../../types';
|
||||
import type { ReindexState } from '../../../use_reindex_state';
|
||||
import { ReindexProgress } from './progress';
|
||||
import { useAppContext } from '../../../../../../../app_context';
|
||||
import { getPrimaryButtonLabel } from '../../messages';
|
||||
|
||||
/**
|
||||
* Displays a flyout that shows the current reindexing status for a given index.
|
||||
*/
|
||||
export const ChecklistFlyoutStep: React.FunctionComponent<{
|
||||
closeFlyout: () => void;
|
||||
reindexState: ReindexState;
|
||||
startReindex: () => void;
|
||||
cancelReindex: () => void;
|
||||
}> = ({ closeFlyout, reindexState, startReindex, cancelReindex }) => {
|
||||
const {
|
||||
services: { api },
|
||||
} = useAppContext();
|
||||
|
||||
const { loadingState, status, hasRequiredPrivileges } = reindexState;
|
||||
const loading =
|
||||
loadingState === LoadingState.Loading || status === DataStreamReindexStatus.inProgress;
|
||||
const isCompleted = status === DataStreamReindexStatus.completed;
|
||||
const hasFetchFailed = status === DataStreamReindexStatus.fetchFailed;
|
||||
const hasReindexingFailed = status === DataStreamReindexStatus.failed;
|
||||
|
||||
const { data: nodes } = api.useLoadNodeDiskSpace();
|
||||
|
||||
const showMainButton = !hasFetchFailed && !isCompleted && hasRequiredPrivileges;
|
||||
const shouldShowCancelButton = showMainButton && status === DataStreamReindexStatus.inProgress;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlyoutBody>
|
||||
{hasRequiredPrivileges === false && (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.insufficientPrivilegeCallout.calloutTitle"
|
||||
defaultMessage="You do not have sufficient privileges to reindex this index"
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="warning"
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{nodes && nodes.length > 0 && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
data-test-subj="lowDiskSpaceCallout"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.lowDiskSpaceCalloutTitle"
|
||||
defaultMessage="Nodes with low disk space"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.lowDiskSpaceCalloutDescription"
|
||||
defaultMessage="Disk usage has exceeded the low watermark, which may prevent reindexing. The following nodes are impacted:"
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<ul>
|
||||
{nodes.map(({ nodeName, available, nodeId }) => (
|
||||
<li key={nodeId} data-test-subj="impactedNodeListItem">
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.lowDiskSpaceUsedText"
|
||||
defaultMessage="{nodeName} ({available} available)"
|
||||
values={{
|
||||
nodeName,
|
||||
available,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
|
||||
{(hasFetchFailed || hasReindexingFailed) && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="warning"
|
||||
data-test-subj={hasFetchFailed ? 'fetchFailedCallout' : 'reindexingFailedCallout'}
|
||||
title={
|
||||
hasFetchFailed ? (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.fetchFailedCalloutTitle"
|
||||
defaultMessage="Reindex status not available"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingFailedCalloutTitle"
|
||||
defaultMessage="Reindexing error"
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{reindexState.errorMessage}
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="Reindexing is performed in the background. You can return to the Upgrade Assistant to view progress."
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.readonlyCallout.backgroundResumeDetail"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
<ReindexProgress reindexState={reindexState} />
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="cross" onClick={closeFlyout} flush="left">
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.closeButtonLabel"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
{shouldShowCancelButton && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color={'accent'}
|
||||
iconType={'pause'}
|
||||
onClick={cancelReindex}
|
||||
disabled={!hasRequiredPrivileges}
|
||||
data-test-subj="cancelDataStreamReindexingButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.cancelReindexButtonLabel"
|
||||
defaultMessage="Cancel reindexing"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
{showMainButton && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
fill
|
||||
color={status === DataStreamReindexStatus.inProgress ? 'primary' : 'warning'}
|
||||
iconType={status === DataStreamReindexStatus.inProgress ? undefined : 'refresh'}
|
||||
onClick={startReindex}
|
||||
isLoading={loading}
|
||||
disabled={loading || !hasRequiredPrivileges}
|
||||
data-test-subj="startDataStreamReindexingButton"
|
||||
>
|
||||
{getPrimaryButtonLabel(status)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
|
@ -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 { ChecklistFlyoutStep } from './checklist_step';
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { FormattedMessage, FormattedRelativeTime } from '@kbn/i18n-react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import moment from 'moment';
|
||||
import { DataStreamReindexStatus } from '../../../../../../../../../common/types';
|
||||
import type { ReindexState } from '../../../use_reindex_state';
|
||||
import { StepProgress, StepProgressStep } from '../../../../reindex/flyout/step_progress';
|
||||
import { getDataStreamReindexProgress } from '../../../../../../../lib/utils';
|
||||
import { ReindexingDocumentsStepTitle } from './progress_title';
|
||||
import { CancelLoadingState } from '../../../../../../types';
|
||||
|
||||
interface Props {
|
||||
reindexState: ReindexState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a list of steps in the reindex operation, the current status, a progress bar,
|
||||
* and any error messages that are encountered.
|
||||
*/
|
||||
export const ReindexProgress: React.FunctionComponent<Props> = (props) => {
|
||||
const { status, reindexTaskPercComplete, cancelLoadingState, taskStatus } = props.reindexState;
|
||||
|
||||
// The reindexing step is special because it generally lasts longer and can be cancelled mid-flight
|
||||
const reindexingDocsStep = {
|
||||
title: (
|
||||
<EuiFlexGroup component="span">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ReindexingDocumentsStepTitle {...props} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
} as StepProgressStep;
|
||||
|
||||
const inProgress =
|
||||
status === DataStreamReindexStatus.inProgress || status === DataStreamReindexStatus.completed;
|
||||
|
||||
let euiProgressColor = 'subdued';
|
||||
|
||||
if (cancelLoadingState === CancelLoadingState.Error) {
|
||||
reindexingDocsStep.status = 'failed';
|
||||
euiProgressColor = 'danger';
|
||||
} else if (
|
||||
cancelLoadingState === CancelLoadingState.Loading ||
|
||||
cancelLoadingState === CancelLoadingState.Requested
|
||||
) {
|
||||
reindexingDocsStep.status = 'inProgress';
|
||||
euiProgressColor = 'subdued';
|
||||
} else if (status === DataStreamReindexStatus.failed) {
|
||||
reindexingDocsStep.status = 'failed';
|
||||
euiProgressColor = 'danger';
|
||||
} else if (
|
||||
status === DataStreamReindexStatus.cancelled ||
|
||||
cancelLoadingState === CancelLoadingState.Success
|
||||
) {
|
||||
reindexingDocsStep.status = 'cancelled';
|
||||
} else if (status === undefined) {
|
||||
reindexingDocsStep.status = 'incomplete';
|
||||
euiProgressColor = 'subdued';
|
||||
} else if (status === DataStreamReindexStatus.inProgress) {
|
||||
reindexingDocsStep.status = 'inProgress';
|
||||
euiProgressColor = 'primary';
|
||||
} else if (status === DataStreamReindexStatus.completed) {
|
||||
reindexingDocsStep.status = 'complete';
|
||||
euiProgressColor = 'success';
|
||||
} else {
|
||||
// not started // undefined
|
||||
reindexingDocsStep.status = 'incomplete';
|
||||
euiProgressColor = 'subdued';
|
||||
}
|
||||
|
||||
const progressPercentage = inProgress
|
||||
? getDataStreamReindexProgress(status, reindexTaskPercComplete)
|
||||
: undefined;
|
||||
const showProgressValueText = inProgress;
|
||||
const progressMaxValue = inProgress ? 100 : undefined;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs" data-test-subj="reindexChecklistTitle">
|
||||
<h3>
|
||||
{status === DataStreamReindexStatus.inProgress ? (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingInProgressTitle"
|
||||
defaultMessage="Reindexing in progress…"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklistTitle"
|
||||
defaultMessage="Reindex data stream"
|
||||
/>
|
||||
)}
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{inProgress && (
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiProgress
|
||||
label={
|
||||
taskStatus ? (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingInProgressTitle"
|
||||
defaultMessage="Started {startTimeFromNow}"
|
||||
values={{
|
||||
startTimeFromNow: (
|
||||
<FormattedRelativeTime
|
||||
value={(taskStatus.startTimeMs - +moment()) / 1000}
|
||||
updateIntervalInSeconds={1}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
valueText={showProgressValueText}
|
||||
value={progressPercentage}
|
||||
max={progressMaxValue}
|
||||
color={euiProgressColor}
|
||||
size="m"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<StepProgress steps={[reindexingDocsStep]} />
|
||||
</EuiFlexItem>
|
||||
{inProgress && (
|
||||
<EuiFlexItem>
|
||||
{!taskStatus && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.fetchingStatus"
|
||||
defaultMessage="Fetching Status…"
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
{taskStatus && (
|
||||
<EuiFlexGroup direction="column" gutterSize="xs" style={{ padding: '0 28px' }}>
|
||||
{taskStatus.errorsCount > 0 && (
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s" color="danger">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.progressStep.failedTitle',
|
||||
{
|
||||
defaultMessage:
|
||||
'{count, plural, =1 {# Index} other {# Indices}} failed to reindex.',
|
||||
values: { count: taskStatus.errorsCount },
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s" color="success">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.progressStep.completeTitle',
|
||||
{
|
||||
defaultMessage:
|
||||
'{count, plural, =1 {# Index} other {# Indices}} successfully reindexed.',
|
||||
values: { count: taskStatus.successCount },
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s" color="primary">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.progressStep.inProgressTitle',
|
||||
{
|
||||
defaultMessage:
|
||||
'{count, plural, =1 {# Index} other {# Indices}} currently reindexing.',
|
||||
values: { count: taskStatus.inProgressCount },
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
{i18n.translate(
|
||||
'xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.progressStep.pendingTitle',
|
||||
{
|
||||
defaultMessage:
|
||||
'{count, plural, =1 {# Index} other {# Indices}} waiting to start.',
|
||||
values: { count: taskStatus.pendingCount },
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { CancelLoadingState } from '../../../../../../types';
|
||||
import { DataStreamReindexStatus } from '../../../../../../../../../common/types';
|
||||
import type { ReindexState } from '../../../use_reindex_state';
|
||||
|
||||
export const ReindexingDocumentsStepTitle: React.FunctionComponent<{
|
||||
reindexState: ReindexState;
|
||||
}> = ({ reindexState: { status, cancelLoadingState } }) => {
|
||||
switch (cancelLoadingState) {
|
||||
case CancelLoadingState.Requested:
|
||||
case CancelLoadingState.Loading: {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklist.cancelButton.cancellingLabel"
|
||||
defaultMessage="Cancelling…"
|
||||
/>
|
||||
);
|
||||
}
|
||||
case CancelLoadingState.Success: {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklist.cancelButton.cancelledLabel"
|
||||
defaultMessage="Cancelled"
|
||||
/>
|
||||
);
|
||||
}
|
||||
case CancelLoadingState.Error: {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklist.cancelButton.errorLabel"
|
||||
defaultMessage="Failed to cancel reindexing"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case DataStreamReindexStatus.inProgress: {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklist.inProgress.reindexingDocumentsStepTitle"
|
||||
defaultMessage="Reindexing data stream"
|
||||
/>
|
||||
);
|
||||
}
|
||||
case DataStreamReindexStatus.failed:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklist.failed.reindexingDocumentsStepTitle"
|
||||
defaultMessage="Reindexing failed"
|
||||
/>
|
||||
);
|
||||
case DataStreamReindexStatus.fetchFailed:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklist.fetchFailed.reindexingDocumentsStepTitle"
|
||||
defaultMessage="Fetching status failed"
|
||||
/>
|
||||
);
|
||||
case DataStreamReindexStatus.cancelled:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklist.cancelled.reindexingDocumentsStepTitle"
|
||||
defaultMessage="Reindexing cancelled"
|
||||
/>
|
||||
);
|
||||
case DataStreamReindexStatus.completed:
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklist.completed.reindexingDocumentsStepTitle"
|
||||
defaultMessage="Reindexing completed"
|
||||
/>
|
||||
);
|
||||
case DataStreamReindexStatus.notStarted:
|
||||
default: {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.reindexingChecklist.inProgress.reindexingDocumentsStepTitle"
|
||||
defaultMessage="Reindex data stream"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { EuiFlyoutBody, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { DataStreamMetadata } from '../../../../../../../../../common/types';
|
||||
|
||||
interface Props {
|
||||
meta: DataStreamMetadata;
|
||||
}
|
||||
|
||||
export const ReindexingCompletedFlyoutStep: React.FunctionComponent<Props> = ({ meta }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutBody>
|
||||
<EuiTitle size="s">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.warningsStep.acceptChangesTitle"
|
||||
defaultMessage="Data Stream Reindexing Complete"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.warningsStep.acceptChangesTitle"
|
||||
defaultMessage="Success! {count, plural, =1 {# backing index} other {# backing indices}} successfully reindexed."
|
||||
values={{ count: meta.indicesRequiringUpgradeCount }}
|
||||
/>
|
||||
</p>
|
||||
</EuiFlyoutBody>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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 { ReindexingCompletedFlyoutStep } from './completed_step';
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import {
|
||||
DataStreamReindexWarning,
|
||||
DataStreamReindexWarningTypes,
|
||||
DataStreamMetadata,
|
||||
} from '../../../../../../../../../common/types';
|
||||
import { useAppContext } from '../../../../../../../app_context';
|
||||
import {
|
||||
IncompatibleDataInDataStreamWarningCheckbox,
|
||||
WarningCheckboxProps,
|
||||
} from './warning_step_checkbox';
|
||||
|
||||
interface CheckedIds {
|
||||
[id: string]: boolean;
|
||||
}
|
||||
|
||||
const warningToComponentMap: Record<
|
||||
DataStreamReindexWarningTypes,
|
||||
React.FunctionComponent<WarningCheckboxProps>
|
||||
> = {
|
||||
incompatibleDataStream: IncompatibleDataInDataStreamWarningCheckbox,
|
||||
};
|
||||
|
||||
export const idForWarning = (id: number) => `reindexWarning-${id}`;
|
||||
interface WarningsConfirmationFlyoutProps {
|
||||
hideWarningsStep: () => void;
|
||||
continueReindex: () => void;
|
||||
warnings: DataStreamReindexWarning[];
|
||||
meta: DataStreamMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays warning text about destructive changes required to reindex this index. The user
|
||||
* must acknowledge each change before being allowed to proceed.
|
||||
*/
|
||||
export const ConfirmReindexingFlyoutStep: React.FunctionComponent<
|
||||
WarningsConfirmationFlyoutProps
|
||||
> = ({ warnings, hideWarningsStep, continueReindex, meta }) => {
|
||||
const {
|
||||
services: {
|
||||
core: { docLinks },
|
||||
},
|
||||
} = useAppContext();
|
||||
const { links } = docLinks;
|
||||
|
||||
const [checkedIds, setCheckedIds] = useState<CheckedIds>(
|
||||
warnings.reduce((initialCheckedIds, warning, index) => {
|
||||
initialCheckedIds[idForWarning(index)] = false;
|
||||
return initialCheckedIds;
|
||||
}, {} as { [id: string]: boolean })
|
||||
);
|
||||
|
||||
// Do not allow to proceed until all checkboxes are checked.
|
||||
const blockAdvance = Object.values(checkedIds).filter((v) => v).length < warnings.length;
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const optionId = e.target.id;
|
||||
|
||||
setCheckedIds((prev) => ({
|
||||
...prev,
|
||||
...{
|
||||
[optionId]: !checkedIds[optionId],
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutBody>
|
||||
{warnings.length > 0 && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.warningsStep.destructiveCallout.calloutTitle"
|
||||
defaultMessage="This operation requires destructive changes that cannot be reversed"
|
||||
/>
|
||||
}
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.warningsStep.destructiveCallout.calloutDetail"
|
||||
defaultMessage="Ensure data has been backed up before continuing. To proceed with reindexing this data, confirm below."
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.dataStreamReindexing.flyout.warningsStep.acceptChangesTitle"
|
||||
defaultMessage="{count, plural, =1 {# backing index} other {# backing indices}}, including current write index, will be re-indexed. Current write index will be rolled over first."
|
||||
values={{ count: meta.indicesRequiringUpgradeCount }}
|
||||
/>
|
||||
</p>
|
||||
<EuiSpacer size="m" />
|
||||
{warnings.map((warning, index) => {
|
||||
const WarningCheckbox = warningToComponentMap[warning.warningType];
|
||||
return (
|
||||
<WarningCheckbox
|
||||
key={idForWarning(index)}
|
||||
isChecked={checkedIds[idForWarning(index)]}
|
||||
onChange={onChange}
|
||||
docLinks={links}
|
||||
id={idForWarning(index)}
|
||||
// @ts-ignore
|
||||
meta={{ ...meta, ...warning.meta }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="arrowLeft" onClick={hideWarningsStep} flush="left">
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.backButtonLabel"
|
||||
defaultMessage="Back"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton fill color="primary" onClick={continueReindex} disabled={blockAdvance}>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.checklistStep.startReindexingButtonLabel"
|
||||
defaultMessage="Start reindexing"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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 { ConfirmReindexingFlyoutStep } from './confirm_step';
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
EuiCheckbox,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DocLinksStart } from '@kbn/core/public';
|
||||
import {
|
||||
DataStreamReindexWarning,
|
||||
DataStreamReindexWarningTypes,
|
||||
} from '../../../../../../../../../common/types';
|
||||
|
||||
export const hasReindexWarning = (
|
||||
warnings: DataStreamReindexWarning[],
|
||||
warningType: DataStreamReindexWarningTypes
|
||||
): boolean => {
|
||||
return Boolean(warnings.find((warning) => warning.warningType === warningType));
|
||||
};
|
||||
|
||||
const WarningCheckbox: React.FunctionComponent<{
|
||||
isChecked: boolean;
|
||||
warningId: string;
|
||||
label: React.ReactNode;
|
||||
description: React.ReactNode;
|
||||
documentationUrl?: string;
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
}> = ({ isChecked, warningId, label, onChange, description, documentationUrl }) => (
|
||||
<>
|
||||
<EuiText>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiCheckbox
|
||||
id={warningId}
|
||||
label={<strong>{label}</strong>}
|
||||
checked={isChecked}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
{documentationUrl !== undefined && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink href={documentationUrl} target="_blank" external={false}>
|
||||
<EuiIconTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.warningsStep.documentationLinkLabel"
|
||||
defaultMessage="Documentation"
|
||||
/>
|
||||
}
|
||||
position="right"
|
||||
type="help"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="xs" />
|
||||
|
||||
{description}
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
|
||||
export interface WarningCheckboxProps {
|
||||
isChecked: boolean;
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
docLinks: DocLinksStart['links'];
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const IncompatibleDataInDataStreamWarningCheckbox: React.FunctionComponent<
|
||||
WarningCheckboxProps
|
||||
> = ({ isChecked, onChange, id }) => {
|
||||
return (
|
||||
<WarningCheckbox
|
||||
isChecked={isChecked}
|
||||
onChange={onChange}
|
||||
warningId={id}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.warningsStep.incompatibleDataWarningTitle"
|
||||
defaultMessage="Reindex all incompatible data for this data stream"
|
||||
/>
|
||||
}
|
||||
description={null}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* 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, { Fragment } from 'react';
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import {
|
||||
DataStreamMetadata,
|
||||
DataStreamReindexStatus,
|
||||
} from '../../../../../../../../../common/types';
|
||||
import { LoadingState } from '../../../../../../types';
|
||||
import type { ReindexState } from '../../../use_reindex_state';
|
||||
import { useAppContext } from '../../../../../../../app_context';
|
||||
import { DurationClarificationCallOut } from './warnings_callout';
|
||||
import { getPrimaryButtonLabel } from '../../messages';
|
||||
|
||||
/**
|
||||
* Displays a flyout that shows the current reindexing status for a given index.
|
||||
*/
|
||||
|
||||
export const DataStreamDetailsFlyoutStep: React.FunctionComponent<{
|
||||
closeFlyout: () => void;
|
||||
reindexState: ReindexState;
|
||||
startReindex: () => void;
|
||||
lastIndexCreationDateFormatted: string;
|
||||
meta: DataStreamMetadata;
|
||||
}> = ({ closeFlyout, reindexState, startReindex, lastIndexCreationDateFormatted, meta }) => {
|
||||
const {
|
||||
services: {
|
||||
api,
|
||||
core: { http },
|
||||
},
|
||||
} = useAppContext();
|
||||
|
||||
const { loadingState, status, hasRequiredPrivileges } = reindexState;
|
||||
const loading =
|
||||
loadingState === LoadingState.Loading || status === DataStreamReindexStatus.inProgress;
|
||||
const isCompleted = status === DataStreamReindexStatus.completed;
|
||||
const hasFetchFailed = status === DataStreamReindexStatus.fetchFailed;
|
||||
const hasReindexingFailed = status === DataStreamReindexStatus.failed;
|
||||
|
||||
const { data: nodes } = api.useLoadNodeDiskSpace();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlyoutBody>
|
||||
<DurationClarificationCallOut
|
||||
formattedDate={lastIndexCreationDateFormatted}
|
||||
learnMoreUrl={meta.documentationUrl}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
{hasRequiredPrivileges === false && (
|
||||
<Fragment>
|
||||
<EuiSpacer />
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.insufficientPrivilegeCallout.calloutTitle"
|
||||
defaultMessage="You do not have sufficient privileges to reindex this data stream."
|
||||
/>
|
||||
}
|
||||
color="danger"
|
||||
iconType="warning"
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
{nodes && nodes.length > 0 && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
iconType="warning"
|
||||
data-test-subj="lowDiskSpaceCallout"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.lowDiskSpaceCalloutTitle"
|
||||
defaultMessage="Nodes with low disk space"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.lowDiskSpaceCalloutDescription"
|
||||
defaultMessage="Disk usage has exceeded the low watermark, which may prevent reindexing. The following nodes are impacted:"
|
||||
/>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<ul>
|
||||
{nodes.map(({ nodeName, available, nodeId }) => (
|
||||
<li key={nodeId} data-test-subj="impactedNodeListItem">
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.lowDiskSpaceUsedText"
|
||||
defaultMessage="{nodeName} ({available} available)"
|
||||
values={{
|
||||
nodeName,
|
||||
available,
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
|
||||
{(hasFetchFailed || hasReindexingFailed) && (
|
||||
<>
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="warning"
|
||||
data-test-subj={hasFetchFailed ? 'fetchFailedCallout' : 'reindexingFailedCallout'}
|
||||
title={
|
||||
hasFetchFailed ? (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.fetchFailedCalloutTitle"
|
||||
defaultMessage="Data stream reindex status not available"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.reindexingFailedCalloutTitle"
|
||||
defaultMessage="Data stream reindexing error"
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{reindexState.errorMessage}
|
||||
</EuiCallOut>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
)}
|
||||
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.notCompatibleIndicesText"
|
||||
defaultMessage="You have {backingIndicesCount} backing indices on this data stream that were created in ES 7.x and will not be compatible with next version."
|
||||
values={{
|
||||
backingIndicesCount: meta.indicesRequiringUpgradeCount,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.requiredUpgradeText"
|
||||
defaultMessage="{allBackingIndices} total backing indices, and {backingIndicesRequireingUpgrade} requires upgrade."
|
||||
values={{
|
||||
allBackingIndices: meta.allIndicesCount,
|
||||
backingIndicesRequireingUpgrade: meta.indicesRequiringUpgradeCount,
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<ul>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.readOnlyText"
|
||||
tagName="li"
|
||||
defaultMessage="If you do not need to update historical data, mark as read-only. You can reindex post-upgrade if updates are needed."
|
||||
/>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.reindexOptionListTitle"
|
||||
defaultMessage="Reindex"
|
||||
/>
|
||||
<ul>
|
||||
<FormattedMessage
|
||||
tagName="li"
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.reindexOption.rolledOverIndex"
|
||||
defaultMessage="The current write index will be rolled over and reindexed."
|
||||
/>
|
||||
<FormattedMessage
|
||||
tagName="li"
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.reindexOption.additionalIndices"
|
||||
defaultMessage="Additional backing indices will be reindexed and remain editable."
|
||||
/>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.reindexDescription"
|
||||
defaultMessage="If you no longer need this data, you can also proceed by deleting these indices. {indexManagementLinkHtml}"
|
||||
values={{
|
||||
indexManagementLinkHtml: (
|
||||
<EuiLink
|
||||
href={`${http.basePath.prepend(
|
||||
'/app/management/data/index_management/indices'
|
||||
)}`}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.indexMgmtLink"
|
||||
defaultMessage="Go to index management"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer />
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty iconType="cross" onClick={closeFlyout} flush="left">
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.detailsStep.closeButtonLabel"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
{!hasFetchFailed && !isCompleted && hasRequiredPrivileges && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color={status === DataStreamReindexStatus.cancelled ? 'warning' : 'primary'}
|
||||
iconType={status === DataStreamReindexStatus.cancelled ? 'play' : undefined}
|
||||
onClick={startReindex}
|
||||
isLoading={loading}
|
||||
disabled={loading || !hasRequiredPrivileges}
|
||||
data-test-subj="startReindexingButton"
|
||||
>
|
||||
{getPrimaryButtonLabel(status)}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
|
@ -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 { DataStreamDetailsFlyoutStep } from './details_step';
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiCallOut, EuiLink } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
interface Props {
|
||||
formattedDate: string;
|
||||
learnMoreUrl: string;
|
||||
}
|
||||
export const DurationClarificationCallOut: React.FunctionComponent<Props> = ({
|
||||
formattedDate,
|
||||
learnMoreUrl,
|
||||
}) => {
|
||||
return (
|
||||
<EuiCallOut color="primary">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.warningsStep.indicesNeedReindexing"
|
||||
defaultMessage="Indices created on or before {formattedDate} need to be reindexed to a compatible format or marked as read-only."
|
||||
values={{ formattedDate }}
|
||||
/>
|
||||
<br />
|
||||
<br />
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.warningsStep.suggestReadOnly"
|
||||
defaultMessage="Depending on size and resources, reindexing may take extended time and your data will be in a read-only state until the job has completed. {learnMoreHtml}"
|
||||
values={{
|
||||
learnMoreHtml: (
|
||||
<EuiLink href={learnMoreUrl} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.warningsStep.learnMoreLink"
|
||||
defaultMessage="Learn more"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -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 { InitializingFlyoutStep } from './initializing_step';
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutBody,
|
||||
EuiIcon,
|
||||
EuiLoadingSpinner,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
interface InitializingFlyoutStepProps {
|
||||
errorMessage?: string | null;
|
||||
}
|
||||
|
||||
export const InitializingFlyoutStep: React.FunctionComponent<InitializingFlyoutStepProps> = ({
|
||||
errorMessage,
|
||||
}) => {
|
||||
const hasInitializingError = !!errorMessage;
|
||||
return (
|
||||
<>
|
||||
<EuiFlyoutBody>
|
||||
<EuiSpacer size="xxl" />
|
||||
<EuiSpacer size="xxl" />
|
||||
<EuiFlexGroup direction="column" alignItems="center" justifyContent="center">
|
||||
<EuiFlexItem>
|
||||
{hasInitializingError ? (
|
||||
<EuiIcon type="alert" size="xl" color="danger" />
|
||||
) : (
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s">
|
||||
{hasInitializingError ? (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.initializingStep.errorLoadingDataStreamInfo"
|
||||
defaultMessage="Error loading data stream info"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.dataStream.reindexing.flyout.initializingStep.loadingDataStreamInfo"
|
||||
defaultMessage="Loading Data stream info"
|
||||
/>
|
||||
)}
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{hasInitializingError && (
|
||||
<EuiFlexItem>
|
||||
<EuiText>{errorMessage}</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutBody>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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 type FlyoutStep = 'initializing' | 'notStarted' | 'confirm' | 'inProgress' | 'completed';
|
|
@ -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 { DataStreamTableRow } from './table_row';
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
EuiIcon,
|
||||
EuiLoadingSpinner,
|
||||
EuiText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { DataStreamReindexStatus } from '../../../../../../common/types';
|
||||
import { getDataStreamReindexProgressLabel } from '../../../../lib/utils';
|
||||
import { LoadingState } from '../../../types';
|
||||
import { useDataStreamReindexContext } from './context';
|
||||
|
||||
const i18nTexts = {
|
||||
reindexLoadingStatusText: i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecations.dataStream.reindexLoadingStatusText',
|
||||
{
|
||||
defaultMessage: 'Loading status…',
|
||||
}
|
||||
),
|
||||
reindexInProgressText: i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecations.dataStream.reindexInProgressText',
|
||||
{
|
||||
defaultMessage: 'Reindexing in progress…',
|
||||
}
|
||||
),
|
||||
reindexCompleteText: i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecations.dataStream.reindexCompleteText',
|
||||
{
|
||||
defaultMessage: 'Reindex complete',
|
||||
}
|
||||
),
|
||||
reindexFailedText: i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecations.dataStream.reindexFailedText',
|
||||
{
|
||||
defaultMessage: 'Reindex failed',
|
||||
}
|
||||
),
|
||||
reindexFetchFailedText: i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecations.dataStream.reindexFetchFailedText',
|
||||
{
|
||||
defaultMessage: 'Reindex status not available',
|
||||
}
|
||||
),
|
||||
reindexCanceledText: i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecations.dataStream.reindexCanceledText',
|
||||
{
|
||||
defaultMessage: 'Reindex cancelled',
|
||||
}
|
||||
),
|
||||
resolutionText: i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecations.dataStream.resolutionLabel',
|
||||
{
|
||||
defaultMessage: 'Reindex',
|
||||
}
|
||||
),
|
||||
resolutionTooltipLabel: i18n.translate(
|
||||
'xpack.upgradeAssistant.esDeprecations.dataStream.resolutionTooltipLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'Resolve this issue by reindexing this data stream. This issue can be resolved automatically.',
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const DataStreamReindexResolutionCell: React.FunctionComponent = () => {
|
||||
const { reindexState } = useDataStreamReindexContext();
|
||||
|
||||
if (reindexState.loadingState === LoadingState.Loading) {
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{i18nTexts.reindexLoadingStatusText}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
switch (reindexState.status) {
|
||||
case DataStreamReindexStatus.inProgress:
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="m" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">
|
||||
{i18nTexts.reindexInProgressText}{' '}
|
||||
{getDataStreamReindexProgressLabel(
|
||||
reindexState.status,
|
||||
reindexState.reindexTaskPercComplete
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
case DataStreamReindexStatus.completed:
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="check" color="success" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{i18nTexts.reindexCompleteText}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
case DataStreamReindexStatus.failed:
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="warning" color="danger" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{i18nTexts.reindexFailedText}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
case DataStreamReindexStatus.fetchFailed:
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="warning" color="danger" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{i18nTexts.reindexFetchFailedText}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<EuiToolTip position="top" content={i18nTexts.resolutionTooltipLabel}>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="indexSettings" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="s">{i18nTexts.resolutionText}</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiToolTip>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { EuiTableRowCell } from '@elastic/eui';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { EnrichedDeprecationInfo } from '../../../../../../common/types';
|
||||
import { GlobalFlyout } from '../../../../../shared_imports';
|
||||
import { useAppContext } from '../../../../app_context';
|
||||
import {
|
||||
uiMetricService,
|
||||
UIM_DATA_STREAM_REINDEX_CLOSE_FLYOUT_CLICK,
|
||||
UIM_DATA_STREAM_REINDEX_OPEN_FLYOUT_CLICK,
|
||||
} from '../../../../lib/ui_metric';
|
||||
import { DeprecationTableColumns } from '../../../types';
|
||||
import { EsDeprecationsTableCells } from '../../es_deprecations_table_cells';
|
||||
import { DataStreamReindexResolutionCell } from './resolution_table_cell';
|
||||
import { DataStreamReindexFlyout } from './flyout';
|
||||
import { DataStreamReindexStatusProvider, useDataStreamReindexContext } from './context';
|
||||
|
||||
const { useGlobalFlyout } = GlobalFlyout;
|
||||
|
||||
interface TableRowProps {
|
||||
deprecation: EnrichedDeprecationInfo;
|
||||
rowFieldNames: DeprecationTableColumns[];
|
||||
}
|
||||
|
||||
const DataStreamTableRowCells: React.FunctionComponent<TableRowProps> = ({
|
||||
rowFieldNames,
|
||||
deprecation,
|
||||
}) => {
|
||||
const [showFlyout, setShowFlyout] = useState(false);
|
||||
const dataStreamContext = useDataStreamReindexContext();
|
||||
const { addContent: addContentToGlobalFlyout, removeContent: removeContentFromGlobalFlyout } =
|
||||
useGlobalFlyout();
|
||||
|
||||
const closeFlyout = useCallback(async () => {
|
||||
removeContentFromGlobalFlyout('dataStreamReindexFlyout');
|
||||
setShowFlyout(false);
|
||||
uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_DATA_STREAM_REINDEX_CLOSE_FLYOUT_CLICK);
|
||||
}, [removeContentFromGlobalFlyout]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showFlyout) {
|
||||
addContentToGlobalFlyout({
|
||||
id: 'dataStreamReindexFlyout',
|
||||
Component: DataStreamReindexFlyout,
|
||||
props: {
|
||||
...dataStreamContext,
|
||||
deprecation,
|
||||
closeFlyout,
|
||||
},
|
||||
flyoutProps: {
|
||||
onClose: closeFlyout,
|
||||
className: 'eui-textBreakWord',
|
||||
'data-test-subj': 'reindexDetails',
|
||||
'aria-labelledby': 'reindexDetailsFlyoutTitle',
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [addContentToGlobalFlyout, deprecation, dataStreamContext, showFlyout, closeFlyout]);
|
||||
|
||||
useEffect(() => {
|
||||
if (showFlyout) {
|
||||
uiMetricService.trackUiMetric(METRIC_TYPE.CLICK, UIM_DATA_STREAM_REINDEX_OPEN_FLYOUT_CLICK);
|
||||
}
|
||||
}, [showFlyout]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{rowFieldNames.map((field: DeprecationTableColumns) => {
|
||||
return (
|
||||
<EuiTableRowCell
|
||||
key={field}
|
||||
truncateText={false}
|
||||
data-test-subj={`dataStreamReindexTableCell-${field}`}
|
||||
>
|
||||
<EsDeprecationsTableCells
|
||||
fieldName={field}
|
||||
openFlyout={() => setShowFlyout(true)}
|
||||
deprecation={deprecation}
|
||||
resolutionTableCell={<DataStreamReindexResolutionCell />}
|
||||
/>
|
||||
</EuiTableRowCell>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DataStreamTableRow: React.FunctionComponent<TableRowProps> = (props) => {
|
||||
const {
|
||||
services: { api },
|
||||
} = useAppContext();
|
||||
|
||||
return (
|
||||
<DataStreamReindexStatusProvider dataStreamName={props.deprecation.index!} api={api}>
|
||||
<DataStreamTableRowCells {...props} />
|
||||
</DataStreamReindexStatusProvider>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useRef, useCallback, useState, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
DataStreamReindexStatus,
|
||||
DataStreamReindexWarning,
|
||||
DataStreamMetadata,
|
||||
DataStreamReindexStatusResponse,
|
||||
DataStreamProgressDetails,
|
||||
} from '../../../../../../common/types';
|
||||
import { CancelLoadingState, LoadingState } from '../../../types';
|
||||
import { ApiService } from '../../../../lib/api';
|
||||
|
||||
const POLL_INTERVAL = 1000;
|
||||
|
||||
export interface ReindexState {
|
||||
loadingState: LoadingState;
|
||||
cancelLoadingState?: CancelLoadingState;
|
||||
|
||||
status?: DataStreamReindexStatus;
|
||||
reindexTaskPercComplete: number | null;
|
||||
errorMessage: string | null;
|
||||
reindexWarnings?: DataStreamReindexWarning[];
|
||||
hasRequiredPrivileges?: boolean;
|
||||
taskStatus?: DataStreamProgressDetails;
|
||||
|
||||
meta: DataStreamMetadata | null;
|
||||
}
|
||||
|
||||
const getReindexState = (
|
||||
reindexState: ReindexState,
|
||||
{
|
||||
reindexOp,
|
||||
warnings,
|
||||
hasRequiredPrivileges,
|
||||
meta: updatedMeta,
|
||||
}: DataStreamReindexStatusResponse & { meta?: DataStreamMetadata | null }
|
||||
) => {
|
||||
const newReindexState: ReindexState = {
|
||||
...reindexState,
|
||||
|
||||
reindexWarnings: warnings,
|
||||
meta: updatedMeta || reindexState.meta,
|
||||
loadingState: LoadingState.Success,
|
||||
};
|
||||
|
||||
if (warnings) {
|
||||
newReindexState.reindexWarnings = warnings;
|
||||
}
|
||||
|
||||
if (hasRequiredPrivileges !== undefined) {
|
||||
newReindexState.hasRequiredPrivileges = hasRequiredPrivileges;
|
||||
}
|
||||
|
||||
if (reindexOp) {
|
||||
newReindexState.status = reindexOp.status;
|
||||
|
||||
if (reindexOp.status === DataStreamReindexStatus.notStarted) {
|
||||
return newReindexState;
|
||||
}
|
||||
|
||||
if (reindexOp.status === DataStreamReindexStatus.failed) {
|
||||
newReindexState.errorMessage = reindexOp.errorMessage;
|
||||
return newReindexState;
|
||||
}
|
||||
|
||||
if (
|
||||
reindexOp.status === DataStreamReindexStatus.inProgress ||
|
||||
reindexOp.status === DataStreamReindexStatus.completed
|
||||
) {
|
||||
newReindexState.taskStatus = reindexOp.progressDetails;
|
||||
newReindexState.reindexTaskPercComplete = reindexOp.reindexTaskPercComplete;
|
||||
}
|
||||
|
||||
if (
|
||||
reindexState.cancelLoadingState === CancelLoadingState.Requested &&
|
||||
reindexOp.status === DataStreamReindexStatus.inProgress
|
||||
) {
|
||||
newReindexState.cancelLoadingState = CancelLoadingState.Loading;
|
||||
}
|
||||
}
|
||||
|
||||
return newReindexState;
|
||||
};
|
||||
|
||||
export const useReindexStatus = ({
|
||||
dataStreamName,
|
||||
api,
|
||||
}: {
|
||||
dataStreamName: string;
|
||||
api: ApiService;
|
||||
}) => {
|
||||
const [reindexState, setReindexState] = useState<ReindexState>({
|
||||
loadingState: LoadingState.Loading,
|
||||
errorMessage: null,
|
||||
reindexTaskPercComplete: null,
|
||||
taskStatus: undefined,
|
||||
meta: null,
|
||||
});
|
||||
|
||||
const pollIntervalIdRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const isMounted = useRef(false);
|
||||
|
||||
const clearPollInterval = useCallback(() => {
|
||||
if (pollIntervalIdRef.current) {
|
||||
clearTimeout(pollIntervalIdRef.current);
|
||||
pollIntervalIdRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const updateStatus = useCallback(async () => {
|
||||
clearPollInterval();
|
||||
try {
|
||||
const { data, error } = await api.getDataStreamReindexStatus(dataStreamName);
|
||||
|
||||
if (error) {
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return {
|
||||
...prevValue,
|
||||
loadingState: LoadingState.Error,
|
||||
errorMessage: error.message.toString(),
|
||||
status: DataStreamReindexStatus.fetchFailed,
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (data === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return getReindexState(prevValue, data);
|
||||
});
|
||||
|
||||
if (data.reindexOp && data.reindexOp.status === DataStreamReindexStatus.inProgress) {
|
||||
// Only keep polling if it exists and is in progress.
|
||||
pollIntervalIdRef.current = setTimeout(updateStatus, POLL_INTERVAL);
|
||||
}
|
||||
} catch (error) {
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return {
|
||||
...prevValue,
|
||||
loadingState: LoadingState.Error,
|
||||
errorMessage: error.message.toString(),
|
||||
status: DataStreamReindexStatus.fetchFailed,
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [clearPollInterval, api, dataStreamName]);
|
||||
|
||||
const startReindex = useCallback(async () => {
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return {
|
||||
...prevValue,
|
||||
status: DataStreamReindexStatus.inProgress,
|
||||
reindexTaskPercComplete: null,
|
||||
errorMessage: null,
|
||||
cancelLoadingState: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
if (reindexState.status === DataStreamReindexStatus.failed) {
|
||||
try {
|
||||
await api.cancelDataStreamReindexTask(dataStreamName);
|
||||
} catch (_) {
|
||||
// if the task has already failed, attempt to cancel the task
|
||||
// before attempting to start the reindexing again.
|
||||
}
|
||||
}
|
||||
|
||||
const { data: reindexOp, error } = await api.startDataStreamReindexTask(dataStreamName);
|
||||
|
||||
if (error) {
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return {
|
||||
...prevValue,
|
||||
loadingState: LoadingState.Error,
|
||||
errorMessage: error.message.toString(),
|
||||
status: DataStreamReindexStatus.failed,
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return getReindexState(prevValue, { reindexOp, meta: prevValue.meta });
|
||||
});
|
||||
updateStatus();
|
||||
}, [api, dataStreamName, updateStatus, reindexState.status]);
|
||||
|
||||
const loadDataStreamMetadata = useCallback(async () => {
|
||||
try {
|
||||
const { data, error } = await api.getDataStreamMetadata(dataStreamName);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return {
|
||||
...prevValue,
|
||||
loadingState: LoadingState.Success,
|
||||
meta: data || null,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
// if state is completed, we don't need to update the meta
|
||||
if (prevValue.status === DataStreamReindexStatus.completed) {
|
||||
return prevValue;
|
||||
}
|
||||
|
||||
return {
|
||||
...prevValue,
|
||||
loadingState: LoadingState.Error,
|
||||
errorMessage: error.message.toString(),
|
||||
status: DataStreamReindexStatus.failed,
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [api, dataStreamName]);
|
||||
|
||||
const cancelReindex = useCallback(async () => {
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return {
|
||||
...prevValue,
|
||||
cancelLoadingState: CancelLoadingState.Requested,
|
||||
};
|
||||
});
|
||||
try {
|
||||
const { error } = await api.cancelDataStreamReindexTask(dataStreamName);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return {
|
||||
...prevValue,
|
||||
cancelLoadingState: CancelLoadingState.Success,
|
||||
status: DataStreamReindexStatus.cancelled,
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
setReindexState((prevValue: ReindexState) => {
|
||||
return {
|
||||
...prevValue,
|
||||
cancelLoadingState: CancelLoadingState.Error,
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [api, dataStreamName]);
|
||||
|
||||
useEffect(() => {
|
||||
updateStatus();
|
||||
}, [updateStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
|
||||
// Clean up on unmount.
|
||||
clearPollInterval();
|
||||
};
|
||||
}, [clearPollInterval]);
|
||||
|
||||
return {
|
||||
reindexState,
|
||||
loadDataStreamMetadata,
|
||||
|
||||
startReindex,
|
||||
cancelReindex,
|
||||
updateStatus,
|
||||
};
|
||||
};
|
|
@ -9,5 +9,6 @@ export { MlSnapshotsTableRow } from './ml_snapshots';
|
|||
export { IndexSettingsTableRow } from './index_settings';
|
||||
export { DefaultTableRow } from './default';
|
||||
export { ReindexTableRow } from './reindex';
|
||||
export { DataStreamTableRow } from './data_streams';
|
||||
export { ClusterSettingsTableRow } from './cluster_settings';
|
||||
export { HealthIndicatorTableRow } from './health_indicator';
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
ReindexTableRow,
|
||||
ClusterSettingsTableRow,
|
||||
HealthIndicatorTableRow,
|
||||
DataStreamTableRow,
|
||||
} from './deprecation_types';
|
||||
import { DeprecationTableColumns } from '../types';
|
||||
import { DEPRECATION_TYPE_MAP, PAGINATION_CONFIG } from '../constants';
|
||||
|
@ -130,6 +131,9 @@ const renderTableRowCells = (
|
|||
case 'healthIndicator':
|
||||
return <HealthIndicatorTableRow deprecation={deprecation} rowFieldNames={cellTypes} />;
|
||||
|
||||
case 'dataStream':
|
||||
return <DataStreamTableRow deprecation={deprecation} rowFieldNames={cellTypes} />;
|
||||
|
||||
default:
|
||||
return <DefaultTableRow deprecation={deprecation} rowFieldNames={cellTypes} />;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
ResponseError,
|
||||
SystemIndicesMigrationStatus,
|
||||
ReindexStatusResponse,
|
||||
DataStreamReindexStatusResponse,
|
||||
DataStreamMetadata,
|
||||
} from '../../../common/types';
|
||||
import {
|
||||
API_BASE_PATH,
|
||||
|
@ -209,6 +211,41 @@ export class ApiService {
|
|||
});
|
||||
}
|
||||
|
||||
public async getDataStreamReindexStatus(dataStreamName: string) {
|
||||
return await this.sendRequest<DataStreamReindexStatusResponse>({
|
||||
path: `${API_BASE_PATH}/reindex_data_streams/${dataStreamName}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
public async getDataStreamMetadata(dataStreamName: string) {
|
||||
return await this.sendRequest<DataStreamMetadata>({
|
||||
path: `${API_BASE_PATH}/reindex_data_streams/${dataStreamName}/metadata`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
public async startDataStreamReindexTask(dataStreamName: string) {
|
||||
return await this.sendRequest({
|
||||
path: `${API_BASE_PATH}/reindex_data_streams/${dataStreamName}`,
|
||||
method: 'post',
|
||||
});
|
||||
}
|
||||
|
||||
public async cancelDataStreamReindexTask(dataStreamName: string) {
|
||||
return await this.sendRequest({
|
||||
path: `${API_BASE_PATH}/reindex_data_streams/${dataStreamName}/cancel`,
|
||||
method: 'post',
|
||||
});
|
||||
}
|
||||
|
||||
public async pauseDataStreamReindexTask(dataStreamName: string) {
|
||||
return await this.sendRequest({
|
||||
path: `${API_BASE_PATH}/reindex_data_streams/${dataStreamName}/pause`,
|
||||
method: 'post',
|
||||
});
|
||||
}
|
||||
|
||||
public async getReindexStatus(indexName: string) {
|
||||
return await this.sendRequest<ReindexStatusResponse>({
|
||||
path: `${API_BASE_PATH}/reindex/${indexName}`,
|
||||
|
|
|
@ -13,10 +13,18 @@ export const UIM_ES_DEPRECATIONS_PAGE_LOAD = 'es_deprecations_page_load';
|
|||
export const UIM_KIBANA_DEPRECATIONS_PAGE_LOAD = 'kibana_deprecations_page_load';
|
||||
export const UIM_OVERVIEW_PAGE_LOAD = 'overview_page_load';
|
||||
export const UIM_ES_DEPRECATION_LOGS_PAGE_LOAD = 'es_deprecation_logs_page_load';
|
||||
|
||||
// Reindexing
|
||||
export const UIM_REINDEX_OPEN_FLYOUT_CLICK = 'reindex_open_flyout_click';
|
||||
export const UIM_REINDEX_CLOSE_FLYOUT_CLICK = 'reindex_close_flyout_click';
|
||||
export const UIM_REINDEX_START_CLICK = 'reindex_start_click';
|
||||
export const UIM_REINDEX_STOP_CLICK = 'reindex_stop_click';
|
||||
// Data Streams Reindexing
|
||||
export const UIM_DATA_STREAM_REINDEX_OPEN_FLYOUT_CLICK = 'data_stream_reindex_open_flyout_click';
|
||||
export const UIM_DATA_STREAM_REINDEX_CLOSE_FLYOUT_CLICK = 'data_stream_reindex_close_flyout_click';
|
||||
export const UIM_DATA_STREAM_REINDEX_START_CLICK = 'data_stream_reindex_start_click';
|
||||
export const UIM_DATA_STREAM_REINDEX_STOP_CLICK = 'data_stream_reindex_stop_click';
|
||||
|
||||
export const UIM_BACKUP_DATA_CLOUD_CLICK = 'backup_data_cloud_click';
|
||||
export const UIM_BACKUP_DATA_ON_PREM_CLICK = 'backup_data_on_prem_click';
|
||||
export const UIM_RESET_LOGS_COUNTER_CLICK = 'reset_logs_counter_click';
|
||||
|
|
|
@ -9,7 +9,7 @@ import { pipe } from 'fp-ts/lib/pipeable';
|
|||
import { tryCatch, fold } from 'fp-ts/lib/Either';
|
||||
|
||||
import { DEPRECATION_WARNING_UPPER_LIMIT } from '../../../common/constants';
|
||||
import { ReindexStep } from '../../../common/types';
|
||||
import { ReindexStep, DataStreamReindexStatus } from '../../../common/types';
|
||||
|
||||
export const validateRegExpString = (s: string) =>
|
||||
pipe(
|
||||
|
@ -101,3 +101,33 @@ export const getReindexProgressLabel = (
|
|||
}
|
||||
return `${percentsComplete}%`;
|
||||
};
|
||||
|
||||
export const getDataStreamReindexProgress = (
|
||||
status: DataStreamReindexStatus,
|
||||
reindexTaskPercComplete: number | null
|
||||
): number => {
|
||||
switch (status) {
|
||||
case DataStreamReindexStatus.notStarted:
|
||||
return 0;
|
||||
|
||||
case DataStreamReindexStatus.fetchFailed:
|
||||
case DataStreamReindexStatus.failed:
|
||||
case DataStreamReindexStatus.cancelled:
|
||||
case DataStreamReindexStatus.inProgress: {
|
||||
return reindexTaskPercComplete !== null ? Math.round(reindexTaskPercComplete * 100) : 0;
|
||||
}
|
||||
case DataStreamReindexStatus.completed: {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const getDataStreamReindexProgressLabel = (
|
||||
status: DataStreamReindexStatus,
|
||||
reindexTaskPercComplete: number | null
|
||||
): string => {
|
||||
const percentsComplete = getDataStreamReindexProgress(status, reindexTaskPercComplete);
|
||||
return `${percentsComplete}%`;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
* 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 { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
import { LicensingPluginSetup } from '@kbn/licensing-plugin/server';
|
||||
|
||||
import { TransportResult } from '@elastic/elasticsearch';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
DataStreamReindexStatus,
|
||||
DataStreamReindexOperation,
|
||||
DataStreamMetadata,
|
||||
DataStreamReindexWarning,
|
||||
DataStreamReindexTaskStatusResponse,
|
||||
DataStreamReindexStatusCancelled,
|
||||
} from '../../../common/types';
|
||||
|
||||
import { error } from './error';
|
||||
|
||||
interface DataStreamReindexService {
|
||||
/**
|
||||
* Checks whether or not the user has proper privileges required to reindex this index.
|
||||
* @param dataStreamName
|
||||
*/
|
||||
hasRequiredPrivileges: (dataStreamName: string) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Checks an index's settings and mappings to flag potential issues during reindex.
|
||||
* Resolves to null if index does not exist.
|
||||
* @param dataStreamName
|
||||
*/
|
||||
detectReindexWarnings: (
|
||||
dataStreamName: string
|
||||
) => Promise<DataStreamReindexWarning[] | undefined>;
|
||||
|
||||
/**
|
||||
* Creates a new reindex operation for a given index.
|
||||
* @param dataStreamName
|
||||
*/
|
||||
createReindexOperation: (dataStreamName: string) => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Polls Elasticsearch's Data stream status API to retrieve the status of the reindex operation.
|
||||
* @param dataStreamName
|
||||
*/
|
||||
fetchReindexStatus: (dataStreamName: string) => Promise<DataStreamReindexOperation>;
|
||||
|
||||
/**
|
||||
* Cancels an in-progress reindex operation for a given index.
|
||||
* @param dataStreamName
|
||||
*/
|
||||
cancelReindexing: (dataStreamName: string) => Promise<DataStreamReindexStatusCancelled>;
|
||||
|
||||
/**
|
||||
* Retrieves metadata about the data stream.
|
||||
* @param dataStreamName
|
||||
*/
|
||||
getDataStreamMetadata: (dataStreamName: string) => Promise<DataStreamMetadata | null>;
|
||||
}
|
||||
|
||||
export interface DataStreamReindexServiceFactoryParams {
|
||||
esClient: ElasticsearchClient;
|
||||
log: Logger;
|
||||
licensing: LicensingPluginSetup;
|
||||
}
|
||||
|
||||
export const dataStreamReindexServiceFactory = ({
|
||||
esClient,
|
||||
licensing,
|
||||
}: DataStreamReindexServiceFactoryParams): DataStreamReindexService => {
|
||||
return {
|
||||
hasRequiredPrivileges: async (dataStreamName: string): Promise<boolean> => {
|
||||
/**
|
||||
* To avoid a circular dependency on Security we use a work around
|
||||
* here to detect whether Security is available and enabled
|
||||
* (i.e., via the licensing plugin). This enables Security to use
|
||||
* functionality exposed through Upgrade Assistant.
|
||||
*/
|
||||
const license = await firstValueFrom(licensing.license$);
|
||||
|
||||
const securityFeature = license.getFeature('security');
|
||||
|
||||
// If security is disabled or unavailable, return true.
|
||||
if (!securityFeature || !(securityFeature.isAvailable && securityFeature.isEnabled)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const names = [dataStreamName];
|
||||
const resp = await esClient.security.hasPrivileges({
|
||||
body: {
|
||||
cluster: ['manage', 'cancel_task'],
|
||||
index: [
|
||||
{
|
||||
names,
|
||||
allow_restricted_indices: true,
|
||||
privileges: ['all'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return resp.has_all_requested;
|
||||
},
|
||||
async detectReindexWarnings(): Promise<DataStreamReindexWarning[]> {
|
||||
return [
|
||||
{
|
||||
warningType: 'incompatibleDataStream',
|
||||
},
|
||||
];
|
||||
},
|
||||
async createReindexOperation(dataStreamName: string) {
|
||||
const indexExists = await esClient.indices.exists({ index: dataStreamName });
|
||||
if (!indexExists) {
|
||||
throw error.indexNotFound(`Index ${dataStreamName} does not exist in this cluster.`);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await esClient.transport.request<{ acknowledged: boolean }>({
|
||||
method: 'POST',
|
||||
path: '/_migration/reindex',
|
||||
body: {
|
||||
mode: 'upgrade',
|
||||
source: {
|
||||
index: dataStreamName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!result.acknowledged) {
|
||||
throw error.reindexTaskFailed(
|
||||
`The reindex operation failed to start for ${dataStreamName}`
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err.status === 400 && err.error.type === 'resource_already_exists_exception') {
|
||||
throw error.reindexAlreadyInProgress(
|
||||
`A reindex operation already in-progress for ${dataStreamName}`
|
||||
);
|
||||
}
|
||||
|
||||
throw error.reindexTaskFailed(
|
||||
`The reindex operation failed to start for ${dataStreamName}`
|
||||
);
|
||||
}
|
||||
},
|
||||
async fetchReindexStatus(dataStreamName: string): Promise<DataStreamReindexOperation> {
|
||||
// Check reindexing task progress
|
||||
try {
|
||||
const taskResponse = await esClient.transport.request<DataStreamReindexTaskStatusResponse>({
|
||||
method: 'GET',
|
||||
path: `/_migration/reindex/${dataStreamName}/_status`,
|
||||
});
|
||||
|
||||
if (taskResponse.exception) {
|
||||
// Include the entire task result in the error message. This should be guaranteed
|
||||
// to be JSON-serializable since it just came back from Elasticsearch.
|
||||
throw error.reindexTaskFailed(
|
||||
`Data Stream Reindexing exception:\n${taskResponse.exception}\n${JSON.stringify(
|
||||
taskResponse,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
if (taskResponse.complete) {
|
||||
// Check that no failures occurred
|
||||
if (taskResponse.errors.length) {
|
||||
// Include the entire task result in the error message. This should be guaranteed
|
||||
// to be JSON-serializable since it just came back from Elasticsearch.
|
||||
throw error.reindexTaskFailed(
|
||||
`Reindexing failed with ${taskResponse.errors.length} errors:\n${JSON.stringify(
|
||||
taskResponse,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
// Update the status
|
||||
return {
|
||||
reindexTaskPercComplete: 1,
|
||||
status: DataStreamReindexStatus.completed,
|
||||
progressDetails: {
|
||||
startTimeMs: taskResponse.start_time_millis,
|
||||
successCount: taskResponse.successes,
|
||||
pendingCount: taskResponse.pending,
|
||||
inProgressCount: (taskResponse.in_progress ?? []).length,
|
||||
errorsCount: (taskResponse.errors ?? []).length,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// Updated the percent complete
|
||||
const perc = taskResponse.successes / taskResponse.total_indices_in_data_stream;
|
||||
|
||||
return {
|
||||
status: DataStreamReindexStatus.inProgress,
|
||||
reindexTaskPercComplete: perc,
|
||||
progressDetails: {
|
||||
startTimeMs: taskResponse.start_time_millis,
|
||||
successCount: taskResponse.successes,
|
||||
pendingCount: taskResponse.pending,
|
||||
inProgressCount: (taskResponse.in_progress ?? []).length,
|
||||
errorsCount: (taskResponse.errors ?? []).length,
|
||||
},
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
if (
|
||||
err.name === 'ResponseError' &&
|
||||
(err.message as string).includes('resource_not_found_exception')
|
||||
) {
|
||||
// cancelled, never started, or successful task but finished from than 24 hours ago
|
||||
// Since this API should be called as a follow up from _migrate API, we can assume that the task is not started
|
||||
return {
|
||||
status: DataStreamReindexStatus.notStarted,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: DataStreamReindexStatus.failed,
|
||||
errorMessage: err.toString(),
|
||||
};
|
||||
}
|
||||
},
|
||||
async cancelReindexing(dataStreamName: string) {
|
||||
const resp = await esClient.transport.request<{ acknowledged: boolean }>({
|
||||
method: 'POST',
|
||||
path: `/_migration/reindex/${dataStreamName}/_cancel`,
|
||||
});
|
||||
|
||||
if (!resp.acknowledged) {
|
||||
throw error.reindexCannotBeCancelled(`Could not cancel reindex.`);
|
||||
}
|
||||
|
||||
return {
|
||||
status: DataStreamReindexStatus.cancelled,
|
||||
};
|
||||
},
|
||||
async getDataStreamMetadata(dataStreamName: string): Promise<DataStreamMetadata | null> {
|
||||
try {
|
||||
const { body: statsBody } = (await esClient.transport.request(
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/${dataStreamName}/_stats`,
|
||||
},
|
||||
{ meta: true }
|
||||
)) as TransportResult<any>;
|
||||
|
||||
const { data_streams: dataStreamsDeprecations } = await esClient.migration.deprecations({
|
||||
filter_path: `data_streams`,
|
||||
});
|
||||
|
||||
const deprecationsDetails = dataStreamsDeprecations[dataStreamName];
|
||||
if (!deprecationsDetails || !deprecationsDetails.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the first deprecation that has reindex_required set to true
|
||||
const deprecationDetails = deprecationsDetails.find(
|
||||
(deprecation) => deprecation._meta!.reindex_required
|
||||
);
|
||||
if (!deprecationDetails) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const indicesRequiringUpgrade: string[] =
|
||||
deprecationDetails._meta!.indices_requiring_upgrade;
|
||||
const allIndices = Object.keys(statsBody.indices);
|
||||
|
||||
let indicesRequiringUpgradeDocsCount = 0;
|
||||
let indicesRequiringUpgradeDocsSize = 0;
|
||||
|
||||
const indicesCreationDates = [];
|
||||
for (const index of indicesRequiringUpgrade) {
|
||||
const indexStats = Object.entries(statsBody.indices).find(([key]) => key === index);
|
||||
|
||||
if (!indexStats) {
|
||||
throw error.cannotGrabMetadata(`Index ${index} does not exist in this cluster.`);
|
||||
}
|
||||
|
||||
indicesRequiringUpgradeDocsSize += (indexStats[1] as any).total.store
|
||||
.total_data_set_size_in_bytes;
|
||||
indicesRequiringUpgradeDocsCount += (indexStats[1] as any).total.docs.count;
|
||||
|
||||
const body = await esClient.indices.getSettings({
|
||||
index,
|
||||
flat_settings: true,
|
||||
});
|
||||
|
||||
const creationDate = _.get(body, [index, 'settings', 'index.creation_date']);
|
||||
if (creationDate) {
|
||||
indicesCreationDates.push(creationDate);
|
||||
}
|
||||
}
|
||||
|
||||
const lastIndexRequiringUpgradeCreationDate = Math.max(...indicesCreationDates);
|
||||
|
||||
return {
|
||||
dataStreamName,
|
||||
documentationUrl: deprecationDetails.url,
|
||||
allIndices,
|
||||
allIndicesCount: allIndices.length,
|
||||
indicesRequiringUpgrade,
|
||||
indicesRequiringUpgradeCount: indicesRequiringUpgrade.length,
|
||||
lastIndexRequiringUpgradeCreationDate,
|
||||
indicesRequiringUpgradeDocsSize,
|
||||
indicesRequiringUpgradeDocsCount,
|
||||
};
|
||||
} catch (err) {
|
||||
throw error.cannotGrabMetadata(
|
||||
`Could not grab metadata for ${dataStreamName}. ${err.message.toString()}`
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 {
|
||||
AccessForbidden,
|
||||
IndexNotFound,
|
||||
ReindexTaskFailed,
|
||||
ReindexAlreadyInProgress,
|
||||
ReindexCannotBeCancelled,
|
||||
MetadataCannotBeGrabbed,
|
||||
} from './error_symbols';
|
||||
|
||||
export class ReindexError extends Error {
|
||||
constructor(message: string, public readonly symbol: symbol) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export const createErrorFactory = (symbol: symbol) => (message: string) => {
|
||||
return new ReindexError(message, symbol);
|
||||
};
|
||||
|
||||
export const error = {
|
||||
indexNotFound: createErrorFactory(IndexNotFound),
|
||||
accessForbidden: createErrorFactory(AccessForbidden),
|
||||
cannotGrabMetadata: createErrorFactory(MetadataCannotBeGrabbed),
|
||||
reindexTaskFailed: createErrorFactory(ReindexTaskFailed),
|
||||
reindexAlreadyInProgress: createErrorFactory(ReindexAlreadyInProgress),
|
||||
reindexCannotBeCancelled: createErrorFactory(ReindexCannotBeCancelled),
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 const AccessForbidden = Symbol('AccessForbidden');
|
||||
export const IndexNotFound = Symbol('IndexNotFound');
|
||||
export const ReindexTaskFailed = Symbol('ReindexTaskFailed');
|
||||
export const ReindexAlreadyInProgress = Symbol('ReindexAlreadyInProgress');
|
||||
export const ReindexCannotBeCancelled = Symbol('ReindexCannotBeCancelled');
|
||||
export const MetadataCannotBeGrabbed = Symbol('MetadataCannotBeGrabbed');
|
|
@ -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 { dataStreamReindexServiceFactory } from './data_stream_reindex_service';
|
|
@ -13,12 +13,29 @@ interface Action {
|
|||
}
|
||||
|
||||
interface Actions {
|
||||
actions: Action[];
|
||||
actions?: Action[];
|
||||
}
|
||||
|
||||
export type EsMetadata = Actions & {
|
||||
[key: string]: string;
|
||||
};
|
||||
interface MlActionMetadata {
|
||||
actions?: Action[];
|
||||
snapshot_id: string;
|
||||
job_id: string;
|
||||
}
|
||||
interface DataStreamActionMetadata {
|
||||
actions?: Action[];
|
||||
total_backing_indices: number;
|
||||
reindex_required: boolean;
|
||||
|
||||
// Action required before moving to 9.0
|
||||
indices_requiring_upgrade_count?: number;
|
||||
indices_requiring_upgrade?: string[];
|
||||
|
||||
// Action not required before moving to 9.0
|
||||
ignored_indices_requiring_upgrade?: string[];
|
||||
ignored_indices_requiring_upgrade_count?: number;
|
||||
}
|
||||
|
||||
export type EsMetadata = Actions | MlActionMetadata | DataStreamActionMetadata;
|
||||
|
||||
// TODO(jloleysens): Replace these regexes once this issue is addressed https://github.com/elastic/elasticsearch/issues/118062
|
||||
const ES_INDEX_MESSAGES_REQIURING_REINDEX = [
|
||||
|
@ -27,6 +44,7 @@ const ES_INDEX_MESSAGES_REQIURING_REINDEX = [
|
|||
];
|
||||
|
||||
export const getCorrectiveAction = (
|
||||
deprecationType: EnrichedDeprecationInfo['type'],
|
||||
message: string,
|
||||
metadata: EsMetadata,
|
||||
indexName?: string
|
||||
|
@ -43,6 +61,40 @@ export const getCorrectiveAction = (
|
|||
const requiresIndexSettingsAction = Boolean(indexSettingDeprecation);
|
||||
const requiresClusterSettingsAction = Boolean(clusterSettingDeprecation);
|
||||
const requiresMlAction = /[Mm]odel snapshot/.test(message);
|
||||
const requiresDataStreamsAction = deprecationType === 'data_streams';
|
||||
|
||||
if (requiresDataStreamsAction) {
|
||||
const {
|
||||
total_backing_indices: totalBackingIndices,
|
||||
indices_requiring_upgrade_count: indicesRequiringUpgradeCount = 0,
|
||||
indices_requiring_upgrade: indicesRequiringUpgrade = [],
|
||||
|
||||
ignored_indices_requiring_upgrade: ignoredIndicesRequiringUpgrade = [],
|
||||
ignored_indices_requiring_upgrade_count: ignoredIndicesRequiringUpgradeCount = 0,
|
||||
|
||||
reindex_required: reindexRequired,
|
||||
} = metadata as DataStreamActionMetadata;
|
||||
|
||||
/**
|
||||
* If there are no indices requiring upgrade, or reindexRequired = false.
|
||||
* Then we don't need to show the corrective action
|
||||
*/
|
||||
if (indicesRequiringUpgradeCount < 1 || !reindexRequired) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'dataStream',
|
||||
metadata: {
|
||||
ignoredIndicesRequiringUpgrade,
|
||||
ignoredIndicesRequiringUpgradeCount,
|
||||
totalBackingIndices,
|
||||
indicesRequiringUpgradeCount,
|
||||
indicesRequiringUpgrade,
|
||||
reindexRequired,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (requiresReindexAction) {
|
||||
return {
|
||||
|
@ -65,7 +117,7 @@ export const getCorrectiveAction = (
|
|||
}
|
||||
|
||||
if (requiresMlAction) {
|
||||
const { snapshot_id: snapshotId, job_id: jobId } = metadata!;
|
||||
const { snapshot_id: snapshotId, job_id: jobId } = metadata as MlActionMetadata;
|
||||
|
||||
return {
|
||||
type: 'mlSnapshot',
|
||||
|
|
|
@ -161,9 +161,10 @@ export const getEnrichedDeprecations = async (
|
|||
})
|
||||
.map((deprecation) => {
|
||||
const correctiveAction = getCorrectiveAction(
|
||||
deprecation.type,
|
||||
deprecation.message,
|
||||
deprecation.metadata as EsMetadata,
|
||||
deprecation.index!
|
||||
deprecation.index
|
||||
);
|
||||
|
||||
// If we have found deprecation information for index/indices
|
||||
|
|
|
@ -21,9 +21,11 @@ import { registerUpgradeStatusRoute } from './status';
|
|||
import { registerRemoteClustersRoute } from './remote_clusters';
|
||||
import { registerNodeDiskSpaceRoute } from './node_disk_space';
|
||||
import { registerClusterSettingsRoute } from './cluster_settings';
|
||||
import { registerReindexDataStreamRoutes } from './reindex_data_streams';
|
||||
|
||||
export function registerRoutes(dependencies: RouteDependencies, getWorker: () => ReindexWorker) {
|
||||
registerAppRoutes(dependencies);
|
||||
|
||||
registerCloudBackupStatusRoutes(dependencies);
|
||||
registerClusterUpgradeStatusRoutes(dependencies);
|
||||
registerSystemIndicesMigrationRoutes(dependencies);
|
||||
|
@ -38,4 +40,7 @@ export function registerRoutes(dependencies: RouteDependencies, getWorker: () =>
|
|||
registerRemoteClustersRoute(dependencies);
|
||||
registerNodeDiskSpaceRoute(dependencies);
|
||||
registerClusterSettingsRoute(dependencies);
|
||||
|
||||
// Data streams reindexing
|
||||
registerReindexDataStreamRoutes(dependencies);
|
||||
}
|
||||
|
|
|
@ -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 { registerReindexDataStreamRoutes } from './reindex_data_stream';
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 { kibanaResponseFactory } from '@kbn/core/server';
|
||||
|
||||
import {
|
||||
AccessForbidden,
|
||||
IndexNotFound,
|
||||
ReindexAlreadyInProgress,
|
||||
ReindexCannotBeCancelled,
|
||||
ReindexTaskFailed,
|
||||
MetadataCannotBeGrabbed,
|
||||
} from '../../lib/data_streams/error_symbols';
|
||||
import { ReindexError } from '../../lib/data_streams/error';
|
||||
|
||||
export const mapAnyErrorToKibanaHttpResponse = (e: any) => {
|
||||
if (e instanceof ReindexError) {
|
||||
switch (e.symbol) {
|
||||
case AccessForbidden:
|
||||
return kibanaResponseFactory.forbidden({ body: e.message });
|
||||
case IndexNotFound:
|
||||
return kibanaResponseFactory.notFound({ body: e.message });
|
||||
case ReindexTaskFailed:
|
||||
// Bad data
|
||||
return kibanaResponseFactory.customError({ body: e.message, statusCode: 422 });
|
||||
case ReindexAlreadyInProgress:
|
||||
case ReindexCannotBeCancelled:
|
||||
case MetadataCannotBeGrabbed:
|
||||
return kibanaResponseFactory.badRequest({ body: e.message });
|
||||
default:
|
||||
// nothing matched
|
||||
}
|
||||
}
|
||||
|
||||
throw e;
|
||||
};
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { error } from '../../lib/data_streams/error';
|
||||
import { API_BASE_PATH } from '../../../common/constants';
|
||||
import { DataStreamReindexStatusResponse } from '../../../common/types';
|
||||
import { versionCheckHandlerWrapper } from '../../lib/es_version_precheck';
|
||||
import { dataStreamReindexServiceFactory } from '../../lib/data_streams';
|
||||
|
||||
import { RouteDependencies } from '../../types';
|
||||
import { mapAnyErrorToKibanaHttpResponse } from './map_any_error_to_kibana_http_response';
|
||||
|
||||
export function registerReindexDataStreamRoutes({
|
||||
router,
|
||||
licensing,
|
||||
log,
|
||||
getSecurityPlugin,
|
||||
lib: { handleEsError },
|
||||
}: RouteDependencies) {
|
||||
const BASE_PATH = `${API_BASE_PATH}/reindex_data_streams`;
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: `${BASE_PATH}/{dataStreamName}`,
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'Relies on elasticsearch for authorization',
|
||||
},
|
||||
},
|
||||
options: {
|
||||
access: 'public',
|
||||
summary: `Start the data stream reindexing`,
|
||||
},
|
||||
validate: {
|
||||
params: schema.object({
|
||||
dataStreamName: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
versionCheckHandlerWrapper(async ({ core }, request, response) => {
|
||||
const {
|
||||
elasticsearch: { client: esClient },
|
||||
} = await core;
|
||||
const { dataStreamName } = request.params;
|
||||
try {
|
||||
const callAsCurrentUser = esClient.asCurrentUser;
|
||||
const reindexService = dataStreamReindexServiceFactory({
|
||||
esClient: callAsCurrentUser,
|
||||
log,
|
||||
licensing,
|
||||
});
|
||||
|
||||
if (!(await reindexService.hasRequiredPrivileges(dataStreamName))) {
|
||||
throw error.accessForbidden(
|
||||
i18n.translate(
|
||||
'xpack.upgradeAssistant.datastream.reindex.reindexPrivilegesErrorBatch',
|
||||
{
|
||||
defaultMessage: `You do not have adequate privileges to reindex "{dataStreamName}".`,
|
||||
values: { dataStreamName },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await reindexService.createReindexOperation(dataStreamName);
|
||||
|
||||
return response.ok();
|
||||
} catch (err) {
|
||||
if (err instanceof errors.ResponseError) {
|
||||
return handleEsError({ error: err, response });
|
||||
}
|
||||
return mapAnyErrorToKibanaHttpResponse(err);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: `${BASE_PATH}/{dataStreamName}`,
|
||||
options: {
|
||||
access: 'public',
|
||||
summary: `Get data stream status`,
|
||||
},
|
||||
validate: {
|
||||
params: schema.object({
|
||||
dataStreamName: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
versionCheckHandlerWrapper(async ({ core }, request, response) => {
|
||||
const {
|
||||
elasticsearch: { client: esClient },
|
||||
} = await core;
|
||||
const { dataStreamName } = request.params;
|
||||
const asCurrentUser = esClient.asCurrentUser;
|
||||
|
||||
const reindexService = dataStreamReindexServiceFactory({
|
||||
esClient: asCurrentUser,
|
||||
log,
|
||||
licensing,
|
||||
});
|
||||
|
||||
try {
|
||||
const hasRequiredPrivileges = await reindexService.hasRequiredPrivileges(dataStreamName);
|
||||
|
||||
// If the user doesn't have privileges than querying for warnings is going to fail.
|
||||
const warnings = hasRequiredPrivileges
|
||||
? await reindexService.detectReindexWarnings(dataStreamName)
|
||||
: [];
|
||||
|
||||
const reindexOp = await reindexService.fetchReindexStatus(dataStreamName);
|
||||
|
||||
const body: DataStreamReindexStatusResponse = {
|
||||
reindexOp,
|
||||
warnings,
|
||||
hasRequiredPrivileges,
|
||||
};
|
||||
|
||||
return response.ok({
|
||||
body,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof errors.ResponseError) {
|
||||
return handleEsError({ error: err, response });
|
||||
}
|
||||
return mapAnyErrorToKibanaHttpResponse(error);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
{
|
||||
path: `${BASE_PATH}/{dataStreamName}/metadata`,
|
||||
options: {
|
||||
access: 'public',
|
||||
summary: `Get data stream reindexing metadata`,
|
||||
},
|
||||
validate: {
|
||||
params: schema.object({
|
||||
dataStreamName: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
versionCheckHandlerWrapper(async ({ core }, request, response) => {
|
||||
const {
|
||||
elasticsearch: { client: esClient },
|
||||
} = await core;
|
||||
const { dataStreamName } = request.params;
|
||||
const asCurrentUser = esClient.asCurrentUser;
|
||||
|
||||
const reindexService = dataStreamReindexServiceFactory({
|
||||
esClient: asCurrentUser,
|
||||
log,
|
||||
licensing,
|
||||
});
|
||||
|
||||
try {
|
||||
const dataStreamMetadata = await reindexService.getDataStreamMetadata(dataStreamName);
|
||||
|
||||
return response.ok({
|
||||
body: dataStreamMetadata || undefined,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof errors.ResponseError) {
|
||||
return handleEsError({ error: err, response });
|
||||
}
|
||||
return mapAnyErrorToKibanaHttpResponse(error);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: `${BASE_PATH}/{dataStreamName}/cancel`,
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason: 'Relies on elasticsearch for authorization',
|
||||
},
|
||||
},
|
||||
options: {
|
||||
access: 'public',
|
||||
summary: `Cancel Data Stream reindexing`,
|
||||
},
|
||||
validate: {
|
||||
params: schema.object({
|
||||
dataStreamName: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
versionCheckHandlerWrapper(async ({ core }, request, response) => {
|
||||
const {
|
||||
elasticsearch: { client: esClient },
|
||||
} = await core;
|
||||
const { dataStreamName } = request.params;
|
||||
const callAsCurrentUser = esClient.asCurrentUser;
|
||||
|
||||
const reindexService = dataStreamReindexServiceFactory({
|
||||
esClient: callAsCurrentUser,
|
||||
log,
|
||||
licensing,
|
||||
});
|
||||
|
||||
try {
|
||||
if (!(await reindexService.hasRequiredPrivileges(dataStreamName))) {
|
||||
throw error.accessForbidden(
|
||||
i18n.translate(
|
||||
'xpack.upgradeAssistant.datastream.reindex.reindexPrivilegesErrorBatch',
|
||||
{
|
||||
defaultMessage: `You do not have adequate privileges to cancel reindexing "{dataStreamName}".`,
|
||||
values: { dataStreamName },
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await reindexService.cancelReindexing(dataStreamName);
|
||||
|
||||
return response.ok({ body: { acknowledged: true } });
|
||||
} catch (err) {
|
||||
if (err instanceof errors.ResponseError) {
|
||||
return handleEsError({ error: err, response });
|
||||
}
|
||||
|
||||
return mapAnyErrorToKibanaHttpResponse(error);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue