From a67b93c5a93421da688134745943a3c4dbed708b Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Tue, 9 Apr 2019 16:57:19 -0700 Subject: [PATCH 01/14] Add SnapshotTable and SnapshotDetails. --- .../home/repository_list/repository_list.tsx | 2 +- .../snapshot_list/snapshot_details/index.ts | 7 + .../snapshot_details/snapshot_details.tsx | 39 +++++ .../home/snapshot_list/snapshot_list.tsx | 136 ++++++++++++++++- .../snapshot_list/snapshot_table/index.ts | 7 + .../snapshot_table/snapshot_table.tsx | 142 ++++++++++++++++++ .../documentation/documentation_links.ts | 4 + .../public/app/services/http/index.ts | 3 +- .../{requests.ts => repository_requests.ts} | 0 .../app/services/http/snapshot_requests.ts | 22 +++ 10 files changed, 357 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/index.ts create mode 100644 x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx create mode 100644 x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/index.ts create mode 100644 x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx rename x-pack/plugins/snapshot_restore/public/app/services/http/{requests.ts => repository_requests.ts} (100%) create mode 100644 x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx index f5baeeb6868ae..bc695b75c9aba 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx @@ -64,7 +64,7 @@ export const RepositoryList: React.FunctionComponent = ({ ); diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/index.ts b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/index.ts new file mode 100644 index 0000000000000..a4934bae7dfa7 --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SnapshotDetails } from './snapshot_details'; diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx new file mode 100644 index 0000000000000..50a31afd8456d --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; + +interface Props extends RouteComponentProps { + snapshotId: string; + onClose: () => void; +} + +const SnapshotDetailsUi: React.FunctionComponent = ({ snapshotId, onClose, history }) => { + return ( + + + +

+ {snapshotId} +

+
+
+ + Details go here +
+ ); +}; + +export const SnapshotDetails = withRouter(SnapshotDetailsUi); diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx index a45ef4fbb27f6..dcc8deb0a75ef 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx @@ -4,8 +4,138 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Fragment, useEffect, useState } from 'react'; +import { RouteComponentProps } from 'react-router-dom'; -export const SnapshotList: React.FunctionComponent = () => { - return
List of snapshots
; +import { SectionError, SectionLoading } from '../../../components'; +import { BASE_PATH } from '../../../constants'; +import { useAppDependencies } from '../../../index'; +import { documentationLinksService } from '../../../services/documentation'; +import { loadSnapshots } from '../../../services/http'; + +import { SnapshotDetails } from './snapshot_details'; +import { SnapshotTable } from './snapshot_table'; + +import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; + +interface MatchParams { + snapshotId?: string; +} +interface Props extends RouteComponentProps {} + +export const SnapshotList: React.FunctionComponent = ({ + match: { + params: { snapshotId }, + }, + history, +}) => { + const { + core: { + i18n: { FormattedMessage }, + }, + } = useAppDependencies(); + + const { + error, + loading, + data: { snapshots }, + request: reload, + } = loadSnapshots(); + + const [currentSnapshot, setCurrentSnapshot] = useState(undefined); + + const openSnapshotDetails = (id: string) => { + setCurrentSnapshot(id); + history.push(`${BASE_PATH}/snapshots/${id}`); + }; + + const closeSnapshotDetails = () => { + setCurrentSnapshot(undefined); + history.push(`${BASE_PATH}/snapshots`); + }; + + useEffect( + () => { + setCurrentSnapshot(snapshotId); + }, + [snapshotId] + ); + + if (loading) { + return ( + + + + ); + } + + if (error) { + return ( + + } + error={error} + /> + ); + } + + if (snapshots && snapshots.length === 0) { + return ( + + + + } + body={ + +

+ +

+
+ } + actions={ + + + + } + /> + ); + } + + return ( + + {currentSnapshot ? ( + + ) : null} + + + ); }; diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/index.ts b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/index.ts new file mode 100644 index 0000000000000..f09487fd3f10a --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SnapshotTable } from './snapshot_table'; diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx new file mode 100644 index 0000000000000..a6c824d3882f7 --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiButton, EuiInMemoryTable, EuiLink } from '@elastic/eui'; +import moment from 'moment'; +import React from 'react'; +import { RouteComponentProps, withRouter } from 'react-router-dom'; + +import { Snapshot } from '../../../../../../common/types'; +import { useAppDependencies } from '../../../../index'; + +const DATE_FORMAT = 'MMMM Do, YYYY h:mm:ss A'; + +interface Props extends RouteComponentProps { + snapshots: Snapshot[]; + reload: () => Promise; + openSnapshotDetails: (id: string) => void; +} + +const SnapshotTableUi: React.FunctionComponent = ({ + snapshots, + reload, + openSnapshotDetails, + history, +}) => { + const { + core: { + i18n: { FormattedMessage, translate }, + }, + } = useAppDependencies(); + + const columns = [ + { + field: 'id', + name: translate('xpack.snapshotRestore.snapshotList.table.idColumnTitle', { + defaultMessage: 'ID', + }), + truncateText: true, + sortable: true, + render: (id: string, snapshot: Snapshot) => ( + openSnapshotDetails(id)}>{id} + ), + }, + { + field: 'summary.startEpoch', + name: translate('xpack.snapshotRestore.snapshotList.table.startTimeColumnTitle', { + defaultMessage: 'Date created', + }), + truncateText: true, + sortable: true, + render: (startEpoch: string) => moment.unix(Number(startEpoch)).format(DATE_FORMAT), + }, + { + field: 'summary.duration', + name: translate('xpack.snapshotRestore.snapshotList.table.durationColumnTitle', { + defaultMessage: 'Duration', + }), + truncateText: true, + sortable: true, + width: '120px', + render: (duration: string) => duration, + }, + { + field: 'summary.indices', + name: translate('xpack.snapshotRestore.snapshotList.table.indicesColumnTitle', { + defaultMessage: 'Indices', + }), + truncateText: true, + sortable: true, + width: '120px', + render: (indices: string) => indices, + }, + { + field: 'summary.totalShards', + name: translate('xpack.snapshotRestore.snapshotList.table.shardsColumnTitle', { + defaultMessage: 'Shards', + }), + truncateText: true, + sortable: true, + width: '120px', + render: (totalShards: string) => totalShards, + }, + { + field: 'summary.failedShards', + name: translate('xpack.snapshotRestore.snapshotList.table.failedShardsColumnTitle', { + defaultMessage: 'Failed shards', + }), + truncateText: true, + sortable: true, + width: '120px', + render: (failedShards: string) => failedShards, + }, + ]; + + const sorting = { + sort: { + field: 'id', + direction: 'asc', + }, + }; + + const pagination = { + initialPageSize: 20, + pageSizeOptions: [10, 20, 50], + }; + + const search = { + toolsRight: ( + + + + ), + box: { + incremental: true, + schema: true, + }, + }; + + return ( + ({ + 'data-test-subj': 'srSnapshotListTableRow', + })} + cellProps={(item: any, column: any) => ({ + 'data-test-subj': `srSnapshotListTableCell-${column.field}`, + })} + /> + ); +}; + +export const SnapshotTable = withRouter(SnapshotTableUi); diff --git a/x-pack/plugins/snapshot_restore/public/app/services/documentation/documentation_links.ts b/x-pack/plugins/snapshot_restore/public/app/services/documentation/documentation_links.ts index 1fcb3fd7c6e4f..144d41c4c1fe6 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/documentation/documentation_links.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/documentation/documentation_links.ts @@ -40,6 +40,10 @@ class DocumentationLinksService { return `${this.esDocBasePath}${REPOSITORY_DOC_PATHS.default}`; } } + + public getSnapshotDocUrl() { + return `${this.esDocBasePath}/modules-snapshots.html#_snapshot`; + } } export const documentationLinksService = new DocumentationLinksService(); diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/index.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/index.ts index 87f938c296181..d8eda129b6966 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/index.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ export { httpService } from './http'; -export * from './requests'; +export * from './repository_requests'; +export * from './snapshot_requests'; diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/requests.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts similarity index 100% rename from x-pack/plugins/snapshot_restore/public/app/services/http/requests.ts rename to x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts new file mode 100644 index 0000000000000..2d13e3e8c5eed --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { API_BASE_PATH } from '../../../../common/constants'; +import { httpService } from './http'; +import { useRequest } from './use_request'; + +export const loadSnapshots = () => { + return useRequest({ + path: httpService.addBasePath(`${API_BASE_PATH}snapshots`), + method: 'get', + }); +}; + +export const loadSnapshot = (id: string) => { + return useRequest({ + path: httpService.addBasePath(`${API_BASE_PATH}snapshots/${encodeURIComponent(id)}`), + method: 'get', + }); +}; From c882a831a53739ab1601b9116d50b2048c559e30 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 10 Apr 2019 15:52:35 -0700 Subject: [PATCH 02/14] Remove associations between multiple repositories with a single snapshot. - Add snapshot column to table. - Retrieve complete snapshot details in getAllHandler. - Display and load detail panel while table is still loading. - Simplify detail-panel logic and update routes. - Fix cleanup function bug in useRequest hook. --- .../snapshot_restore/common/types/snapshot.ts | 24 +------- .../snapshot_restore/public/app/app.tsx | 6 +- .../repository_form/repository_form.tsx | 8 --- .../snapshot_restore/public/app/index.tsx | 1 + .../public/app/sections/home/home.tsx | 6 +- .../repository_details/repository_details.tsx | 32 +++++----- .../home/repository_list/repository_list.tsx | 4 +- .../snapshot_details/snapshot_details.tsx | 25 +++++++- .../home/snapshot_list/snapshot_list.tsx | 57 +++++++++--------- .../snapshot_table/snapshot_table.tsx | 60 ++++++++++++------- .../app/services/http/snapshot_requests.ts | 8 ++- .../public/app/services/http/use_request.ts | 11 +++- .../snapshot_restore/server/lib/index.ts | 2 +- .../server/lib/snapshot_serialization.ts | 42 +++---------- .../server/routes/api/snapshots.test.ts | 39 +++++++----- .../server/routes/api/snapshots.ts | 60 ++++++------------- .../snapshot_restore/server/types/snapshot.ts | 19 ------ 17 files changed, 178 insertions(+), 226 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/common/types/snapshot.ts b/x-pack/plugins/snapshot_restore/common/types/snapshot.ts index a4c68dafa65ed..449209e25f1ee 100644 --- a/x-pack/plugins/snapshot_restore/common/types/snapshot.ts +++ b/x-pack/plugins/snapshot_restore/common/types/snapshot.ts @@ -4,30 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface Snapshot { - id: string; - summary: SnapshotSummary; - repositories: string[]; -} - -export interface SnapshotSummary { - status: string; - /** This and other numerical values are typed as strings. e.g. '1554501400'. */ - startEpoch: string; - /** e.g. '21:56:40' */ - startTime: string; - endEpoch: string; - /** e.g. '21:56:45' */ - endTime: string; - /** Includes unit, e.g. '4.7s' */ - duration: string; - indices: string; - successfulShards: string; - failedShards: string; - totalShards: string; -} - export interface SnapshotDetails { + repository: string; snapshot: string; uuid: string; versionId: number; diff --git a/x-pack/plugins/snapshot_restore/public/app/app.tsx b/x-pack/plugins/snapshot_restore/public/app/app.tsx index b18f446e65f4f..507b8716fe27a 100644 --- a/x-pack/plugins/snapshot_restore/public/app/app.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/app.tsx @@ -17,7 +17,11 @@ export const App = () => { - + ); diff --git a/x-pack/plugins/snapshot_restore/public/app/components/repository_form/repository_form.tsx b/x-pack/plugins/snapshot_restore/public/app/components/repository_form/repository_form.tsx index 83041356666f9..d3477176109a2 100644 --- a/x-pack/plugins/snapshot_restore/public/app/components/repository_form/repository_form.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/components/repository_form/repository_form.tsx @@ -61,16 +61,8 @@ export const RepositoryForm: React.FunctionComponent = ({ error: repositoryTypesError, loading: repositoryTypesLoading, data: repositoryTypes, - setIsMounted, } = loadRepositoryTypes(); - // Set mounted to false when unmounting to avoid in-flight request setting state on unmounted component - useEffect(() => { - return () => { - setIsMounted(false); - }; - }, []); - // Repository state const [repository, setRepository] = useState({ ...originalRepository, diff --git a/x-pack/plugins/snapshot_restore/public/app/index.tsx b/x-pack/plugins/snapshot_restore/public/app/index.tsx index b94a1ca27e2b9..17e67fa5ca7e3 100644 --- a/x-pack/plugins/snapshot_restore/public/app/index.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/index.tsx @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import React, { createContext, useContext, useReducer } from 'react'; import { render } from 'react-dom'; import { HashRouter } from 'react-router-dom'; diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx index bd4ec07c96d91..48e1a45720035 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx @@ -100,7 +100,11 @@ export const SnapshotRestoreHome: React.FunctionComponent = ({ - + diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx index c1add77287ef5..e4332186384b9 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx @@ -6,22 +6,6 @@ import React, { Fragment } from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; -import { useAppDependencies } from '../../../../index'; -import { documentationLinksService } from '../../../../services/documentation'; -import { loadRepository } from '../../../../services/http'; -import { textService } from '../../../../services/text'; - -import { REPOSITORY_TYPES } from '../../../../../../common/constants'; -import { Repository } from '../../../../../../common/types'; -import { - RepositoryDeleteProvider, - RepositoryVerificationBadge, - SectionError, - SectionLoading, -} from '../../../../components'; -import { BASE_PATH } from '../../../../constants'; -import { TypeDetails } from './type_details'; - import { EuiButton, EuiButtonEmpty, @@ -39,6 +23,22 @@ import { import 'brace/theme/textmate'; +import { useAppDependencies } from '../../../../index'; +import { documentationLinksService } from '../../../../services/documentation'; +import { loadRepository } from '../../../../services/http'; +import { textService } from '../../../../services/text'; + +import { REPOSITORY_TYPES } from '../../../../../../common/constants'; +import { Repository } from '../../../../../../common/types'; +import { + RepositoryDeleteProvider, + RepositoryVerificationBadge, + SectionError, + SectionLoading, +} from '../../../../components'; +import { BASE_PATH } from '../../../../constants'; +import { TypeDetails } from './type_details'; + interface Props extends RouteComponentProps { repositoryName: Repository['name']; onClose: () => void; diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx index bc695b75c9aba..c0e8f7d9d860e 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx @@ -19,13 +19,13 @@ import { RepositoryTable } from './repository_table'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; interface MatchParams { - name?: Repository['name']; + repositoryName?: Repository['name']; } interface Props extends RouteComponentProps {} export const RepositoryList: React.FunctionComponent = ({ match: { - params: { name }, + params: { repositoryName: name }, }, history, }) => { diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index 50a31afd8456d..38959dd05e623 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -8,13 +8,30 @@ import React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; +// import { useAppDependencies } from '../../../../index'; +import { loadSnapshot } from '../../../../services/http'; interface Props extends RouteComponentProps { + repositoryName: string; snapshotId: string; onClose: () => void; } -const SnapshotDetailsUi: React.FunctionComponent = ({ snapshotId, onClose, history }) => { +const SnapshotDetailsUi: React.FunctionComponent = ({ + repositoryName, + snapshotId, + onClose, +}) => { + // const { + // core: { i18n }, + // } = useAppDependencies(); + + const { + // error, + // loading, + data: snapshotDetails, + } = loadSnapshot(repositoryName, snapshotId); + return ( = ({ snapshotId, onClose

- {snapshotId} + {repositoryName} > {snapshotId}

- Details go here + + {snapshotDetails && snapshotDetails.uid} +
); }; diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx index dcc8deb0a75ef..cb2b6f5a10455 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useEffect, useState } from 'react'; +import React, { Fragment } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { SectionError, SectionLoading } from '../../../components'; @@ -19,13 +19,15 @@ import { SnapshotTable } from './snapshot_table'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; interface MatchParams { + repositoryName?: string; snapshotId?: string; } + interface Props extends RouteComponentProps {} export const SnapshotList: React.FunctionComponent = ({ match: { - params: { snapshotId }, + params: { repositoryName: pathRepositoryName, snapshotId: pathSnapshotId }, }, history, }) => { @@ -42,27 +44,18 @@ export const SnapshotList: React.FunctionComponent = ({ request: reload, } = loadSnapshots(); - const [currentSnapshot, setCurrentSnapshot] = useState(undefined); - - const openSnapshotDetails = (id: string) => { - setCurrentSnapshot(id); - history.push(`${BASE_PATH}/snapshots/${id}`); + const openSnapshotDetails = (repositoryName: string, snapshotId: string) => { + history.push(`${BASE_PATH}/snapshots/${repositoryName}/${snapshotId}`); }; const closeSnapshotDetails = () => { - setCurrentSnapshot(undefined); history.push(`${BASE_PATH}/snapshots`); }; - useEffect( - () => { - setCurrentSnapshot(snapshotId); - }, - [snapshotId] - ); + let content; if (loading) { - return ( + content = ( = ({ /> ); - } - - if (error) { - return ( + } else if (error) { + content = ( = ({ error={error} /> ); - } - - if (snapshots && snapshots.length === 0) { - return ( + } else if (snapshots && snapshots.length === 0) { + content = ( = ({ } /> ); - } - - return ( - - {currentSnapshot ? ( - - ) : null} + } else { + content = ( + ); + } + + return ( + + {pathRepositoryName && pathSnapshotId ? ( + + ) : null} + {content} ); }; diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx index a6c824d3882f7..dd2afc28238c3 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx @@ -8,15 +8,15 @@ import moment from 'moment'; import React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; -import { Snapshot } from '../../../../../../common/types'; +import { SnapshotDetails } from '../../../../../../common/types'; import { useAppDependencies } from '../../../../index'; const DATE_FORMAT = 'MMMM Do, YYYY h:mm:ss A'; interface Props extends RouteComponentProps { - snapshots: Snapshot[]; + snapshots: SnapshotDetails[]; reload: () => Promise; - openSnapshotDetails: (id: string) => void; + openSnapshotDetails: (repositoryName: string, snapshotId: string) => void; } const SnapshotTableUi: React.FunctionComponent = ({ @@ -33,70 +33,84 @@ const SnapshotTableUi: React.FunctionComponent = ({ const columns = [ { - field: 'id', - name: translate('xpack.snapshotRestore.snapshotList.table.idColumnTitle', { - defaultMessage: 'ID', + field: 'repository', + name: translate('xpack.snapshotRestore.snapshotList.table.repositoryColumnTitle', { + defaultMessage: 'Repository', }), truncateText: true, sortable: true, - render: (id: string, snapshot: Snapshot) => ( - openSnapshotDetails(id)}>{id} + // We deliberately don't link to the repository from here because the API request for populating + // this table takes so long, and navigating away by accident is a really poor UX. + render: (repository: string) => repository, + }, + { + field: 'snapshot', + name: translate('xpack.snapshotRestore.snapshotList.table.snapshotColumnTitle', { + defaultMessage: 'Snapshot', + }), + truncateText: true, + sortable: true, + render: (snapshotId: string, snapshot: SnapshotDetails) => ( + openSnapshotDetails(snapshot.repository, snapshotId)}> + {snapshotId} + ), }, { - field: 'summary.startEpoch', + field: 'startTimeInMillis', name: translate('xpack.snapshotRestore.snapshotList.table.startTimeColumnTitle', { defaultMessage: 'Date created', }), truncateText: true, sortable: true, - render: (startEpoch: string) => moment.unix(Number(startEpoch)).format(DATE_FORMAT), + render: (startTimeInMillis: number) => + moment.unix(Number(startTimeInMillis)).format(DATE_FORMAT), }, { - field: 'summary.duration', + field: 'durationInMillis', name: translate('xpack.snapshotRestore.snapshotList.table.durationColumnTitle', { defaultMessage: 'Duration', }), truncateText: true, sortable: true, - width: '120px', - render: (duration: string) => duration, + width: '100px', + render: (durationInMillis: number) => Math.round(durationInMillis / 1000), }, { - field: 'summary.indices', + field: 'indices', name: translate('xpack.snapshotRestore.snapshotList.table.indicesColumnTitle', { defaultMessage: 'Indices', }), truncateText: true, sortable: true, - width: '120px', - render: (indices: string) => indices, + width: '100px', + render: (indices: string[]) => indices.length, }, { - field: 'summary.totalShards', + field: 'shards.total', name: translate('xpack.snapshotRestore.snapshotList.table.shardsColumnTitle', { defaultMessage: 'Shards', }), truncateText: true, sortable: true, - width: '120px', - render: (totalShards: string) => totalShards, + width: '100px', + render: (totalShards: number) => totalShards, }, { - field: 'summary.failedShards', + field: 'shards.failed', name: translate('xpack.snapshotRestore.snapshotList.table.failedShardsColumnTitle', { defaultMessage: 'Failed shards', }), truncateText: true, sortable: true, - width: '120px', - render: (failedShards: string) => failedShards, + width: '100px', + render: (failedShards: number) => failedShards, }, ]; const sorting = { sort: { - field: 'id', + field: 'repository', direction: 'asc', }, }; diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts index 2d13e3e8c5eed..4b3623334a9bc 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts @@ -14,9 +14,13 @@ export const loadSnapshots = () => { }); }; -export const loadSnapshot = (id: string) => { +export const loadSnapshot = (repositoryName: string, snapshotId: string) => { return useRequest({ - path: httpService.addBasePath(`${API_BASE_PATH}snapshots/${encodeURIComponent(id)}`), + path: httpService.addBasePath( + `${API_BASE_PATH}snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent( + snapshotId + )}` + ), method: 'get', }); }; diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts index c1c85dbb7a342..3d933cc000e37 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts @@ -72,13 +72,21 @@ export const useRequest = ({ path, method, body, interval }: UseRequest) => { useEffect( () => { + function cleanup() { + isMounted.current = false; + } + request(); + if (interval) { const intervalRequest = setInterval(request, interval); return () => { + cleanup(); clearInterval(intervalRequest); }; } + + return cleanup; }, [path] ); @@ -88,8 +96,5 @@ export const useRequest = ({ path, method, body, interval }: UseRequest) => { loading, data, request, - setIsMounted: (status: boolean) => { - isMounted.current = status; - }, }; }; diff --git a/x-pack/plugins/snapshot_restore/server/lib/index.ts b/x-pack/plugins/snapshot_restore/server/lib/index.ts index 27673530f4b9f..0de34961db2f6 100644 --- a/x-pack/plugins/snapshot_restore/server/lib/index.ts +++ b/x-pack/plugins/snapshot_restore/server/lib/index.ts @@ -5,4 +5,4 @@ */ export { booleanizeSettings } from './booleanize_settings'; -export { deserializeSnapshotSummary, deserializeSnapshotDetails } from './snapshot_serialization'; +export { deserializeSnapshotDetails } from './snapshot_serialization'; diff --git a/x-pack/plugins/snapshot_restore/server/lib/snapshot_serialization.ts b/x-pack/plugins/snapshot_restore/server/lib/snapshot_serialization.ts index d7ee0be1dd00f..422d292328cc5 100644 --- a/x-pack/plugins/snapshot_restore/server/lib/snapshot_serialization.ts +++ b/x-pack/plugins/snapshot_restore/server/lib/snapshot_serialization.ts @@ -4,42 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SnapshotDetails, SnapshotSummary } from '../../common/types'; -import { SnapshotDetailsEs, SnapshotSummaryEs } from '../types'; +import { SnapshotDetails } from '../../common/types'; +import { SnapshotDetailsEs } from '../types'; -export function deserializeSnapshotSummary(snapshotSummaryEs: SnapshotSummaryEs): SnapshotSummary { - if (!snapshotSummaryEs || typeof snapshotSummaryEs !== 'object') { - throw new Error('Unable to deserialize snapshot summary'); - } - - const { - status, - start_epoch: startEpoch, - start_time: startTime, - end_epoch: endEpoch, - end_time: endTime, - duration, - indices, - successful_shards: successfulShards, - failed_shards: failedShards, - total_shards: totalShards, - } = snapshotSummaryEs; - - return { - status, - startEpoch, - startTime, - endEpoch, - endTime, - duration, - indices, - successfulShards, - failedShards, - totalShards, - }; -} - -export function deserializeSnapshotDetails(snapshotDetailsEs: SnapshotDetailsEs): SnapshotDetails { +export function deserializeSnapshotDetails( + repository: string, + snapshotDetailsEs: SnapshotDetailsEs +): SnapshotDetails { if (!snapshotDetailsEs || typeof snapshotDetailsEs !== 'object') { throw new Error('Unable to deserialize snapshot details'); } @@ -62,6 +33,7 @@ export function deserializeSnapshotDetails(snapshotDetailsEs: SnapshotDetailsEs) } = snapshotDetailsEs; return { + repository, snapshot, uuid, versionId, diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts index 3cea3e57ad60d..0ae7b502cd698 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts @@ -19,29 +19,33 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { barRepository: {}, }; - const mockCatSnapshotsFooResponse = Promise.resolve([ - { - id: 'snapshot1', - }, - ]); - - const mockCatSnapshotsBarResponse = Promise.resolve([ - { - id: 'snapshot2', - }, - ]); + const mockGetSnapshotsFooResponse = Promise.resolve({ + snapshots: [ + { + snapshot: 'snapshot1', + }, + ], + }); + + const mockGetSnapshotsBarResponse = Promise.resolve({ + snapshots: [ + { + snapshot: 'snapshot2', + }, + ], + }); const callWithRequest = jest .fn() .mockReturnValueOnce(mockSnapshotGetRepositoryEsResponse) - .mockReturnValueOnce(mockCatSnapshotsFooResponse) - .mockReturnValueOnce(mockCatSnapshotsBarResponse); + .mockReturnValueOnce(mockGetSnapshotsFooResponse) + .mockReturnValueOnce(mockGetSnapshotsBarResponse); const expectedResponse = { errors: [], snapshots: [ - { repositories: ['fooRepository'], id: 'snapshot1', summary: {} }, - { repositories: ['barRepository'], id: 'snapshot2', summary: {} }, + { repository: 'fooRepository', snapshot: 'snapshot1' }, + { repository: 'barRepository', snapshot: 'snapshot2' }, ], }; @@ -85,7 +89,10 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { snapshots: [{ snapshot }], }; const callWithRequest = jest.fn().mockReturnValue(mockSnapshotGetEsResponse); - const expectedResponse = { snapshot }; + const expectedResponse = { + snapshot, + repository, + }; const response = await getOneHandler(mockOneRequest, callWithRequest, mockResponseToolkit); expect(response).toEqual(expectedResponse); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts index 58b7d745f80a7..7d6427f4c8d8d 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { Router, RouterRouteHandler } from '../../../../../server/lib/create_router'; -import { Snapshot, SnapshotDetails } from '../../../common/types'; -import { deserializeSnapshotDetails, deserializeSnapshotSummary } from '../../lib'; -import { SnapshotDetailsEs, SnapshotSummaryEs } from '../../types'; +import { SnapshotDetails } from '../../../common/types'; +import { deserializeSnapshotDetails } from '../../lib'; +import { SnapshotDetailsEs } from '../../types'; export function registerSnapshotsRoutes(router: Router) { router.get('snapshots', getAllHandler); @@ -17,7 +17,7 @@ export const getAllHandler: RouterRouteHandler = async ( req, callWithRequest ): Promise<{ - snapshots: Snapshot[]; + snapshots: SnapshotDetails[]; errors: any[]; }> => { const repositoriesByName = await callWithRequest('snapshot.getRepository', { @@ -30,59 +30,33 @@ export const getAllHandler: RouterRouteHandler = async ( return { snapshots: [], errors: [] }; } + const snapshots: SnapshotDetails[] = []; const errors: any = []; - const fetchSnapshotsForRepository = async (repositoryName: string) => { + const fetchSnapshotsForRepository = async (repository: string) => { try { - const snapshots = await callWithRequest('cat.snapshots', { - repository: repositoryName, - format: 'json', + const { + snapshots: fetchedSnapshots, + }: { snapshots: SnapshotDetailsEs[] } = await callWithRequest('snapshot.get', { + repository, + snapshot: '_all', }); // Decorate each snapshot with the repository with which it's associated. - return snapshots.map((snapshot: any) => ({ - repository: repositoryName, - ...snapshot, - })); + fetchedSnapshots.forEach((snapshot: SnapshotDetailsEs) => { + snapshots.push(deserializeSnapshotDetails(repository, snapshot)); + }); } catch (error) { // These errors are commonly due to a misconfiguration in the repository or plugin errors, // which can result in a variety of 400, 404, and 500 errors. errors.push(error); - return null; } }; - const repositoriesSnapshots = await Promise.all(repositoryNames.map(fetchSnapshotsForRepository)); - - // Multiple repositories can have identical configurations. This means that the same snapshot - // may be listed as belonging to multiple repositories. A map lets us dedupe the snapshots and - // aggregate the repositories that are associated with each one. - const idToSnapshotMap: Record = repositoriesSnapshots - .filter(Boolean) - .reduce((idToSnapshot, snapshots: SnapshotSummaryEs[]) => { - // create an object to store each snapshot and the - // repositories that are associated with it. - snapshots.forEach(summary => { - const { id, repository } = summary; - - if (!idToSnapshot[id]) { - // Instantiate the snapshot object - idToSnapshot[id] = { - id, - // The cat API only returns a subset of the details returned by the get snapshot API. - summary: deserializeSnapshotSummary(summary), - repositories: [], - }; - } - - idToSnapshot[id].repositories.push(repository); - }); - - return idToSnapshot; - }, {}); + await Promise.all(repositoryNames.map(fetchSnapshotsForRepository)); return { - snapshots: Object.values(idToSnapshotMap), + snapshots, errors, }; }; @@ -98,5 +72,5 @@ export const getOneHandler: RouterRouteHandler = async ( }); // If the snapshot is missing the endpoint will return a 404, so we'll never get to this point. - return deserializeSnapshotDetails(snapshots[0]); + return deserializeSnapshotDetails(repository, snapshots[0]); }; diff --git a/x-pack/plugins/snapshot_restore/server/types/snapshot.ts b/x-pack/plugins/snapshot_restore/server/types/snapshot.ts index 3231eec099330..280fcc56ba559 100644 --- a/x-pack/plugins/snapshot_restore/server/types/snapshot.ts +++ b/x-pack/plugins/snapshot_restore/server/types/snapshot.ts @@ -4,25 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface SnapshotSummaryEs { - id: string; - repository: string; - status: string; - /** This and other numerical values are typed as strings. e.g. '1554501400'. */ - start_epoch: string; - /** e.g. '21:56:40' */ - start_time: string; - end_epoch: string; - /** e.g. '21:56:45' */ - end_time: string; - /** Includes unit, e.g. '4.7s' */ - duration: string; - indices: string; - successful_shards: string; - failed_shards: string; - total_shards: string; -} - export interface SnapshotDetailsEs { snapshot: string; uuid: string; From 4a4d5c1f20711849b2b7c74044020534af6690cd Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 10 Apr 2019 16:16:55 -0700 Subject: [PATCH 03/14] Order snapshots from newest to oldest. --- .../snapshot_table/snapshot_table.tsx | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx index dd2afc28238c3..5233c197a1d7b 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx @@ -32,17 +32,6 @@ const SnapshotTableUi: React.FunctionComponent = ({ } = useAppDependencies(); const columns = [ - { - field: 'repository', - name: translate('xpack.snapshotRestore.snapshotList.table.repositoryColumnTitle', { - defaultMessage: 'Repository', - }), - truncateText: true, - sortable: true, - // We deliberately don't link to the repository from here because the API request for populating - // this table takes so long, and navigating away by accident is a really poor UX. - render: (repository: string) => repository, - }, { field: 'snapshot', name: translate('xpack.snapshotRestore.snapshotList.table.snapshotColumnTitle', { @@ -56,6 +45,17 @@ const SnapshotTableUi: React.FunctionComponent = ({ ), }, + { + field: 'repository', + name: translate('xpack.snapshotRestore.snapshotList.table.repositoryColumnTitle', { + defaultMessage: 'Repository', + }), + truncateText: true, + sortable: true, + // We deliberately don't link to the repository from here because the API request for populating + // this table takes so long, and navigating away by accident is a really poor UX. + render: (repository: string) => repository, + }, { field: 'startTimeInMillis', name: translate('xpack.snapshotRestore.snapshotList.table.startTimeColumnTitle', { @@ -108,10 +108,11 @@ const SnapshotTableUi: React.FunctionComponent = ({ }, ]; + // By default, we'll display the most recent snapshots at the top of the table. const sorting = { sort: { - field: 'repository', - direction: 'asc', + field: 'startTimeInMillis', + direction: 'desc', }, }; From e5ba296d5c938b3bcee7ba4023e2d8578776b8f4 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 10 Apr 2019 18:44:06 -0700 Subject: [PATCH 04/14] Add content to details panel. Add initialValue config option to useRequest. --- .../snapshot_restore/common/types/snapshot.ts | 2 +- .../snapshot_details/snapshot_details.tsx | 243 ++++++++++++++++-- .../app/services/http/repository_requests.ts | 2 + .../app/services/http/snapshot_requests.ts | 1 + .../public/app/services/http/use_request.ts | 6 +- .../server/lib/snapshot_serialization.ts | 2 +- 6 files changed, 232 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/common/types/snapshot.ts b/x-pack/plugins/snapshot_restore/common/types/snapshot.ts index 449209e25f1ee..7b8b3798f347c 100644 --- a/x-pack/plugins/snapshot_restore/common/types/snapshot.ts +++ b/x-pack/plugins/snapshot_restore/common/types/snapshot.ts @@ -11,7 +11,7 @@ export interface SnapshotDetails { versionId: number; version: string; indices: string[]; - includeGlobalState: boolean; + includeGlobalState: number; state: string; /** e.g. '2019-04-05T21:56:40.438Z' */ startTime: string; diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index 38959dd05e623..2b332a7db61eb 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -4,11 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + EuiDescriptionList, + EuiDescriptionListDescription, + EuiDescriptionListTitle, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiText, + EuiTextColor, + EuiTitle, +} from '@elastic/eui'; import React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; - -import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; -// import { useAppDependencies } from '../../../../index'; +import { useAppDependencies } from '../../../../index'; import { loadSnapshot } from '../../../../services/http'; interface Props extends RouteComponentProps { @@ -22,15 +33,199 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ snapshotId, onClose, }) => { - // const { - // core: { i18n }, - // } = useAppDependencies(); - const { - // error, - // loading, - data: snapshotDetails, - } = loadSnapshot(repositoryName, snapshotId); + core: { + i18n: { FormattedMessage }, + }, + } = useAppDependencies(); + + const { error, loading, data: snapshotDetails } = loadSnapshot(repositoryName, snapshotId); + + const includeGlobalStateToHumanizedMap = { + 0: ( + + ), + 1: ( + + ), + }; + + let content; + + if (loading) { + // TODO + } else if (error) { + // TODO + } else { + const { + versionId, + version, + // By setting includeGlobalState to false it’s possible to prevent the cluster global state + // to be stored as part of the snapshot. + includeGlobalState, + indices, + state, + failures, + // TODO: startTimeInMillis, + // TODO: endTimeInMillis, + // TODO: durationInMillis, + uuid, + } = snapshotDetails; + + const indicesList = indices.length ? ( +
    + {indices.map(index => ( +
  • {index}
  • + ))} +
+ ) : ( + + + + ); + + const failuresList = failures.length ? ( +
    + {failures.map(failure => ( +
  • {failure}
  • + ))} +
+ ) : ( + + + + ); + + content = ( + + + + + + + + + {version} / {versionId} + + + + + + + + + + {uuid} + + + + + + + + + + + + {includeGlobalStateToHumanizedMap[includeGlobalState]} + + + + + + + + + + {state} + + + + + + + + + + + + {indicesList} + + + + + + + + + + {failuresList} + + + + + ); + } return ( = ({ maxWidth={400} > - -

- {repositoryName} > {snapshotId} -

-
+ + + +

+ {snapshotId} +

+
+
+ + + +

+ {repositoryName} +

+
+
+
- - {snapshotDetails && snapshotDetails.uid} - + {content}
); }; diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts index 9822d2211ae0b..7f051e457d390 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts @@ -12,6 +12,7 @@ export const loadRepositories = () => { return useRequest({ path: httpService.addBasePath(`${API_BASE_PATH}repositories`), method: 'get', + initialData: [], }); }; @@ -26,6 +27,7 @@ export const loadRepositoryTypes = () => { return useRequest({ path: httpService.addBasePath(`${API_BASE_PATH}repository_types`), method: 'get', + initialData: [], }); }; diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts index 4b3623334a9bc..2e38cc21562f7 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/snapshot_requests.ts @@ -11,6 +11,7 @@ export const loadSnapshots = () => { return useRequest({ path: httpService.addBasePath(`${API_BASE_PATH}snapshots`), method: 'get', + initialData: [], }); }; diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts index 3d933cc000e37..2cf258ca31ed1 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts @@ -43,10 +43,10 @@ interface UseRequest extends SendRequest { interval?: number; } -export const useRequest = ({ path, method, body, interval }: UseRequest) => { +export const useRequest = ({ path, method, body, interval, initialData }: UseRequest) => { const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [data, setData] = useState([]); + const [loading, setLoading] = useState(true); + const [data, setData] = useState(initialData); const isMounted = useRef(true); const request = async () => { diff --git a/x-pack/plugins/snapshot_restore/server/lib/snapshot_serialization.ts b/x-pack/plugins/snapshot_restore/server/lib/snapshot_serialization.ts index 422d292328cc5..c623d062400db 100644 --- a/x-pack/plugins/snapshot_restore/server/lib/snapshot_serialization.ts +++ b/x-pack/plugins/snapshot_restore/server/lib/snapshot_serialization.ts @@ -39,7 +39,7 @@ export function deserializeSnapshotDetails( versionId, version, indices, - includeGlobalState, + includeGlobalState: Boolean(includeGlobalState) ? 1 : 0, state, startTime, startTimeInMillis, From 8c7c0b3c3fc7f82b3801822f52aed50ed4753ee2 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 10 Apr 2019 19:13:08 -0700 Subject: [PATCH 05/14] Fix TS errors and API tests. --- .../snapshot_details/snapshot_details.tsx | 6 ++--- .../public/app/services/http/use_request.ts | 1 + .../server/routes/api/snapshots.test.ts | 23 +++++++++++++++++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index 2b332a7db61eb..404e790a9aa14 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -41,7 +41,7 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ const { error, loading, data: snapshotDetails } = loadSnapshot(repositoryName, snapshotId); - const includeGlobalStateToHumanizedMap = { + const includeGlobalStateToHumanizedMap: Record = { 0: ( = ({ const indicesList = indices.length ? (
    - {indices.map(index => ( + {indices.map((index: string) => (
  • {index}
  • ))}
@@ -98,7 +98,7 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ const failuresList = failures.length ? (
    - {failures.map(failure => ( + {failures.map((failure: any) => (
  • {failure}
  • ))}
diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts index 2cf258ca31ed1..bc8d8d84d2899 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts @@ -41,6 +41,7 @@ export const sendRequest = async ({ interface UseRequest extends SendRequest { interval?: number; + initialData?: any; } export const useRequest = ({ path, method, body, interval, initialData }: UseRequest) => { diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts index 0ae7b502cd698..c4a2bc6f86134 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.test.ts @@ -7,6 +7,24 @@ import { Request, ResponseToolkit } from 'hapi'; import { getAllHandler, getOneHandler } from './snapshots'; +const defaultSnapshot = { + repository: undefined, + snapshot: undefined, + uuid: undefined, + versionId: undefined, + version: undefined, + indices: undefined, + includeGlobalState: 0, + state: undefined, + startTime: undefined, + startTimeInMillis: undefined, + endTime: undefined, + endTimeInMillis: undefined, + durationInMillis: undefined, + failures: undefined, + shards: undefined, +}; + describe('[Snapshot and Restore API Routes] Snapshots', () => { const mockResponseToolkit = {} as ResponseToolkit; @@ -44,8 +62,8 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { const expectedResponse = { errors: [], snapshots: [ - { repository: 'fooRepository', snapshot: 'snapshot1' }, - { repository: 'barRepository', snapshot: 'snapshot2' }, + { ...defaultSnapshot, repository: 'fooRepository', snapshot: 'snapshot1' }, + { ...defaultSnapshot, repository: 'barRepository', snapshot: 'snapshot2' }, ], }; @@ -90,6 +108,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => { }; const callWithRequest = jest.fn().mockReturnValue(mockSnapshotGetEsResponse); const expectedResponse = { + ...defaultSnapshot, snapshot, repository, }; From f6263c95071633c0f28345a20da13ea8e3b654bf Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 10 Apr 2019 19:35:48 -0700 Subject: [PATCH 06/14] Add formatDate service to text module. Show time info in detail panel. --- .../snapshot_details/snapshot_details.tsx | 79 +++++++++++++++++-- .../snapshot_table/snapshot_table.tsx | 19 +++-- .../public/app/services/text/format_date.ts | 14 ++++ .../public/app/services/text/index.ts | 1 + 4 files changed, 101 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index 404e790a9aa14..1008d0e251b89 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -21,6 +21,7 @@ import React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; import { useAppDependencies } from '../../../../index'; import { loadSnapshot } from '../../../../services/http'; +import { formatDate } from '../../../../services/text'; interface Props extends RouteComponentProps { repositoryName: string; @@ -74,16 +75,20 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ indices, state, failures, - // TODO: startTimeInMillis, - // TODO: endTimeInMillis, - // TODO: durationInMillis, + startTimeInMillis, + endTimeInMillis, + durationInMillis, uuid, } = snapshotDetails; const indicesList = indices.length ? (
    {indices.map((index: string) => ( -
  • {index}
  • +
  • + + {index} + +
  • ))}
) : ( @@ -99,7 +104,11 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ const failuresList = failures.length ? (
    {failures.map((failure: any) => ( -
  • {failure}
  • +
  • + + {failure} + +
  • ))}
) : ( @@ -223,6 +232,66 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ + + + + + + + + + {formatDate(startTimeInMillis)} + + + + + + + + + + {formatDate(endTimeInMillis)} + + + + + + + + + + + + + + + ); } diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx index 5233c197a1d7b..8dd57382a7b1c 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx @@ -3,15 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiInMemoryTable, EuiLink } from '@elastic/eui'; -import moment from 'moment'; + import React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; +import { EuiButton, EuiInMemoryTable, EuiLink } from '@elastic/eui'; + import { SnapshotDetails } from '../../../../../../common/types'; import { useAppDependencies } from '../../../../index'; - -const DATE_FORMAT = 'MMMM Do, YYYY h:mm:ss A'; +import { formatDate } from '../../../../services/text'; interface Props extends RouteComponentProps { snapshots: SnapshotDetails[]; @@ -63,8 +63,7 @@ const SnapshotTableUi: React.FunctionComponent = ({ }), truncateText: true, sortable: true, - render: (startTimeInMillis: number) => - moment.unix(Number(startTimeInMillis)).format(DATE_FORMAT), + render: (startTimeInMillis: number) => formatDate(startTimeInMillis), }, { field: 'durationInMillis', @@ -74,7 +73,13 @@ const SnapshotTableUi: React.FunctionComponent = ({ truncateText: true, sortable: true, width: '100px', - render: (durationInMillis: number) => Math.round(durationInMillis / 1000), + render: (durationInMillis: number) => ( + + ), }, { field: 'indices', diff --git a/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts b/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts new file mode 100644 index 0000000000000..87aa03e44e5c1 --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { dateFormatAliases } from '@elastic/eui/lib/services/format'; +import moment from 'moment'; + +const DATE_FORMAT = 'MMMM Do, yyyy h:mm:ss A'; + +export function formatDate(epochMs) { + return moment(Number(epochMs)).format(dateFormatAliases.longDateTime); +} diff --git a/x-pack/plugins/snapshot_restore/public/app/services/text/index.ts b/x-pack/plugins/snapshot_restore/public/app/services/text/index.ts index 3bed86f69937a..f442bfbc0ab23 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/text/index.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/text/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ +export { formatDate } from './format_date'; export { textService } from './text'; From 564f59fcdb2a5787014f62964822999aaa3e3d24 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 10 Apr 2019 20:10:47 -0700 Subject: [PATCH 07/14] Fix TS errors. --- .../snapshot_restore/public/app/services/text/format_date.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts b/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts index 87aa03e44e5c1..f397d3603d584 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore import { dateFormatAliases } from '@elastic/eui/lib/services/format'; import moment from 'moment'; -const DATE_FORMAT = 'MMMM Do, yyyy h:mm:ss A'; - -export function formatDate(epochMs) { +export function formatDate(epochMs: number): string { return moment(Number(epochMs)).format(dateFormatAliases.longDateTime); } From 590684d7cd4e1bf7209cb6bcbce83a1d7221a1a3 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 10 Apr 2019 20:15:53 -0700 Subject: [PATCH 08/14] Fix regression introduced into repository requests by missing initialData. --- .../public/app/services/http/repository_requests.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts index 7f051e457d390..3538ef1c66336 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/repository_requests.ts @@ -20,6 +20,7 @@ export const loadRepository = (name: Repository['name']) => { return useRequest({ path: httpService.addBasePath(`${API_BASE_PATH}repositories/${encodeURIComponent(name)}`), method: 'get', + initialData: {}, }); }; From 14c3faaf141538431b80e455af161dddd0782c1e Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 10 Apr 2019 20:18:46 -0700 Subject: [PATCH 09/14] Add loading and missing states to detail panel. --- .../job_list/detail_panel/detail_panel.js | 2 +- .../repository_details/repository_details.tsx | 2 +- .../snapshot_details/snapshot_details.tsx | 42 ++++++++++++++++--- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js index 445b436c123d2..dd0e848f77bde 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js @@ -224,7 +224,7 @@ export class DetailPanelUi extends Component { diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx index e4332186384b9..7ac31a9916d17 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx @@ -77,7 +77,7 @@ const RepositoryDetailsUi: React.FunctionComponent = ({ ); diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index 1008d0e251b89..3b8e34e4dab01 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -19,6 +19,7 @@ import { } from '@elastic/eui'; import React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; +import { SectionError, SectionLoading } from '../../../../components'; import { useAppDependencies } from '../../../../index'; import { loadSnapshot } from '../../../../services/http'; import { formatDate } from '../../../../services/text'; @@ -36,7 +37,7 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ }) => { const { core: { - i18n: { FormattedMessage }, + i18n: { FormattedMessage, translate }, }, } = useAppDependencies(); @@ -62,15 +63,46 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ let content; if (loading) { - // TODO + content = ( + + + + ); } else if (error) { - // TODO + const notFound = error.status === 404; + const errorObject = notFound + ? { + data: { + error: translate('xpack.snapshotRestore.snapshotDetails.errorSnapshotNotFound', { + defaultMessage: `Either the snapshot '{snapshotId}' doesn't exist in the repository '{repositoryName}' or that repository doesn't exist.`, + values: { + snapshotId, + repositoryName, + }, + }), + }, + } + : error; + content = ( + + } + error={errorObject} + /> + ); } else { const { versionId, version, - // By setting includeGlobalState to false it’s possible to prevent the cluster global state - // to be stored as part of the snapshot. + // TODO: Add a tooltip explaining that: a false value means that the cluster global state + // is not stored as part of the snapshot. includeGlobalState, indices, state, From 549405cddbf4b3350e589a2a815472f863ba299b Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 10 Apr 2019 20:36:34 -0700 Subject: [PATCH 10/14] Fix regression in repository routing. --- .../snapshot_restore/public/app/sections/home/home.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx index 48e1a45720035..47ec02d1372d2 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx @@ -99,7 +99,11 @@ export const SnapshotRestoreHome: React.FunctionComponent = ({ - + Date: Wed, 10 Apr 2019 21:24:46 -0700 Subject: [PATCH 11/14] Revert change to rollups. --- .../crud_app/sections/job_list/detail_panel/detail_panel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js index dd0e848f77bde..445b436c123d2 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js @@ -224,7 +224,7 @@ export class DetailPanelUi extends Component { From 0a640efeab0318db0c2ec96e71be0021811e1061 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 11 Apr 2019 10:45:40 -0700 Subject: [PATCH 12/14] Cleanup. - Remove route param aliases. - Remove unnecessary tags. --- .../snapshot_details/snapshot_details.tsx | 24 ++++++++----------- .../home/snapshot_list/snapshot_list.tsx | 10 ++++---- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index 3b8e34e4dab01..3e0c3255c8a18 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -124,13 +124,11 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ ))} ) : ( - - - + ); const failuresList = failures.length ? ( @@ -144,13 +142,11 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ ))} ) : ( - - - + ); content = ( diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx index cb2b6f5a10455..b008100df0ca1 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx @@ -27,7 +27,7 @@ interface Props extends RouteComponentProps {} export const SnapshotList: React.FunctionComponent = ({ match: { - params: { repositoryName: pathRepositoryName, snapshotId: pathSnapshotId }, + params: { repositoryName, snapshotId }, }, history, }) => { @@ -44,8 +44,8 @@ export const SnapshotList: React.FunctionComponent = ({ request: reload, } = loadSnapshots(); - const openSnapshotDetails = (repositoryName: string, snapshotId: string) => { - history.push(`${BASE_PATH}/snapshots/${repositoryName}/${snapshotId}`); + const openSnapshotDetails = (repositoryNameToOpen: string, snapshotIdToOpen: string) => { + history.push(`${BASE_PATH}/snapshots/${repositoryNameToOpen}/${snapshotIdToOpen}`); }; const closeSnapshotDetails = () => { @@ -127,8 +127,8 @@ export const SnapshotList: React.FunctionComponent = ({ {pathRepositoryName && pathSnapshotId ? ( ) : null} From dfb2a5beb64e002921d4e10956b22cd8552dfef1 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 11 Apr 2019 12:27:29 -0700 Subject: [PATCH 13/14] Fix bug introduced into useRequest by using a request-bound flag for tracking outdated requests. --- .../snapshot_details/snapshot_details.tsx | 75 ++++++++++--------- .../home/snapshot_list/snapshot_list.tsx | 2 +- .../app/services/http/snapshot_requests.ts | 10 +-- .../public/app/services/http/use_request.ts | 24 +++--- .../server/routes/api/snapshots.ts | 2 + 5 files changed, 59 insertions(+), 54 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index 3e0c3255c8a18..3be68f742dcff 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -41,7 +41,7 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ }, } = useAppDependencies(); - const { error, loading, data: snapshotDetails } = loadSnapshot(repositoryName, snapshotId); + const { error, data: snapshotDetails } = loadSnapshot(repositoryName, snapshotId); const includeGlobalStateToHumanizedMap: Record = { 0: ( @@ -62,42 +62,7 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ let content; - if (loading) { - content = ( - - - - ); - } else if (error) { - const notFound = error.status === 404; - const errorObject = notFound - ? { - data: { - error: translate('xpack.snapshotRestore.snapshotDetails.errorSnapshotNotFound', { - defaultMessage: `Either the snapshot '{snapshotId}' doesn't exist in the repository '{repositoryName}' or that repository doesn't exist.`, - values: { - snapshotId, - repositoryName, - }, - }), - }, - } - : error; - content = ( - - } - error={errorObject} - /> - ); - } else { + if (snapshotDetails) { const { versionId, version, @@ -322,6 +287,42 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ ); + } else if (error) { + const notFound = error.status === 404; + const errorObject = notFound + ? { + data: { + error: translate('xpack.snapshotRestore.snapshotDetails.errorSnapshotNotFound', { + defaultMessage: `Either the snapshot '{snapshotId}' doesn't exist in the repository '{repositoryName}' or that repository doesn't exist.`, + values: { + snapshotId, + repositoryName, + }, + }), + }, + } + : error; + content = ( + + } + error={errorObject} + /> + ); + } else { + // Assume the content is loading. + content = ( + + + + ); } return ( diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx index b008100df0ca1..238a6ec77f072 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx @@ -125,7 +125,7 @@ export const SnapshotList: React.FunctionComponent = ({ return ( - {pathRepositoryName && pathSnapshotId ? ( + {repositoryName && snapshotId ? ( { - return useRequest({ +export const loadSnapshots = () => + useRequest({ path: httpService.addBasePath(`${API_BASE_PATH}snapshots`), method: 'get', initialData: [], }); -}; -export const loadSnapshot = (repositoryName: string, snapshotId: string) => { - return useRequest({ +export const loadSnapshot = (repositoryName: string, snapshotId: string) => + useRequest({ path: httpService.addBasePath( `${API_BASE_PATH}snapshots/${encodeURIComponent(repositoryName)}/${encodeURIComponent( snapshotId @@ -24,4 +23,3 @@ export const loadSnapshot = (repositoryName: string, snapshotId: string) => { ), method: 'get', }); -}; diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts index bc8d8d84d2899..098e9cccefa09 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { httpService } from './index'; interface SendRequest { @@ -46,9 +46,11 @@ interface UseRequest extends SendRequest { export const useRequest = ({ path, method, body, interval, initialData }: UseRequest) => { const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [data, setData] = useState(initialData); - const isMounted = useRef(true); + + // Tied to every render and bound to each request. + let isOutdatedRequest = false; const request = async () => { setError(null); @@ -58,11 +60,12 @@ export const useRequest = ({ path, method, body, interval, initialData }: UseReq method, body, }); - // Avoid setting state for components that unmounted before request completed - if (!isMounted.current) { + + // Don't update state if an outdated request has resolved. + if (isOutdatedRequest) { return; } - // Set state for components that are still mounted + if (responseError) { setError(responseError); } else { @@ -73,8 +76,8 @@ export const useRequest = ({ path, method, body, interval, initialData }: UseReq useEffect( () => { - function cleanup() { - isMounted.current = false; + function cancelOutdatedRequest() { + isOutdatedRequest = true; } request(); @@ -82,12 +85,13 @@ export const useRequest = ({ path, method, body, interval, initialData }: UseReq if (interval) { const intervalRequest = setInterval(request, interval); return () => { - cleanup(); + cancelOutdatedRequest(); clearInterval(intervalRequest); }; } - return cleanup; + // Called when a new render will trigger this effect. + return cancelOutdatedRequest; }, [path] ); diff --git a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts index 7d6427f4c8d8d..6cac8134d32dd 100644 --- a/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -35,11 +35,13 @@ export const getAllHandler: RouterRouteHandler = async ( const fetchSnapshotsForRepository = async (repository: string) => { try { + // If any of these repositories 504 they will cost the request significant time. const { snapshots: fetchedSnapshots, }: { snapshots: SnapshotDetailsEs[] } = await callWithRequest('snapshot.get', { repository, snapshot: '_all', + ignore_unavailable: true, // Allow request to succeed even if some snapshots are unavailable. }); // Decorate each snapshot with the repository with which it's associated. From 532d4ce00db3b1ebfbb65b8cf5b1103deb35b95e Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 11 Apr 2019 16:27:01 -0700 Subject: [PATCH 14/14] Address feedback. - Fix bug in useRequest which prevented old data from being cleared when subsequent requests returned errors. - Fix formatting bug for failures in detail panel. - Fix routing bug when repository name contains slashes. - Fix/remove test subject selectors. - Add EUI typing. --- .../snapshot_restore/public/app/sections/home/home.tsx | 6 +++++- .../snapshot_list/snapshot_details/snapshot_details.tsx | 6 ++---- .../home/snapshot_list/snapshot_table/snapshot_table.tsx | 2 -- .../public/app/services/http/use_request.ts | 9 ++++----- .../public/app/services/text/format_date.ts | 1 - x-pack/typings/@elastic/eui/index.d.ts | 4 ++++ 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx index 47ec02d1372d2..f0a97a6a9e9b0 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/home.tsx @@ -104,9 +104,13 @@ export const SnapshotRestoreHome: React.FunctionComponent = ({ path={`${BASE_PATH}/repositories/:repositoryName*`} component={RepositoryList} /> + {/* We have two separate SnapshotList routes because repository names could have slashes in + * them. This would break a route with a path like snapshots/:repositoryName?/:snapshotId* + */} + diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx index 3be68f742dcff..2bdee5d519eba 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx @@ -47,14 +47,12 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ 0: ( ), 1: ( ), @@ -101,7 +99,7 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ {failures.map((failure: any) => (
  • - {failure} + {JSON.stringify(failure)}
  • ))} @@ -109,7 +107,7 @@ const SnapshotDetailsUi: React.FunctionComponent = ({ ) : ( ); diff --git a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx index 8dd57382a7b1c..e7fded3807260 100644 --- a/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx +++ b/x-pack/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx @@ -52,8 +52,6 @@ const SnapshotTableUi: React.FunctionComponent = ({ }), truncateText: true, sortable: true, - // We deliberately don't link to the repository from here because the API request for populating - // this table takes so long, and navigating away by accident is a really poor UX. render: (repository: string) => repository, }, { diff --git a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts index 098e9cccefa09..a90370d6c9b55 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/http/use_request.ts @@ -54,7 +54,9 @@ export const useRequest = ({ path, method, body, interval, initialData }: UseReq const request = async () => { setError(null); + setData(initialData); setLoading(true); + const { data: responseData, error: responseError } = await sendRequest({ path, method, @@ -66,11 +68,8 @@ export const useRequest = ({ path, method, body, interval, initialData }: UseReq return; } - if (responseError) { - setError(responseError); - } else { - setData(responseData); - } + setError(responseError); + setData(responseData); setLoading(false); }; diff --git a/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts b/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts index f397d3603d584..26b0f85beffb7 100644 --- a/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts +++ b/x-pack/plugins/snapshot_restore/public/app/services/text/format_date.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore import { dateFormatAliases } from '@elastic/eui/lib/services/format'; import moment from 'moment'; diff --git a/x-pack/typings/@elastic/eui/index.d.ts b/x-pack/typings/@elastic/eui/index.d.ts index 537e636ceb110..afdda6f842239 100644 --- a/x-pack/typings/@elastic/eui/index.d.ts +++ b/x-pack/typings/@elastic/eui/index.d.ts @@ -10,3 +10,7 @@ declare module '@elastic/eui' { export const EuiDescribedFormGroup: React.SFC; export const EuiCodeEditor: React.SFC; } + +declare module '@elastic/eui/lib/services/format' { + export const dateFormatAliases: any; +}