From e07194d379f1118169f98cd5a307812e3d0ceed0 Mon Sep 17 00:00:00 2001
From: "opensearch-trigger-bot[bot]"
<98922864+opensearch-trigger-bot[bot]@users.noreply.github.com>
Date: Tue, 1 Nov 2022 15:04:24 -0700
Subject: [PATCH] Complete snapshot restore, mvp for 2.4 (#315) (#326)
* Change alignment of Snapshot Management panels in pages/Main/Main.tsx
Signed-off-by: Chris Hesterman
* Unify vertical button alignment across panels, ContentPanel.tsx
Signed-off-by: Chris Hesterman
* Update jest snapshots, add ROUTE_STYLE variable
Signed-off-by: Chris Hesterman
* Add placeholder restore button to Snapshots panel
Signed-off-by: Chris Hesterman
* Comment out line 20 rollups_spec.js in cypress/integration/
Signed-off-by: Chris Hesterman
* Remove unused code and comment cypress/integration/rollups_spec.js
Signed-off-by: Chris Hesterman
* Starting adaptation/use of existing code for Restore functionality
Signed-off-by: Chris Hesterman
* Adapt Snapshots.tsx to include RestoreSnapshotFlyout
Signed-off-by: Chris Hesterman
* Add restoreSnapshot method to SMservice,RestoreSnapshotResponse int
Signed-off-by: Chris Hesterman
* Implement RestoreSnapshotFlyout open/close
Signed-off-by: Chris Hesterman
* Make Snapshot name appear on RestoreSnapshotFlyout
Signed-off-by: Chris Hesterman
* Add/adapt restoreSnapshot to server/SnapshotManagementService
Signed-off-by: Chris Hesterman
* Add route for restoreSnapshot in server/routes.ts
Signed-off-by: Chris Hesterman
* Rudimentary restore from snapshot functionality reached, buggy
Signed-off-by: Chris Hesterman
* Fix index list-snapshot restore, restrict restore to single selection
Signed-off-by: Chris Hesterman
* Remove console.logs, commented out code
Signed-off-by: Chris Hesterman
* Create Advanced options accordion for Snapshot restore.
Signed-off-by: Chris Hesterman
Currently only visually functional. TODO - make it apply the options
Signed-off-by: Chris Hesterman
* Add initial restore/rename options to ui. Only visual functionality
Signed-off-by: Chris Hesterman
* Resolve radio button events not firing
Signed-off-by: Chris Hesterman
Split radio buttons into SnapshotRestoreOption and SnapshotRenameOptions
Signed-off-by: Chris Hesterman
Move 'restore' button between 'delete' and 'take snapshot'
Signed-off-by: Chris Hesterman
Set default rename option to 'add prefix'
Signed-off-by: Chris Hesterman
* Add AddPrefixInput, remove unused component RestoreSnapshotInitialOptions
Signed-off-by: Chris Hesterman
* Fix issues with restore specific indices
Signed-off-by: Chris Hesterman
Implement first 4 Advanced restore options functionality
Signed-off-by: Chris Hesterman
* Begin cypress testing for snapshots
Signed-off-by: Chris Hesterman
Passing tests for create repo and create snapshot
Signed-off-by: Chris Hesterman
* Begin cypress testing Snapshots. Create repo and Create snapshot done
Signed-off-by: Chris Hesterman
* Implement add_prefix functionality for snapshot restore
Signed-off-by: Chris Hesterman
* Implement rename indices option for restore snapshot
Signed-off-by: Chris Hesterman
* Remove console.logs, unusted variables in SnapshotFlyout, SMservice
Signed-off-by: Chris Hesterman
* Add full stop to help text/errors, add RESTORE_OPTIONS enum
Signed-off-by: Chris Hesterman
* Add placeholder examples to rename options, add line breaks to help text
Signed-off-by: Chris Hesterman
* Progress on custom index settings, ignore index settings
Signed-off-by: Chris Hesterman
* Implement custom index settings functionality
Signed-off-by: Chris Hesterman
* Successfully implement/debug custom index settings/ignore index settings.
Signed-off-by: Chris Hesterman
* Progress on IndexList component, using cat.index.
Signed-off-by: Chris Hesterman
* IndexList with pagination fully functional, todo: styling
Signed-off-by: Chris Hesterman
* Add RestoreActivitiesPanel component folder, files (starter)
Signed-off-by: Chris Hesterman
* Complete IndexList with pagination, sorting.
Signed-off-by: Chris Hesterman
* Add maxWidth to RestoreSnapshotFlyout
Signed-off-by: Chris Hesterman
Implement skeleton Restore activities panel, panel switching in Snapshots
Signed-off-by: Chris Hesterman
* Progress on catIndexRecovery, TODO - debug
Signed-off-by: Chris Hesterman
* Fix syntax bug - server/services/SnapshotManagementService.ts
Signed-off-by: Chris Hesterman
* Resolved some errors, getting a response but not correct response.
Signed-off-by: Chris Hesterman
* Add back line 20 in rollups_spec, required by changes in 2.3 release.
Signed-off-by: Chris Hesterman
* Implement code to use Index Recovery api. successful.
Signed-off-by: Chris Hesterman
* Current progress extracting data and displaying in Restore activities
Signed-off-by: Chris Hesterman
* Implement Restore Activities with self updating until restore done
Signed-off-by: Chris Hesterman
* Add clickable indexes link and hello world click handler
Signed-off-by: Chris Hesterman
* Add toast if no snapshot selected upon restore activities click
Signed-off-by: Chris Hesterman
Add empty flyout where restoring indices will be listed
Signed-off-by: Chris Hesterman
* Change danger toast to warning when no snapshot selected
Signed-off-by: Chris Hesterman
* Fix bug when both custom settings and ignore settings chosen
Signed-off-by: Chris Hesterman
* Add refresh button to restore activities page, remove auto refreshing
Signed-off-by: Chris Hesterman
* Remove unused getRepos func, add repo prop, trim state in RestoreFlyout
Signed-off-by: Chris Hesterman
* Add increment to 2.3.0
Signed-off-by: Chris Hesterman
* Remove index settings from options if none entered, RestoreFlyout
Signed-off-by: Chris Hesterman
* Add rel="noopener noreferrer" to links in help text, use url from constants.
Signed-off-by: Chris Hesterman
* Create CatSnapshotIndex in server/models/interfaces, apply in IndexList
Signed-off-by: Chris Hesterman
* Add rel="noopener noreferrer" to links in IndexList, add CatSnapshotIndex interface
Signed-off-by: Chris Hesterman
* Changes to RestoreActivitesPanel to prep for listing indice
Signed-off-by: Chris Hesterman
* Re do merge commit - forgot to save file.
Signed-off-by: Chris Hesterman
* Snapshots testing in progress
Signed-off-by: Chris Hesterman
* Current progress, cypress testing
Signed-off-by: Chris Hesterman
* Add unit testing for AddPrefixInput, RenameInput components
Signed-off-by: Chris Hesterman
* Add unit tests for SnapshotRenameOptions component
Signed-off-by: Chris Hesterman
* Add unit tests for SnapshotRestoreOption component
Signed-off-by: Chris Hesterman
* Add unit tests for SnapshotIndicesInput component
Signed-off-by: Chris Hesterman
* Change 'Indices' to 'Index' if only 1 index.
Signed-off-by: Chris Hesterman
* Replace anchor tags with EuiLinks set to external, rename options
Signed-off-by: Chris Hesterman
* Change anchor tags to EuiLinks, IndexSettings
Signed-off-by: Chris Hesterman
* Add rel attribute to EuiLink, IndexSettingsInput
Signed-off-by: Chris Hesterman
* Auto populate prefix field initially with "restored_"
Signed-off-by: Chris Hesterman
* Change Restore button to color="primary" (blue)
Signed-off-by: Chris Hesterman
* Make flyout close on restore, direct user to activities tab
Signed-off-by: Chris Hesterman
* Title case flyout 'status', use EuiHealth for status color dot
Signed-off-by: Chris Hesterman
* Add auto status check until 'DONE', RestoreActivities
Signed-off-by: Chris Hesterman
* Add auto status update until 'SUCCESS', Snapshots panel
Signed-off-by: Chris Hesterman
* Change tab implementation to be consistent with other plugins
Signed-off-by: Chris Hesterman
* Minor text and styling fixes, Snapshots panel
Signed-off-by: Chris Hesterman
* Fix styling to match main Snapshot page, RestoreFlyout
Signed-off-by: Chris Hesterman
* Update text to match mocks in Snapshots, RestoreSnapshotFlyout
Signed-off-by: Chris Hesterman
* Pre fill rename inputs, prefix input with default values from mock
Signed-off-by: Chris Hesterman
* Change to title case for status in Restore activities panel
Signed-off-by: Chris Hesterman
* Add warning banner if snapshot is partial, with checkbox to allow
Signed-off-by: Chris Hesterman
* Fix behavior of IndexList, include size when active, else unknown
Signed-off-by: Chris Hesterman
* Functioning start/stop times in RestoreActivities
Signed-off-by: Chris Hesterman
Accurate listing of indices being restored from snapshot
Signed-off-by: Chris Hesterman
* Restore start/stop working. Auto updating working.
Signed-off-by: Chris Hesterman
* Add EuiHealth to RestoreSnapshotFlyout
Signed-off-by: Chris Hesterman
* Progress with restore monitoring, finally error free
Signed-off-by: Chris Hesterman
* Progress on restore/delete buttons correctly disabled edge cases
Signed-off-by: Chris Hesterman
* Add EuiOverlayMask when RestoreSnapshotFlyout active
Signed-off-by: Chris Hesterman
* Create user friendly error toasts with button to full error
Signed-off-by: Chris Hesterman
* Direct user to browser console for full error from error toasts
Signed-off-by: Chris Hesterman
* Progress with restore success toast with button. Close
Signed-off-by: Chris Hesterman
* Custom restore success toast with button to activities panel done
Signed-off-by: Chris Hesterman
* Created custom error toast. Need to debug.
Signed-off-by: Chris Hesterman
* Custom error and success toasts fully functional and error free.
Signed-off-by: Chris Hesterman
* Add and debug Index Settings portion of Advanced Options. Successful.
Signed-off-by: Chris Hesterman
* Remove stray console logs
Signed-off-by: Chris Hesterman
* Add, update jest and cypress testing for snapshots & restore snapshots
Signed-off-by: Chris Hesterman
* Save small change snapshots_spec, forgot before prev commit
Signed-off-by: Chris Hesterman
* Version bump 2.3.0 to 2.3 1
Signed-off-by: Chris Hesterman
* Add version bump 2.3.1 for OS in cypress-workflow.yml
Signed-off-by: Chris Hesterman
* Remove 2.3.1 bump
Signed-off-by: Chris Hesterman
* Change 2.3 to 2.3.0 cypress-workflow.yml
Signed-off-by: Chris Hesterman
* Change OSDB version to 2.3.1, opensearch_dashboards.json
Signed-off-by: Chris Hesterman
* Change 2.4 to 2.4.0 opensearch_dashboards.json
Signed-off-by: Chris Hesterman
* Update for 2.4
Signed-off-by: Chris Hesterman
* Update help text, Snapshots.jsx
Signed-off-by: Chris Hesterman
* Remove Total size column from IndexList
Signed-off-by: Chris Hesterman
* Disable restore if partial snapshot but not checked in banner
Signed-off-by: Chris Hesterman
Remove total size column in indexList
Signed-off-by: Chris Hesterman
* Add status code and code meaning to toasts, error modal
Signed-off-by: Chris Hesterman
* Progress making Restore activities panel independent
Signed-off-by: Chris Hesterman
* RestoreActivitiesPanel no longer needs selected snapshot to access
Signed-off-by: Chris Hesterman
* Clean up unused code,console logs
Signed-off-by: Chris Hesterman
* Modify error toasts to be more user friendly
Signed-off-by: Chris Hesterman
* Handle bad regex input, handle bad JSON input, RestoreSnapshotFlyout
Signed-off-by: Chris Hesterman
* Add check and toast for no indices selected, RestoreSnapshotFlyout
Signed-off-by: Chris Hesterman
* Remove unused interface, server/models/interfaces
Signed-off-by: Chris Hesterman
* Remove unused interface, console log.
Signed-off-by: Chris Hesterman
* Small style changes, RestoreSnapshotFlyout
Signed-off-by: Chris Hesterman
* Resolve one missed conflict from previous merge
Signed-off-by: Chris Hesterman
* Update snapshots_spec.js
Signed-off-by: Chris Hesterman
* Remove unneeded cy.wait, snapshots_spec
Signed-off-by: Chris Hesterman
* Fix small bug with refresh button, Restore activities panel
Signed-off-by: Chris Hesterman
* Remove conflicts in server/models/interfaces
Signed-off-by: Chris Hesterman
* Remove unused prop"ignore", change ternary to if() in toast helper
Signed-off-by: Chris Hesterman
* Change label text styling for radios and checkboxes in flyout
Signed-off-by: Chris Hesterman
* Create checkBoxLabel component in helper.jsx
Signed-off-by: Chris Hesterman
* Update jest snapshots for unit tests.
Signed-off-by: Chris Hesterman
* Remove default close button from Restore activities/indiceslist
Signed-off-by: Chris Hesterman
Signed-off-by: Chris Hesterman
Signed-off-by: Chris Hesterman
---
cypress/integration/snapshots_spec.js | 2 +-
public/models/interfaces.ts | 13 ++
.../components/ErrorModal/ErrorModal.tsx | 36 ++++
.../Snapshots/components/ErrorModal/index.ts | 8 +
.../components/IndexList/IndexList.tsx | 10 +-
.../RenameInput/RenameInput.test.tsx | 4 +-
.../components/RenameInput/RenameInput.tsx | 44 +++--
.../__snapshots__/RenameInput.test.tsx.snap | 100 ++++++----
.../RestoreActivitiesPanel.tsx | 98 ++++++----
.../RestoreSnapshotFlyout.tsx | 117 +++++++++---
.../SnapshotRenameOptions.tsx | 6 +-
.../SnapshotRenameOptions.test.tsx.snap | 57 +-----
.../SnapshotRestoreAdvancedOptions.tsx | 29 +--
.../SnapshotRestoreOption.tsx | 4 +-
.../SnapshotRestoreOption.test.tsx.snap | 38 +---
.../containers/Snapshots/Snapshots.tsx | 180 ++++++++++++++----
public/pages/Snapshots/helper.tsx | 57 +++++-
public/services/IndexService.ts | 2 +-
public/services/SnapshotManagementService.ts | 1 -
server/models/interfaces.ts | 2 +-
server/services/SnapshotManagementService.ts | 2 -
21 files changed, 526 insertions(+), 284 deletions(-)
create mode 100644 public/pages/Snapshots/components/ErrorModal/ErrorModal.tsx
create mode 100644 public/pages/Snapshots/components/ErrorModal/index.ts
diff --git a/cypress/integration/snapshots_spec.js b/cypress/integration/snapshots_spec.js
index 16dc09790..4eaa4631c 100644
--- a/cypress/integration/snapshots_spec.js
+++ b/cypress/integration/snapshots_spec.js
@@ -100,7 +100,7 @@ describe("Snapshots", () => {
cy.get("button").contains("Restore snapshot").click({ force: true });
// Check for success toast
- cy.contains("Restored snapshot test_snapshot to repository test_repo");
+ cy.contains(`Restore from snapshot "test_snapshot" is in progress.`);
});
});
diff --git a/public/models/interfaces.ts b/public/models/interfaces.ts
index e1eb26f39..67020883c 100644
--- a/public/models/interfaces.ts
+++ b/public/models/interfaces.ts
@@ -44,6 +44,19 @@ interface ArgsWithError {
}
export type OnSearchChangeArgs = ArgsWithQuery | ArgsWithError;
+export interface Toast {
+ id?: string;
+ title?: string;
+ iconType?: string;
+ color: string;
+ text?: JSX.Element;
+}
+
+export interface RestoreError {
+ reason?: string,
+ type?: string
+}
+
export interface LatestActivities {
activityType: "Creation" | "Deletion";
status?: string;
diff --git a/public/pages/Snapshots/components/ErrorModal/ErrorModal.tsx b/public/pages/Snapshots/components/ErrorModal/ErrorModal.tsx
new file mode 100644
index 000000000..2f64646cf
--- /dev/null
+++ b/public/pages/Snapshots/components/ErrorModal/ErrorModal.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { EuiModal, EuiText, EuiButton, EuiModalHeader, EuiModalFooter, EuiModalBody, EuiModalHeaderTitle } from "@elastic/eui";
+import React from "react";
+
+
+interface ErrorModalProps {
+ error: React.ErrorInfo;
+ onClick: (e: React.MouseEvent) => void;
+}
+
+const ErrorModal = ({ onClick, error }: ErrorModalProps) => {
+
+ return (
+ <>
+
+
+ {error.type}
+
+
+
+ {error.reason}.
+
+
+
+ Close
+
+
+ >
+ );
+};
+
+export default ErrorModal;
\ No newline at end of file
diff --git a/public/pages/Snapshots/components/ErrorModal/index.ts b/public/pages/Snapshots/components/ErrorModal/index.ts
new file mode 100644
index 000000000..be1bb1457
--- /dev/null
+++ b/public/pages/Snapshots/components/ErrorModal/index.ts
@@ -0,0 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import ErrorModal from "./ErrorModal";
+
+export default ErrorModal;
\ No newline at end of file
diff --git a/public/pages/Snapshots/components/IndexList/IndexList.tsx b/public/pages/Snapshots/components/IndexList/IndexList.tsx
index a4b85f2ed..f610c2fde 100644
--- a/public/pages/Snapshots/components/IndexList/IndexList.tsx
+++ b/public/pages/Snapshots/components/IndexList/IndexList.tsx
@@ -21,14 +21,10 @@ const IndexList = ({ indices, snapshot, onClick, title }: IndexListProps) => {
{
field: "index",
name: "Index",
- width: "70%",
+ width: "100%",
+ truncateText: true,
sortable: true,
- },
- {
- field: "store.size",
- name: "Total size",
- sortable: true,
- },
+ }
];
return (
diff --git a/public/pages/Snapshots/components/RenameInput/RenameInput.test.tsx b/public/pages/Snapshots/components/RenameInput/RenameInput.test.tsx
index a84ff53b2..b7a9e0264 100644
--- a/public/pages/Snapshots/components/RenameInput/RenameInput.test.tsx
+++ b/public/pages/Snapshots/components/RenameInput/RenameInput.test.tsx
@@ -34,11 +34,11 @@ describe("RenameInput component", () => {
it("accepts user input", () => {
// User enters text
- userEvent.type(screen.getByTestId("renamePatternInput"), "(.+)");
+ userEvent.type(screen.getByTestId("renamePatternInput"), "{selectall}{del}(.+)");
expect(screen.getByTestId("renamePatternInput")).toHaveValue("(.+)");
- userEvent.type(screen.getByTestId("renameReplacementInput"), "test_$1");
+ userEvent.type(screen.getByTestId("renameReplacementInput"), "{selectall}{del}test_$1");
expect(screen.getByTestId("renameReplacementInput")).toHaveValue("test_$1");
});
diff --git a/public/pages/Snapshots/components/RenameInput/RenameInput.tsx b/public/pages/Snapshots/components/RenameInput/RenameInput.tsx
index c94f87353..d509bb2ce 100644
--- a/public/pages/Snapshots/components/RenameInput/RenameInput.tsx
+++ b/public/pages/Snapshots/components/RenameInput/RenameInput.tsx
@@ -3,18 +3,17 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { EuiFormRow, EuiFieldText, EuiSpacer } from "@elastic/eui";
+import { EuiFormRow, EuiFieldText, EuiSpacer, EuiText, EuiLink } from "@elastic/eui";
import React, { useState, ChangeEvent } from "react";
-import CustomLabel from "../../../../components/CustomLabel";
-
+import { RESTORE_SNAPSHOT_DOCUMENTATION_URL } from "../../../../utils/constants"
interface RenameInputProps {
getRenamePattern: (prefix: string) => void;
getRenameReplacement: (prefix: string) => void;
}
const RenameInput = ({ getRenamePattern, getRenameReplacement }: RenameInputProps) => {
- const [renamePattern, setRenamePattern] = useState("");
- const [renameReplacement, setRenameReplacement] = useState("");
+ const [renamePattern, setRenamePattern] = useState("(.+)");
+ const [renameReplacement, setRenameReplacement] = useState("restored_$1");
const onPatternChange = (e: ChangeEvent) => {
setRenamePattern(e.target.value);
@@ -26,23 +25,44 @@ const RenameInput = ({ getRenamePattern, getRenameReplacement }: RenameInputProp
getRenameReplacement(e.target.value);
};
- const patternHelpText =
- "Use regular expressiojn to define how index names will be renamed. By default, input (.+) to reuse the entire index name. [Learn more]";
- const replacementHelpText =
- "Define the format of renamed indices. Use $0 to include the entire matching index name, $1 to include the content of the first capture group, etc. [Learn more]";
-
return (
<>
-
+
+ Rename Pattern
+
+
+
+ Use regular expression to define how index names will be renamed.
+
+ By default, input (.+) to reuse the entire index name.{" "}
+
+ Learn more
+
+
+
-
+
+ Rename Replacement
+
+
+
+ Define the format of renamed indices. Use $0 to include the
+
+ entire matching index name, $1 to include the content of the first
+
+ capture group, etc.{" "}
+
+ Learn more
+
+
+
diff --git a/public/pages/Snapshots/components/RenameInput/__snapshots__/RenameInput.test.tsx.snap b/public/pages/Snapshots/components/RenameInput/__snapshots__/RenameInput.test.tsx.snap
index 938fc61a6..22d72d475 100644
--- a/public/pages/Snapshots/components/RenameInput/__snapshots__/RenameInput.test.tsx.snap
+++ b/public/pages/Snapshots/components/RenameInput/__snapshots__/RenameInput.test.tsx.snap
@@ -6,28 +6,39 @@ exports[`RenameInput component renders without error 1`] = `
class="euiSpacer euiSpacer--l"
/>
+ Learn more
+ EuiIconMock
+
+ (opens in a new tab or window)
+
+
+
-
- Use regular expressiojn to define how index names will be renamed. By default, input (.+) to reuse the entire index name. [Learn more]
-
-
@@ -56,28 +67,41 @@ exports[`RenameInput component renders without error 1`] = `
class="euiSpacer euiSpacer--m"
/>
+ Learn more
+ EuiIconMock
+
+ (opens in a new tab or window)
+
+
+
-
- Define the format of renamed indices. Use $0 to include the entire matching index name, $1 to include the content of the first capture group, etc. [Learn more]
-
-
diff --git a/public/pages/Snapshots/components/RestoreActivitiesPanel/RestoreActivitiesPanel.tsx b/public/pages/Snapshots/components/RestoreActivitiesPanel/RestoreActivitiesPanel.tsx
index 6e03f3abc..5855c5f7a 100644
--- a/public/pages/Snapshots/components/RestoreActivitiesPanel/RestoreActivitiesPanel.tsx
+++ b/public/pages/Snapshots/components/RestoreActivitiesPanel/RestoreActivitiesPanel.tsx
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { EuiInMemoryTable, EuiSpacer, EuiLink, EuiFlyout, EuiButton } from "@elastic/eui";
+import { EuiInMemoryTable, EuiSpacer, EuiLink, EuiFlyout, EuiButton, EuiEmptyPrompt } from "@elastic/eui";
import _ from "lodash";
import React, { useEffect, useContext, useState, useMemo } from "react";
import { SnapshotManagementService } from "../../../../services";
@@ -17,11 +17,13 @@ import IndexList from "../IndexList";
interface RestoreActivitiesPanelProps {
snapshotManagementService: SnapshotManagementService;
snapshotId: string;
- repository: string;
restoreStartRef: number;
+ restoreCount: number
}
-export const RestoreActivitiesPanel: React.FC = ({ snapshotManagementService, snapshotId, restoreStartRef }: RestoreActivitiesPanelProps) => {
+const intervalIds: ReturnType[] = [];
+
+export const RestoreActivitiesPanel = ({ snapshotManagementService, snapshotId, restoreStartRef, restoreCount }: RestoreActivitiesPanelProps) => {
const context = useContext(CoreServicesContext);
const [startTime, setStartTime] = useState("");
const [stopTime, setStopTime] = useState("");
@@ -30,16 +32,17 @@ export const RestoreActivitiesPanel: React.FC = ({
const [flyout, setFlyout] = useState(false);
useEffect(() => {
- context!.chrome.setBreadcrumbs([BREADCRUMBS.SNAPSHOT_MANAGEMENT, BREADCRUMBS.SNAPSHOTS, BREADCRUMBS.SNAPSHOT_RESTORE]);
-
- let getStatusInterval: ReturnType;
+ context?.chrome.setBreadcrumbs([BREADCRUMBS.SNAPSHOT_MANAGEMENT, BREADCRUMBS.SNAPSHOTS, BREADCRUMBS.SNAPSHOT_RESTORE]);
- if (stage.slice(0, 4) !== "Done") {
- getStatusInterval = setInterval(() => {
+ if (stage !== "Done (100%)" || indices.length < restoreCount) {
+ intervalIds.push(setInterval(() => {
getRestoreStatus();
- }, 1000);
+ }, 2000))
+
return () => {
- clearInterval(getStatusInterval)
+ intervalIds.forEach((id) => {
+ clearInterval(id);
+ })
}
}
}, [stage]);
@@ -52,24 +55,27 @@ export const RestoreActivitiesPanel: React.FC = ({
try {
const res = await snapshotManagementService.getIndexRecovery();
- if (stage.indexOf("Done") >= 0) {
- return;
- }
-
if (res.ok) {
const response: GetIndexRecoveryResponse = res.response;
setRestoreStatus(response);
} else {
context?.notifications.toasts.addDanger(res.error);
+ const message = JSON.parse(res.error).error.root_cause[0].reason
+ const trimmedMessage = message.slice(message.indexOf("]") + 1, message.indexOf(".") + 1);
+ context?.notifications.toasts.addError(JSON.parse(res.error), {
+ title: `There was a problem loading the recovery status.`,
+ toastMessage: `${trimmedMessage} Open browser console & click below for details.`
+ });
}
} catch (err) {
- context?.notifications.toasts.addDanger(getErrorMessage(err, "There was a problem loading the recovery."));
+ context?.notifications.toasts.addDanger(getErrorMessage(err, "There was a problem loading the recovery status."));
}
};
- const onIndexesClick = (e: React.MouseEvent) => {
+ const onIndexesClick = async (e: React.MouseEvent) => {
e.preventDefault();
+ await getRestoreStatus();
setFlyout(true);
};
@@ -80,7 +86,7 @@ export const RestoreActivitiesPanel: React.FC = ({
const setRestoreStatus = (response: GetIndexRecoveryResponse) => {
let minStartTime: number = 0;
let maxStopTime: number = 0;
- let stageIndex: number = Infinity;
+ let stageIndex: number = 4;
let doneCount: number = 0;
const indexes: CatSnapshotIndex[] = [];
const stages: string[] = ["START", "INIT", "INDEX", "FINALIZE", "DONE"];
@@ -88,45 +94,55 @@ export const RestoreActivitiesPanel: React.FC = ({
// Loop through indices in response, filter out kibana index,
// gather progress info then use it to create progress field values.
for (let item in response) {
- if (item.indexOf("kibana") < 0) {
- const info = response[item as keyof GetIndexRecoveryResponse].shards[0]
+ const responseItem = item as keyof GetIndexRecoveryResponse;
+ if (
+ item.indexOf("kibana") < 0 &&
+ response[responseItem].shards &&
+ response[responseItem].shards[0].start_time_in_millis >= restoreStartRef
+ ) {
+ const info = response[responseItem].shards[0];
const stage = stages.indexOf(info.stage);
const size = `${(info.index.size.total_in_bytes / 1024 ** 2).toFixed(2)}mb`;
const time = {
start_time: info.start_time_in_millis,
- stop_time: info.stop_time_in_millis,
+ stop_time: info.stop_time_in_millis ? info.stop_time_in_millis : Date.now()
};
- doneCount = stage === stages.length - 1 ? doneCount + 1 : doneCount;
+ doneCount = stage === 4 ? doneCount + 1 : doneCount;
stageIndex = stage < stageIndex ? stage : stageIndex;
- minStartTime = minStartTime && minStartTime < time.start_time ? minStartTime : time.start_time;
+
maxStopTime = maxStopTime && maxStopTime > time.stop_time ? maxStopTime : time.stop_time;
if (info.source.index && info.source.snapshot === snapshotId) {
+ minStartTime = minStartTime && minStartTime < time.start_time ? minStartTime : time.start_time;
indexes.push({ index: info.source.index, "store.size": size });
}
}
}
- let percent = Math.floor((doneCount / indices.length) * 100);
- percent = stageIndex === 4 ? 100 : percent;
+ let percent = Math.floor((doneCount / restoreCount) * 100);
setIndices(indexes);
- setStartTime(new Date(minStartTime).toLocaleString().replace(",", " "));
- setStopTime(new Date(maxStopTime).toLocaleString().replace(",", " ") || "In progress");
- setStage(`${stages[stageIndex][0] + stages[stageIndex].toLowerCase().slice(1)} (${percent}%)`);
+ setStopTime(new Date(maxStopTime).toLocaleString().replace(",", " "));
+ setStartTime(new Date(minStartTime).toLocaleString().replace(",", " "))
+
+ if (stages[stageIndex]) {
+ stageIndex = (stageIndex === 4 && doneCount < restoreCount) ? 2 : stageIndex;
+ setStage(`${stages[stageIndex][0] + stages[stageIndex].toLowerCase().slice(1)} (${percent}%)`);
+ }
+
};
- const actions = useMemo(() => {
+ const actions = useMemo(() => (
[
-
+
Refresh
,
- ];
- }, [])
+ ]
+ ), []);
- const indexText = `${indices.length === 1 && Object.keys(indices[0]).length > 0 ? "Index" : "Indices"}`
- const indexes = `${indices.length === 1 && Object.keys(indices[0]).length === 0 ? "0" : indices.length} ${indexText}`;
+ const indexText = `${restoreCount === 1 ? "Index" : "Indices"}`
+ const indexes = `${restoreCount} ${indexText}`;
const restoreStatus = [
{
@@ -161,11 +177,23 @@ export const RestoreActivitiesPanel: React.FC = ({
},
];
+ const message = (There are no restore activities.} titleSize="s">)
+
return (
<>
- {flyout && }
+ {flyout &&
+
+
+
+ }
-
+
diff --git a/public/pages/Snapshots/components/RestoreSnapshotFlyout/RestoreSnapshotFlyout.tsx b/public/pages/Snapshots/components/RestoreSnapshotFlyout/RestoreSnapshotFlyout.tsx
index 393a0991d..bd529982d 100644
--- a/public/pages/Snapshots/components/RestoreSnapshotFlyout/RestoreSnapshotFlyout.tsx
+++ b/public/pages/Snapshots/components/RestoreSnapshotFlyout/RestoreSnapshotFlyout.tsx
@@ -15,6 +15,9 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiAccordion,
+ EuiCheckbox,
+ EuiCallOut,
+ EuiText
} from "@elastic/eui";
import _ from "lodash";
import React, { Component, ChangeEvent } from "react";
@@ -39,7 +42,7 @@ interface RestoreSnapshotProps {
snapshotManagementService: SnapshotManagementService;
indexService: IndexService;
onCloseFlyout: () => void;
- getTime: (time: number) => void
+ getRestoreInfo: (time: number, count: number) => void
restoreSnapshot: (snapshotId: string, repository: string, options: object) => void;
snapshotId: string;
repository: string;
@@ -53,16 +56,14 @@ interface RestoreSnapshotState {
renamePattern: string;
renameReplacement: string;
listIndices: boolean;
- indicesList: CatSnapshotIndex[];
-
- repositories: CatRepository[],
- selectedRepoValue: string,
customIndexSettings: string;
ignoreIndexSettings?: string;
+ indicesList: CatSnapshotIndex[];
+ selectedRepoValue: string;
+ repositories: CatRepository[];
snapshot: GetSnapshot | null;
restoreSpecific: boolean;
partial: boolean;
-
repoError: string;
snapshotIdError: string;
}
@@ -97,39 +98,41 @@ export default class RestoreSnapshotFlyout extends Component {
- const { restoreSnapshot, snapshotId, repository, onCloseFlyout, getTime } = this.props;
+ const { restoreSnapshot, snapshotId, repository, onCloseFlyout, getRestoreInfo } = this.props;
const {
- selectedRepoValue,
customIndexSettings,
ignoreIndexSettings,
restoreSpecific,
selectedIndexOptions,
indexOptions,
- snapshot,
renameIndices,
prefix,
+ snapshot,
renamePattern,
renameReplacement,
} = this.state;
const { add_prefix } = RESTORE_OPTIONS;
const selectedIndices = selectedIndexOptions.map((option) => option.label).join(",");
const allIndices = indexOptions.map((option) => option.label).join(",");
- // TODO replace unintelligible regex below with (.+) and add $1 to user provided prefix then add that to renameReplacement
- const pattern = renameIndices === add_prefix ? "(? {
+ try {
+ return JSON.parse(testString);
+ } catch (err) {
+ this.context.notifications.toasts.addError(err, { title: `Please enter valid JSON.` });
+ return false;
+ }
+ }
+
+ checkSelectedIndices = (indices: string): string | boolean => {
+ const { restoreSpecific } = this.state;
+ try {
+ if (restoreSpecific && indices.length === 0) {
+ throw "No indices selected.";
+ } else {
+ return indices;
+ }
+ } catch (err) {
+
+ return false;
+ }
+ }
+
onClickIndices = async () => {
const { snapshot } = this.state;
const indices = snapshot!.indices.join(",");
@@ -183,6 +210,13 @@ export default class RestoreSnapshotFlyout extends Component ({ index: index.index, ["store.size"]: index["store.size"] }))
- const inactiveIndices = snapshot?.indices.filter((index) => !activeIndexNames.includes(index) && index.length)
+ const inactiveIndices = snapshot?.indices.filter((index) => !activeIndexNames.includes(index) && index.length && index.indexOf("kibana") < 0)
.map((index) => ({ index: index, "store.size": "unknown" }));
this.setState({ indicesList: [...formattedIndices, ...inactiveIndices] });
} else {
- this.context.notifications.toasts.addDanger(response.error);
+ const message = JSON.parse(response.error).error.root_cause[0].reason
+ const trimmedMessage = message.slice(message.indexOf("]") + 1, message.indexOf(".") + 1);
+ this.context.notifications.toasts.addError(response.error, {
+ title: `There was a problem loading the indices for this snapshot`,
+ toastMessage: `${trimmedMessage} Open browser console & click below for details.`
+ });
}
} catch (err) {
this.context.notifications.toasts.addDanger(getErrorMessage(err, "There was a problem loading the indices for this snapshot."));
@@ -306,6 +345,7 @@ export default class RestoreSnapshotFlyout extends Component
@@ -325,18 +365,21 @@ export default class RestoreSnapshotFlyout extends Component
-
+
+
- {snapshot?.snapshot}
+
+ {snapshot?.snapshot}
- {snapshot?.state}
+ {status}
- {snapshot?.indices.length}
+
+ {snapshot?.indices.length}
@@ -397,8 +440,6 @@ export default class RestoreSnapshotFlyout extends Component
-
+
+
+
+ {snapshot?.failed_shards &&
+ You are about to restore a partial snapshot. One or more shards may be missing in this
snapshot. Do you want to continue?
+
+ Allow restore partial snapshots}
+ checked={String(_.get(snapshot, partial, false)) == "true"}
+ onChange={this.onToggle}
+ />
+ }
+
+
-
+
>
- )
- }
+ )}
);
}
diff --git a/public/pages/Snapshots/components/SnapshotRenameOptions/SnapshotRenameOptions.tsx b/public/pages/Snapshots/components/SnapshotRenameOptions/SnapshotRenameOptions.tsx
index aa7e9be72..e8e00e982 100644
--- a/public/pages/Snapshots/components/SnapshotRenameOptions/SnapshotRenameOptions.tsx
+++ b/public/pages/Snapshots/components/SnapshotRenameOptions/SnapshotRenameOptions.tsx
@@ -37,7 +37,7 @@ const SnapshotRenameOptions = ({
}
+ label="Do not rename"
checked={doNotRename}
onChange={onDoNotRenameToggle}
/>
@@ -47,7 +47,7 @@ const SnapshotRenameOptions = ({
}
+ label="Add prefix to restored index names"
checked={addPrefix}
onChange={onAddPrefixToggle}
/>
@@ -57,7 +57,7 @@ const SnapshotRenameOptions = ({
}
+ label="Rename using regular expression (Advanced)"
checked={renameIndices}
onChange={onRenameIndicesToggle}
/>
diff --git a/public/pages/Snapshots/components/SnapshotRenameOptions/__snapshots__/SnapshotRenameOptions.test.tsx.snap b/public/pages/Snapshots/components/SnapshotRenameOptions/__snapshots__/SnapshotRenameOptions.test.tsx.snap
index 36f6b5d1e..12120e8f4 100644
--- a/public/pages/Snapshots/components/SnapshotRenameOptions/__snapshots__/SnapshotRenameOptions.test.tsx.snap
+++ b/public/pages/Snapshots/components/SnapshotRenameOptions/__snapshots__/SnapshotRenameOptions.test.tsx.snap
@@ -27,24 +27,7 @@ exports[`SnapshotRenameOptions component renders without error 1`] = `
class="euiRadio__label"
for="do_not_rename"
>
-
-
+ Do not rename
-
-
+ Add prefix to restored index names
-
-
+ Rename using regular expression (Advanced)
diff --git a/public/pages/Snapshots/components/SnapshotRestoreAdvancedOptions/SnapshotRestoreAdvancedOptions.tsx b/public/pages/Snapshots/components/SnapshotRestoreAdvancedOptions/SnapshotRestoreAdvancedOptions.tsx
index a5725711a..f51bfbd7b 100644
--- a/public/pages/Snapshots/components/SnapshotRestoreAdvancedOptions/SnapshotRestoreAdvancedOptions.tsx
+++ b/public/pages/Snapshots/components/SnapshotRestoreAdvancedOptions/SnapshotRestoreAdvancedOptions.tsx
@@ -5,7 +5,7 @@
import React, { ChangeEvent } from "react";
import { EuiCheckbox, EuiSpacer, EuiText } from "@elastic/eui";
-import CustomLabel from "../../../../components/CustomLabel";
+import { CheckBoxLabel } from "../../helper"
import IndexSettingsInput from "../../components/IndexSettingsInput";
import { RESTORE_OPTIONS } from "../../../../models/interfaces";
@@ -17,8 +17,6 @@ interface SnapshotAdvancedOptionsProps {
onRestoreClusterStateToggle: (e: ChangeEvent) => void;
ignoreUnavailable: boolean;
onIgnoreUnavailableToggle: (e: ChangeEvent) => void;
- restorePartial: boolean;
- onRestorePartialToggle: (e: ChangeEvent) => void;
customizeIndexSettings: boolean;
onCustomizeIndexSettingsToggle: (e: ChangeEvent) => void;
ignoreIndexSettings: boolean;
@@ -34,8 +32,6 @@ const SnapshotRestoreAdvancedOptions = ({
onIgnoreUnavailableToggle,
restoreClusterState,
onRestoreClusterStateToggle,
- restorePartial,
- onRestorePartialToggle,
customizeIndexSettings,
onCustomizeIndexSettingsToggle,
ignoreIndexSettings,
@@ -46,7 +42,6 @@ const SnapshotRestoreAdvancedOptions = ({
restore_aliases,
include_global_state,
ignore_unavailable,
- partial,
customize_index_settings,
ignore_index_settings,
} = RESTORE_OPTIONS;
@@ -55,7 +50,7 @@ const SnapshotRestoreAdvancedOptions = ({
}
+ label={
}
checked={restoreAliases}
onChange={onRestoreAliasesToggle}
/>
@@ -64,7 +59,7 @@ const SnapshotRestoreAdvancedOptions = ({
}
+ label={
Restore cluster state from snapshots}
checked={restoreClusterState}
onChange={onRestoreClusterStateToggle}
/>
@@ -74,7 +69,7 @@ const SnapshotRestoreAdvancedOptions = ({
@@ -83,15 +78,6 @@ const SnapshotRestoreAdvancedOptions = ({
onChange={onIgnoreUnavailableToggle}
/>
-
-
-
}
- checked={restorePartial}
- onChange={onRestorePartialToggle}
- />
-
Custom index settings
@@ -107,7 +93,7 @@ const SnapshotRestoreAdvancedOptions = ({
}
+ label={
}
checked={customizeIndexSettings}
onChange={onCustomizeIndexSettingsToggle}
/>
@@ -119,7 +105,10 @@ const SnapshotRestoreAdvancedOptions = ({
+
}
checked={ignoreIndexSettings}
onChange={onIgnoreIndexSettingsToggle}
diff --git a/public/pages/Snapshots/components/SnapshotRestoreOption/SnapshotRestoreOption.tsx b/public/pages/Snapshots/components/SnapshotRestoreOption/SnapshotRestoreOption.tsx
index 49dd2fec4..6398947ba 100644
--- a/public/pages/Snapshots/components/SnapshotRestoreOption/SnapshotRestoreOption.tsx
+++ b/public/pages/Snapshots/components/SnapshotRestoreOption/SnapshotRestoreOption.tsx
@@ -34,7 +34,7 @@ const SnapshotRestoreOption = ({
}
+ label="Restore all indices in snapshot"
checked={restoreAllIndices}
onChange={onRestoreAllIndicesToggle}
/>
@@ -44,7 +44,7 @@ const SnapshotRestoreOption = ({
}
+ label="Restore specific indices"
checked={restoreSpecificIndices}
onChange={onRestoreSpecificIndicesToggle}
/>
diff --git a/public/pages/Snapshots/components/SnapshotRestoreOption/__snapshots__/SnapshotRestoreOption.test.tsx.snap b/public/pages/Snapshots/components/SnapshotRestoreOption/__snapshots__/SnapshotRestoreOption.test.tsx.snap
index 879e63c5c..5509d887b 100644
--- a/public/pages/Snapshots/components/SnapshotRestoreOption/__snapshots__/SnapshotRestoreOption.test.tsx.snap
+++ b/public/pages/Snapshots/components/SnapshotRestoreOption/__snapshots__/SnapshotRestoreOption.test.tsx.snap
@@ -27,24 +27,7 @@ exports[`SnapshotRestoreOption component renders without error 1`] = `
class="euiRadio__label"
for="restore_all_indices"
>
-
-
+ Restore all indices in snapshot
-
-
+ Restore specific indices
diff --git a/public/pages/Snapshots/containers/Snapshots/Snapshots.tsx b/public/pages/Snapshots/containers/Snapshots/Snapshots.tsx
index 922721a62..0c3c53514 100644
--- a/public/pages/Snapshots/containers/Snapshots/Snapshots.tsx
+++ b/public/pages/Snapshots/containers/Snapshots/Snapshots.tsx
@@ -6,11 +6,23 @@
import React, { Component } from "react";
import _ from "lodash";
import { RouteComponentProps } from "react-router-dom";
-import { EuiButton, EuiInMemoryTable, EuiLink, EuiTableFieldDataColumnType, EuiText, EuiPageHeader, EuiTabs, EuiTab } from "@elastic/eui";
+import {
+ EuiButton,
+ EuiInMemoryTable,
+ EuiLink,
+ EuiTableFieldDataColumnType,
+ EuiText,
+ EuiPageHeader,
+ EuiTabs,
+ EuiTab,
+ EuiOverlayMask,
+ EuiGlobalToastList,
+} from "@elastic/eui";
import { FieldValueSelectionFilterConfigType } from "@elastic/eui/src/components/search_bar/filters/field_value_selection_filter";
import { CoreServicesContext } from "../../../../components/core_services";
import { SnapshotManagementService, IndexService } from "../../../../services";
import { getErrorMessage } from "../../../../utils/helpers";
+import { Toast, RestoreError } from "../../../../models/interfaces"
import { CatSnapshotWithRepoAndPolicy as SnapshotsWithRepoAndPolicy } from "../../../../../server/models/interfaces";
import { ContentPanel } from "../../../../components/ContentPanel";
import SnapshotFlyout from "../../components/SnapshotFlyout/SnapshotFlyout";
@@ -18,9 +30,11 @@ import CreateSnapshotFlyout from "../../components/CreateSnapshotFlyout";
import RestoreSnapshotFlyout from "../../components/RestoreSnapshotFlyout";
import RestoreActivitiesPanel from "../../components/RestoreActivitiesPanel";
import { Snapshot } from "../../../../../models/interfaces";
-import { BREADCRUMBS, RESTORE_SNAPSHOT_DOCUMENTATION_URL, ROUTES } from "../../../../utils/constants";
+import { BREADCRUMBS, ROUTES } from "../../../../utils/constants";
import { renderTimestampMillis } from "../../../SnapshotPolicies/helpers";
+import ErrorModal from "../../../Snapshots/components/ErrorModal/ErrorModal"
import DeleteModal from "../../../Repositories/components/DeleteModal/DeleteModal";
+import { getToasts } from "../../helper"
import { snapshotStatusRender, truncateSpan } from "../../helper";
interface SnapshotsProps extends RouteComponentProps {
@@ -34,6 +48,10 @@ interface SnapshotsState {
loadingSnapshots: boolean;
snapshotPanel: boolean;
restoreStart: number;
+ restoreCount: number;
+ toasts: Toast[];
+ viewError: boolean;
+ error: RestoreError;
selectedItems: SnapshotsWithRepoAndPolicy[];
@@ -52,6 +70,7 @@ interface SnapshotsState {
export default class Snapshots extends Component {
static contextType = CoreServicesContext;
columns: EuiTableFieldDataColumnType[];
+ private tabsRef;
constructor(props: SnapshotsProps) {
super(props);
@@ -62,6 +81,10 @@ export default class Snapshots extends Component
loadingSnapshots: false,
snapshotPanel: true,
restoreStart: 0,
+ restoreCount: 0,
+ toasts: [],
+ error: {},
+ viewError: false,
selectedItems: [],
showFlyout: false,
flyoutSnapshotId: "",
@@ -128,6 +151,7 @@ export default class Snapshots extends Component
},
];
+ this.tabsRef = React.createRef();
this.getSnapshots = _.debounce(this.getSnapshots, 500, { leading: true });
}
@@ -150,7 +174,12 @@ export default class Snapshots extends Component
] as string[];
this.setState({ snapshots, existingPolicyNames });
} else {
- this.context.notifications.toasts.addDanger(response.error);
+ const message = JSON.parse(response.error).error.root_cause[0].reason
+ const trimmedMessage = message.slice(message.indexOf("]") + 1, message.indexOf(".") + 1);
+ this.context.notifications.toasts.addError(response.error, {
+ title: `There was a problem getting the snapshots.`,
+ toastMessage: `${trimmedMessage} Open browser console & click below for details.`
+ });
}
} catch (err) {
this.context.notifications.toasts.addDanger(getErrorMessage(err, "There was a problem loading the snapshots."));
@@ -160,6 +189,7 @@ export default class Snapshots extends Component
};
onSelectionChange = (selectedItems: SnapshotsWithRepoAndPolicy[]): void => {
+ if (this.state.showRestoreFlyout) return;
this.setState({ selectedItems });
};
@@ -182,7 +212,12 @@ export default class Snapshots extends Component
if (response.ok) {
this.context.notifications.toasts.addSuccess(`Deleted snapshot ${snapshotId} from repository ${repository}.`);
} else {
- this.context.notifications.toasts.addDanger(response.error);
+ const message = JSON.parse(response.error).error.root_cause[0].reason
+ const trimmedMessage = message.slice(message.indexOf("]") + 1, message.indexOf(".") + 1);
+ this.context.notifications.toasts.addError(response.error, {
+ title: `There was a problem deleting the snapshot.`,
+ toastMessage: `${trimmedMessage} Open browser console & click below for details.`
+ });
}
} catch (err) {
this.context.notifications.toasts.addDanger(getErrorMessage(err, "There was a problem deleting the snapshot."));
@@ -206,7 +241,13 @@ export default class Snapshots extends Component
this.context.notifications.toasts.addSuccess(`Created snapshot ${snapshotId} in repository ${repository}.`);
await this.getSnapshots();
} else {
- this.context.notifications.toasts.addDanger(response.error);
+ const message = JSON.parse(response.error).error.root_cause[0].reason
+ const trimmedMessage = message.slice(message.indexOf("]") + 1, message.indexOf(".") + 1);
+
+ this.context.notifications.toasts.addError(response.error, {
+ title: `There was a problem creating the snapshot.`,
+ toastMessage: `${trimmedMessage} Open browser console & click below for details.`
+ });
}
} catch (err) {
this.context.notifications.toasts.addDanger(getErrorMessage(err, "There was a problem creating the snapshot."));
@@ -215,27 +256,58 @@ export default class Snapshots extends Component
restoreSnapshot = async (snapshotId: string, repository: string, options: object) => {
try {
+ await this.setState({ toasts: [] })
const { snapshotManagementService } = this.props;
const response = await snapshotManagementService.restoreSnapshot(snapshotId, repository, options);
if (response.ok) {
- this.context.notifications.toasts.addSuccess(`Restored snapshot ${snapshotId} to repository ${repository}. View restore status in "Restore activities in progress" tab`);
+ this.onRestore(true, response);
} else {
- this.context.notifications.toasts.addDanger(response.error);
+ this.onRestore(false, JSON.parse(response.error).error);
}
} catch (err) {
this.context.notifications.toasts.addDanger(getErrorMessage(err, "There was a problem restoring the snapshot."));
}
};
- getRestoreReferenceTime = (time: number) => {
- this.setState({ restoreStart: time })
+ onRestore = (success: boolean, error: object = {}) => {
+ const { selectedItems } = this.state;
+ let errorMessage: string | undefined;
+ if (!success) {
+ const rawMessage = error.reason;
+ const index = rawMessage.indexOf("]") + 1;
+ const startIndex = rawMessage[index] === " " ? index + 1 : index;
+ const message = rawMessage.slice(startIndex).replace(/[\[\]]/g, '"');
+ errorMessage = message.charAt(0).toUpperCase() + message.slice(1);
+ errorMessage = errorMessage?.slice(0, 125) + "...";
+ }
+
+ const toasts = success ?
+ getToasts("success_restore_toast", errorMessage, selectedItems[0].id, this.onClickTab) :
+ getToasts("error_restore_toast", errorMessage, selectedItems[0].id, this.onOpenError);
+ this.setState({ toasts, error: error });
+ }
+
+ onOpenError = () => {
+ this.setState({ viewError: true });
+ }
+
+ onCloseModal = () => {
+ this.setState({ viewError: false, error: {} });
+ }
+
+ getRestoreInfo = (time: number, count: number) => {
+ this.setState({ restoreStart: time, restoreCount: count })
}
onClickRestore = async () => {
this.setState({ showRestoreFlyout: true });
};
+ onToastEnd = () => {
+ this.setState({ toasts: [] });
+ }
+
onCloseRestoreFlyout = () => {
this.setState({ showRestoreFlyout: false });
};
@@ -248,26 +320,38 @@ export default class Snapshots extends Component
const prev = target.previousElementSibling;
const next = target.nextElementSibling;
- if (selectedItems.length === 0) {
- this.context.notifications.toasts.addWarning("Please select a snapshot to view restore activities");
- return;
+ if (snapshotPanel) {
+ this.context.chrome.setBreadcrumbs([BREADCRUMBS.SNAPSHOT_MANAGEMENT, BREADCRUMBS.SNAPSHOTS]);
}
- this.context.chrome.setBreadcrumbs([BREADCRUMBS.SNAPSHOT_MANAGEMENT, BREADCRUMBS.SNAPSHOTS]);
+ if (target.textContent !== "View restore activities") {
+ target.ariaSelected = "true";
+ target.classList.add("euiTab-isSelected");
- target.ariaSelected = "true";
- target.classList.add("euiTab-isSelected");
+ if (prev) {
+ prev.classList.remove("euiTab-isSelected");
+ prev.ariaSelected = "false";
+ }
- if (prev) {
- prev.classList.remove("euiTab-isSelected");
- prev.ariaSelected = "false";
- }
- if (next) {
- next.classList.remove("euiTab-isSelected");
- next.ariaSelected = "false";
+ if (next) {
+ next.classList.remove("euiTab-isSelected");
+ next.ariaSelected = "false";
+ }
+ } else {
+ const firstTab = this.tabsRef.current?.firstChild;
+ const secondTab = this.tabsRef.current?.lastChild;
+
+ firstTab!.ariaSelected = "false";
+ firstTab!.classList.remove("euiTab-isSelected");
+
+ secondTab!.ariaSelected = "true";
+ secondTab!.classList.add("euiTab-isSelected");
}
+ let newState = { snapshotPanel: snapshotPanel, selectedItems }
- this.setState({ snapshotPanel: snapshotPanel });
+ if (snapshotPanel) newState.selectedItems = [];
+
+ this.setState(newState);
};
render() {
@@ -277,7 +361,11 @@ export default class Snapshots extends Component
selectedItems,
loadingSnapshots,
snapshotPanel,
+ toasts,
+ viewError,
+ error,
restoreStart,
+ restoreCount,
showFlyout,
flyoutSnapshotId,
flyoutSnapshotRepo,
@@ -335,11 +423,8 @@ export default class Snapshots extends Component
const subTitleText = (
- Snapshots are taken automatically from snapshot policies, or you can initiate manual snapshots to save to a repository.
- To restore a snapshot, use the snapshot restore API.{" "}
-
- Learn more
-
+ Snapshots of indices are taken automatically from snapshot policies,
or you can initiate manual snapshots to save to a repository.
+ You can restore indices by selecting a snapshot.
);
@@ -347,7 +432,7 @@ export default class Snapshots extends Component
return (
<>
-
+
Snapshots
Restore activities in progress
@@ -355,11 +440,12 @@ export default class Snapshots extends Component
{snapshotPanel || (
)}
+
{snapshotPanel && (
/>
)}
+ {/* Overlay added to preserve correct Delete/Restore button status, accurately depict selected snapshots upon leaving flyout */}
{showRestoreFlyout && (
-
+
+
+
+ )}
+
+
+
+ {viewError && (
+
)}
{isDeleteModalVisible && (
diff --git a/public/pages/Snapshots/helper.tsx b/public/pages/Snapshots/helper.tsx
index 7cc12680b..c45f6cb86 100644
--- a/public/pages/Snapshots/helper.tsx
+++ b/public/pages/Snapshots/helper.tsx
@@ -5,7 +5,8 @@
import React from "react";
import _ from "lodash";
-import { EuiHealth } from "@elastic/eui";
+import { Toast } from "../../models/interfaces"
+import { EuiHealth, EuiButton, EuiFlexGroup, EuiSpacer, EuiText } from "@elastic/eui";
export function truncateLongText(text: string, truncateLen: number = 20): string {
if (text.length > truncateLen) {
@@ -28,3 +29,57 @@ export function snapshotStatusRender(value: string): React.ReactElement {
return {capital};
}
+
+
+export const getToasts = (id: string, message: string | undefined, snapshotId: string, onClick: (e: React.MouseEvent) => void): Toast[] => {
+ const toasts = [
+ {
+ id: "success_restore_toast",
+ title: `Restore from snapshot "${snapshotId}" is in progress.`,
+ iconType: "check",
+ color: "success",
+ text: (
+ <>
+
+
+ View restore activities
+
+ >
+ )
+ },
+ {
+ id: "error_restore_toast",
+ title: `Failed to restore snapshot "${snapshotId}"`,
+ color: "danger",
+ text: (
+ <>
+ {message}
+
+
+ View full error
+
+ >
+ )
+ }
+ ]
+ if (id === "success_restore_toast") {
+ return [toasts[0]]
+ }
+ return [toasts[1]];
+}
+
+interface CheckboxLabelProps {
+ title: string;
+ helpText: string;
+}
+
+export const CheckBoxLabel = ({ title, helpText }: CheckboxLabelProps) => (
+ <>
+ {title}
+
+ {helpText}
+
+ >
+);
diff --git a/public/services/IndexService.ts b/public/services/IndexService.ts
index 5e3e8248e..da2d058a4 100644
--- a/public/services/IndexService.ts
+++ b/public/services/IndexService.ts
@@ -38,7 +38,7 @@ export default class IndexService {
getDataStreamsAndIndicesNames = async (searchValue: string): Promise> => {
const [getIndicesResponse, getDataStreamsResponse] = await Promise.all([
- this.getIndices({ from: 0, size: 10, search: searchValue, sortDirection: "desc", sortField: "index", showDataStreams: true }),
+ this.getIndices({ from: 0, size: 100, search: searchValue, sortDirection: "desc", sortField: "index", showDataStreams: true }),
this.getDataStreams({ search: searchValue }),
]);
diff --git a/public/services/SnapshotManagementService.ts b/public/services/SnapshotManagementService.ts
index cddf89378..75fe1fa22 100644
--- a/public/services/SnapshotManagementService.ts
+++ b/public/services/SnapshotManagementService.ts
@@ -64,7 +64,6 @@ export default class SnapshotManagementService {
getIndexRecovery = async (): Promise> => {
const url = NODE_API._RECOVERY;
- console.log("URL", url);
const response = (await this.httpClient.get(url)) as ServerResponse;
return response;
};
diff --git a/server/models/interfaces.ts b/server/models/interfaces.ts
index 0b995160a..9d3be7544 100644
--- a/server/models/interfaces.ts
+++ b/server/models/interfaces.ts
@@ -385,7 +385,6 @@ export interface GetIndexRecoveryResponse {
];
};
}
-
export interface CatSnapshotWithRepoAndPolicy {
id: string;
status: string;
@@ -434,6 +433,7 @@ export interface GetSnapshot {
restore_aliases?: boolean;
ignore_unavailable?: boolean;
ignore_index_settings?: boolean;
+ failed_shards?: number;
rename_pattern?: string;
rename_replacement?: string;
partial?: boolean;
diff --git a/server/services/SnapshotManagementService.ts b/server/services/SnapshotManagementService.ts
index 1aa3ebe46..fb236dc8a 100644
--- a/server/services/SnapshotManagementService.ts
+++ b/server/services/SnapshotManagementService.ts
@@ -314,7 +314,6 @@ export default class SnapshotManagementService {
queryString: queryString.trim() ? `${queryString.trim()}` : "*",
};
const res = await callWithRequest("ism.getSMPolicies", params);
- console.log("policy response", res);
const policies: DocumentSMPolicy[] = res.policies.map(
(p: { _id: string; _seq_no: number; _primary_term: number; sm_policy: SMPolicy }) => ({
seqNo: p._seq_no,
@@ -515,7 +514,6 @@ export default class SnapshotManagementService {
request: OpenSearchDashboardsRequest,
response: OpenSearchDashboardsResponseFactory
): Promise>> => {
- console.log(request);
try {
const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request);
const res: CatSnapshotIndex[] = await callWithRequest("cat.indices", {