mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Add on-Cloud state to Upgrade Assistant 'Back up data' step (#109956)
This commit is contained in:
parent
f04c915622
commit
986ac3b373
13 changed files with 314 additions and 63 deletions
|
@ -26,7 +26,7 @@ const idToUrlMap = {
|
|||
const shareMock = sharePluginMock.createSetupContract();
|
||||
shareMock.url.locators.get = (id) => ({
|
||||
// @ts-expect-error This object is missing some properties that we're not using in the UI
|
||||
getUrl: (): string | undefined => idToUrlMap[id],
|
||||
useUrl: (): string | undefined => idToUrlMap[id],
|
||||
});
|
||||
|
||||
export const getAppContextMock = (mockHttpClient: HttpSetup) => ({
|
||||
|
|
|
@ -28,9 +28,15 @@ export const indexSettingDeprecations = {
|
|||
|
||||
export const API_BASE_PATH = '/api/upgrade_assistant';
|
||||
|
||||
/**
|
||||
* This is the repository where Cloud stores its backup snapshots.
|
||||
*/
|
||||
export const CLOUD_SNAPSHOT_REPOSITORY = 'found-snapshots';
|
||||
|
||||
export const DEPRECATION_WARNING_UPPER_LIMIT = 999999;
|
||||
export const DEPRECATION_LOGS_SOURCE_ID = 'deprecation_logs';
|
||||
export const DEPRECATION_LOGS_INDEX = '.logs-deprecation.elasticsearch-default';
|
||||
export const DEPRECATION_LOGS_INDEX_PATTERN = '.logs-deprecation.elasticsearch-default';
|
||||
|
||||
export const CLOUD_BACKUP_STATUS_POLL_INTERVAL_MS = 60000;
|
||||
export const DEPRECATION_LOGS_COUNT_POLL_INTERVAL_MS = 60000;
|
||||
|
|
|
@ -218,6 +218,11 @@ export interface EnrichedDeprecationInfo
|
|||
resolveDuringUpgrade: boolean;
|
||||
}
|
||||
|
||||
export interface CloudBackupStatus {
|
||||
isBackedUp: boolean;
|
||||
lastBackupTime?: string;
|
||||
}
|
||||
|
||||
export interface ESUpgradeStatus {
|
||||
totalCriticalDeprecations: number;
|
||||
deprecations: EnrichedDeprecationInfo[];
|
||||
|
|
|
@ -5,72 +5,40 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiText, EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
import type { EuiStepProps } from '@elastic/eui/src/components/steps/step';
|
||||
|
||||
import { useAppContext } from '../../../app_context';
|
||||
import type { CloudSetup } from '../../../../../../cloud/public';
|
||||
import { OnPremBackup } from './on_prem_backup';
|
||||
import { CloudBackup, CloudBackupStatusResponse } from './cloud_backup';
|
||||
|
||||
const i18nTexts = {
|
||||
backupStepTitle: i18n.translate('xpack.upgradeAssistant.overview.backupStepTitle', {
|
||||
defaultMessage: 'Back up your data',
|
||||
}),
|
||||
const title = i18n.translate('xpack.upgradeAssistant.overview.backupStepTitle', {
|
||||
defaultMessage: 'Back up your data',
|
||||
});
|
||||
|
||||
backupStepDescription: i18n.translate('xpack.upgradeAssistant.overview.backupStepDescription', {
|
||||
defaultMessage: 'Back up your data before addressing any deprecation warnings.',
|
||||
}),
|
||||
};
|
||||
interface Props {
|
||||
cloud?: CloudSetup;
|
||||
cloudBackupStatusResponse?: CloudBackupStatusResponse;
|
||||
}
|
||||
|
||||
const SnapshotRestoreAppLink: React.FunctionComponent = () => {
|
||||
const { share } = useAppContext();
|
||||
|
||||
const [snapshotRestoreUrl, setSnapshotRestoreUrl] = useState<string | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const getSnapshotRestoreUrl = async () => {
|
||||
const locator = share.url.locators.get('SNAPSHOT_RESTORE_LOCATOR');
|
||||
|
||||
if (!locator) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = await locator.getUrl({
|
||||
page: 'snapshots',
|
||||
});
|
||||
setSnapshotRestoreUrl(url);
|
||||
export const getBackupStep = ({ cloud, cloudBackupStatusResponse }: Props): EuiStepProps => {
|
||||
if (cloud?.isCloudEnabled) {
|
||||
return {
|
||||
title,
|
||||
status: cloudBackupStatusResponse!.data?.isBackedUp ? 'complete' : 'incomplete',
|
||||
children: (
|
||||
<CloudBackup
|
||||
cloudBackupStatusResponse={cloudBackupStatusResponse!}
|
||||
cloudSnapshotsUrl={`${cloud!.deploymentUrl}/elasticsearch/snapshots`}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
getSnapshotRestoreUrl();
|
||||
}, [share]);
|
||||
|
||||
return (
|
||||
<EuiButton href={snapshotRestoreUrl} data-test-subj="snapshotRestoreLink">
|
||||
{i18n.translate('xpack.upgradeAssistant.overview.snapshotRestoreLink', {
|
||||
defaultMessage: 'Create snapshot',
|
||||
})}
|
||||
</EuiButton>
|
||||
);
|
||||
};
|
||||
|
||||
const BackupStep: React.FunctionComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<EuiText>
|
||||
<p>{i18nTexts.backupStepDescription}</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<SnapshotRestoreAppLink />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getBackupStep = (): EuiStepProps => {
|
||||
return {
|
||||
title: i18nTexts.backupStepTitle,
|
||||
title,
|
||||
status: 'incomplete',
|
||||
children: <BackupStep />,
|
||||
children: <OnPremBackup />,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* 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 moment from 'moment-timezone';
|
||||
import { FormattedDate, FormattedTime, FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiLoadingContent,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiText,
|
||||
EuiButton,
|
||||
EuiSpacer,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { CloudBackupStatus } from '../../../../../common/types';
|
||||
import { UseRequestResponse } from '../../../../shared_imports';
|
||||
import { ResponseError } from '../../../lib/api';
|
||||
|
||||
export type CloudBackupStatusResponse = UseRequestResponse<CloudBackupStatus, ResponseError>;
|
||||
|
||||
interface Props {
|
||||
cloudBackupStatusResponse: UseRequestResponse<CloudBackupStatus, ResponseError>;
|
||||
cloudSnapshotsUrl: string;
|
||||
}
|
||||
|
||||
export const CloudBackup: React.FunctionComponent<Props> = ({
|
||||
cloudBackupStatusResponse,
|
||||
cloudSnapshotsUrl,
|
||||
}) => {
|
||||
const { isInitialRequest, isLoading, error, data, resendRequest } = cloudBackupStatusResponse;
|
||||
|
||||
if (isInitialRequest && isLoading) {
|
||||
return <EuiLoadingContent lines={3} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.upgradeAssistant.overview.cloudBackup.loadingError', {
|
||||
defaultMessage: 'An error occurred while retrieving the latest snapshot status',
|
||||
})}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj="cloudBackupErrorCallout"
|
||||
>
|
||||
<p>
|
||||
{error.statusCode} - {error.message}
|
||||
</p>
|
||||
<EuiButton color="danger" onClick={resendRequest} data-test-subj="cloudBackupRetryButton">
|
||||
{i18n.translate('xpack.upgradeAssistant.overview.cloudBackup.retryButton', {
|
||||
defaultMessage: 'Try again',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
|
||||
const lastBackupTime = moment(data!.lastBackupTime).toISOString();
|
||||
|
||||
const statusMessage = data!.isBackedUp ? (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="check" color="success" />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.overview.cloudBackup.hasSnapshotMessage"
|
||||
defaultMessage="Last snapshot created on {lastBackupTime}."
|
||||
values={{
|
||||
lastBackupTime: (
|
||||
<>
|
||||
<FormattedDate
|
||||
value={lastBackupTime}
|
||||
year="numeric"
|
||||
month="long"
|
||||
day="2-digit"
|
||||
/>{' '}
|
||||
<FormattedTime value={lastBackupTime} timeZoneName="short" hour12={false} />
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type="alert" color="danger" />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.upgradeAssistant.overview.cloudBackup.noSnapshotMessage', {
|
||||
defaultMessage: `Your data isn't backed up.`,
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{statusMessage}
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiButton
|
||||
href={cloudSnapshotsUrl}
|
||||
data-test-subj="cloudSnapshotsLink"
|
||||
target="_blank"
|
||||
iconType="popout"
|
||||
iconSide="right"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.overview.cloudBackup.snapshotsLink"
|
||||
defaultMessage="Create snapshot"
|
||||
/>
|
||||
</EuiButton>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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 { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiText, EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { useAppContext } from '../../../app_context';
|
||||
|
||||
const SnapshotRestoreAppLink: React.FunctionComponent = () => {
|
||||
const { share } = useAppContext();
|
||||
|
||||
const snapshotRestoreUrl = share.url.locators
|
||||
.get('SNAPSHOT_RESTORE_LOCATOR')
|
||||
?.useUrl({ page: 'snapshots' });
|
||||
|
||||
return (
|
||||
<EuiButton href={snapshotRestoreUrl} data-test-subj="snapshotRestoreLink">
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.overview.snapshotRestoreLink"
|
||||
defaultMessage="Create snapshot"
|
||||
/>
|
||||
</EuiButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const OnPremBackup: React.FunctionComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<EuiText>
|
||||
<p>
|
||||
{i18n.translate('xpack.upgradeAssistant.overview.backupStepDescription', {
|
||||
defaultMessage: 'Back up your data before addressing any deprecation issues.',
|
||||
})}
|
||||
</p>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<SnapshotRestoreAppLink />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -20,6 +20,7 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { useKibana } from '../../../shared_imports';
|
||||
import { useAppContext } from '../../app_context';
|
||||
import { getBackupStep } from './backup_step';
|
||||
import { getFixIssuesStep } from './fix_issues_step';
|
||||
|
@ -27,6 +28,9 @@ import { getFixLogsStep } from './fix_logs_step';
|
|||
import { getUpgradeStep } from './upgrade_step';
|
||||
|
||||
export const Overview: FunctionComponent = () => {
|
||||
const {
|
||||
services: { cloud },
|
||||
} = useKibana();
|
||||
const { kibanaVersionInfo, breadcrumbs, docLinks, api } = useAppContext();
|
||||
const { nextMajor } = kibanaVersionInfo;
|
||||
|
||||
|
@ -44,6 +48,12 @@ export const Overview: FunctionComponent = () => {
|
|||
breadcrumbs.setBreadcrumbs('overview');
|
||||
}, [breadcrumbs]);
|
||||
|
||||
let cloudBackupStatusResponse;
|
||||
|
||||
if (cloud?.isCloudEnabled) {
|
||||
cloudBackupStatusResponse = api.useLoadCloudBackupStatus();
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiPageBody restrictWidth={true}>
|
||||
<EuiPageContent horizontalPosition="center" color="transparent" paddingSize="none">
|
||||
|
@ -84,7 +94,7 @@ export const Overview: FunctionComponent = () => {
|
|||
|
||||
<EuiSteps
|
||||
steps={[
|
||||
getBackupStep(),
|
||||
getBackupStep({ cloud, cloudBackupStatusResponse }),
|
||||
getFixIssuesStep({ nextMajor }),
|
||||
getFixLogsStep(),
|
||||
getUpgradeStep({ docLinks, nextMajor }),
|
||||
|
|
|
@ -6,8 +6,13 @@
|
|||
*/
|
||||
|
||||
import { HttpSetup } from 'src/core/public';
|
||||
import { ESUpgradeStatus } from '../../../common/types';
|
||||
import { API_BASE_PATH, DEPRECATION_LOGS_COUNT_POLL_INTERVAL_MS } from '../../../common/constants';
|
||||
|
||||
import { ESUpgradeStatus, CloudBackupStatus } from '../../../common/types';
|
||||
import {
|
||||
API_BASE_PATH,
|
||||
DEPRECATION_LOGS_COUNT_POLL_INTERVAL_MS,
|
||||
CLOUD_BACKUP_STATUS_POLL_INTERVAL_MS,
|
||||
} from '../../../common/constants';
|
||||
import {
|
||||
UseRequestConfig,
|
||||
SendRequestConfig,
|
||||
|
@ -45,6 +50,14 @@ export class ApiService {
|
|||
this.client = httpClient;
|
||||
}
|
||||
|
||||
public useLoadCloudBackupStatus() {
|
||||
return this.useRequest<CloudBackupStatus>({
|
||||
path: `${API_BASE_PATH}/cloud_backup_status`,
|
||||
method: 'get',
|
||||
pollIntervalMs: CLOUD_BACKUP_STATUS_POLL_INTERVAL_MS,
|
||||
});
|
||||
}
|
||||
|
||||
public useLoadEsDeprecations() {
|
||||
return this.useRequest<ESUpgradeStatus>({
|
||||
path: `${API_BASE_PATH}/es_deprecations`,
|
||||
|
|
|
@ -14,6 +14,7 @@ export {
|
|||
SendRequestResponse,
|
||||
useRequest,
|
||||
UseRequestConfig,
|
||||
UseRequestResponse,
|
||||
SectionLoading,
|
||||
GlobalFlyout,
|
||||
} from '../../../../src/plugins/es_ui_shared/public/';
|
||||
|
|
|
@ -20,6 +20,7 @@ import { InfraPluginSetup } from '../../infra/server';
|
|||
|
||||
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
|
||||
import { LicensingPluginSetup } from '../../licensing/server';
|
||||
import { DEPRECATION_LOGS_SOURCE_ID, DEPRECATION_LOGS_INDEX } from '../common/constants';
|
||||
|
||||
import { CredentialStore, credentialStoreFactory } from './lib/reindexing/credential_store';
|
||||
import { ReindexWorker } from './lib/reindexing';
|
||||
|
@ -32,7 +33,7 @@ import {
|
|||
reindexOperationSavedObjectType,
|
||||
mlSavedObjectType,
|
||||
} from './saved_object_types';
|
||||
import { DEPRECATION_LOGS_SOURCE_ID, DEPRECATION_LOGS_INDEX } from '../common/constants';
|
||||
import { handleEsError } from './shared_imports';
|
||||
|
||||
import { RouteDependencies } from './types';
|
||||
|
||||
|
@ -119,6 +120,9 @@ export class UpgradeAssistantServerPlugin implements Plugin {
|
|||
}
|
||||
return this.savedObjectsServiceStart;
|
||||
},
|
||||
lib: {
|
||||
handleEsError,
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize version service with current kibana version
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 { API_BASE_PATH, CLOUD_SNAPSHOT_REPOSITORY } from '../../common/constants';
|
||||
import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
|
||||
import { RouteDependencies } from '../types';
|
||||
|
||||
export function registerCloudBackupStatusRoutes({
|
||||
router,
|
||||
lib: { handleEsError },
|
||||
}: RouteDependencies) {
|
||||
// GET most recent Cloud snapshot
|
||||
router.get(
|
||||
{ path: `${API_BASE_PATH}/cloud_backup_status`, validate: false },
|
||||
versionCheckHandlerWrapper(async (context, request, response) => {
|
||||
const { client: clusterClient } = context.core.elasticsearch;
|
||||
|
||||
try {
|
||||
const {
|
||||
body: { snapshots },
|
||||
} = await clusterClient.asCurrentUser.snapshot.get({
|
||||
repository: CLOUD_SNAPSHOT_REPOSITORY,
|
||||
snapshot: '_all',
|
||||
ignore_unavailable: true, // Allow request to succeed even if some snapshots are unavailable.
|
||||
// @ts-expect-error @elastic/elasticsearch "desc" is a new param
|
||||
order: 'desc',
|
||||
sort: 'start_time',
|
||||
size: 1,
|
||||
});
|
||||
|
||||
let isBackedUp = false;
|
||||
let lastBackupTime;
|
||||
|
||||
if (snapshots && snapshots[0]) {
|
||||
isBackedUp = true;
|
||||
lastBackupTime = snapshots![0].start_time;
|
||||
}
|
||||
|
||||
return response.ok({
|
||||
body: {
|
||||
isBackedUp,
|
||||
lastBackupTime,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
return handleEsError({ error, response });
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { RouteDependencies } from '../types';
|
||||
|
||||
import { registerCloudBackupStatusRoutes } from './cloud_backup_status';
|
||||
import { registerESDeprecationRoutes } from './es_deprecations';
|
||||
import { registerDeprecationLoggingRoutes } from './deprecation_logging';
|
||||
import { registerReindexIndicesRoutes } from './reindex_indices';
|
||||
|
@ -17,6 +18,7 @@ import { ReindexWorker } from '../lib/reindexing';
|
|||
import { registerUpgradeStatusRoute } from './status';
|
||||
|
||||
export function registerRoutes(dependencies: RouteDependencies, getWorker: () => ReindexWorker) {
|
||||
registerCloudBackupStatusRoutes(dependencies);
|
||||
registerESDeprecationRoutes(dependencies);
|
||||
registerDeprecationLoggingRoutes(dependencies);
|
||||
registerReindexIndicesRoutes(dependencies, getWorker);
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
*/
|
||||
|
||||
import { IRouter, Logger, SavedObjectsServiceStart } from 'src/core/server';
|
||||
import { CredentialStore } from './lib/reindexing/credential_store';
|
||||
import { LicensingPluginSetup } from '../../licensing/server';
|
||||
import { CredentialStore } from './lib/reindexing/credential_store';
|
||||
import { handleEsError } from './shared_imports';
|
||||
|
||||
export interface RouteDependencies {
|
||||
router: IRouter;
|
||||
|
@ -15,4 +16,7 @@ export interface RouteDependencies {
|
|||
log: Logger;
|
||||
getSavedObjectsService: () => SavedObjectsServiceStart;
|
||||
licensing: LicensingPluginSetup;
|
||||
lib: {
|
||||
handleEsError: typeof handleEsError;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue