Skip to content

Commit

Permalink
Allow User to Cleanup Repository from UI (elastic#53047)
Browse files Browse the repository at this point in the history
* Added repository cleanup button. Added logic for spinner while loading, added new repository request, type and telemetry metric.

* Added additional bindings for server side to hit the cleanup endpoint.

* fix cleanup request

* Added data test subject to the code editors to differentiate them and fixed a broken inport of RepositoryCleanup.

* Added files for a component integration test. The tests are failing right now so we need to get those green. Added a functional test. Need to set up kbn-es to be able to set up a file repository before being able to run the functional tests.

* Added change to the way data-test-subjects were created for the repository list table so that columns can be individually identified. Added functional test to allow checking the details of repositories.

* Removed the jest tests for repository details until we get jest fixed.

* Fixed jest test to reflect updated test subjects.

* Made changes per feedback in PR comments.

* Fixed i10n issues using <FormattedMessage>. Removed reference to blueBird and used Promise.all(). Fixed all nits in PR comments.

* Added i10n fixes for header.

* Added i10n fixes for header.

* Added name parameter for i18n strings.

* Removed i18n string from JSON.stringify call since it's already a string.

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Alison Goryachev <alisonmllr20@gmail.com>
  • Loading branch information
3 people authored and thomasneirynck committed Jan 12, 2020
1 parent 6c1686d commit 54f0c65
Show file tree
Hide file tree
Showing 19 changed files with 295 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export const setup = async (): Promise<HomeTestBed> => {
const tabs = ['snapshots', 'repositories'];

testBed
.find('tab')
.find(`${tab}_tab`)
.at(tabs.indexOf(tab))
.simulate('click');
};
Expand Down Expand Up @@ -360,7 +360,10 @@ export type TestSubjects =
| 'state'
| 'state.title'
| 'state.value'
| 'tab'
| 'repositories_tab'
| 'snapshots_tab'
| 'policies_tab'
| 'restore_status_tab'
| 'tableHeaderCell_durationInMillis_3'
| 'tableHeaderCell_durationInMillis_3.tableHeaderSortButton'
| 'tableHeaderCell_indices_4'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};

const setCleanupRepositoryResponse = (response?: HttpResponse, error?: any) => {
const status = error ? error.status || 503 : 200;

server.respondWith('POST', `${API_BASE_PATH}repositories/:name/cleanup`, [
status,
{ 'Content-Type': 'application/json' },
JSON.stringify(response),
]);
};

const setGetPolicyResponse = (response?: HttpResponse) => {
server.respondWith('GET', `${API_BASE_PATH}policy/:name`, [
200,
Expand All @@ -113,6 +123,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
setLoadIndicesResponse,
setAddPolicyResponse,
setGetPolicyResponse,
setCleanupRepositoryResponse,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,15 @@ describe('<SnapshotRestoreHome />', () => {
test('should have 4 tabs', () => {
const { find } = testBed;

expect(find('tab').length).toBe(4);
expect(find('tab').map(t => t.text())).toEqual([
const tabs = [
find('snapshots_tab'),
find('repositories_tab'),
find('policies_tab'),
find('restore_status_tab'),
];

expect(tabs.length).toBe(4);
expect(tabs.map(t => t.text())).toEqual([
'Snapshots',
'Repositories',
'Policies',
Expand Down
12 changes: 12 additions & 0 deletions x-pack/legacy/plugins/snapshot_restore/common/types/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,15 @@ export interface InvalidRepositoryVerification {
}

export type RepositoryVerification = ValidRepositoryVerification | InvalidRepositoryVerification;

export interface SuccessfulRepositoryCleanup {
cleaned: true;
response: object;
}

export interface FailedRepositoryCleanup {
cleaned: false;
error: object;
}

export type RepositoryCleanup = FailedRepositoryCleanup | SuccessfulRepositoryCleanup;
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export const UIM_REPOSITORY_DELETE = 'repository_delete';
export const UIM_REPOSITORY_DELETE_MANY = 'repository_delete_many';
export const UIM_REPOSITORY_SHOW_DETAILS_CLICK = 'repository_show_details_click';
export const UIM_REPOSITORY_DETAIL_PANEL_VERIFY = 'repository_detail_panel_verify';
export const UIM_REPOSITORY_DETAIL_PANEL_CLEANUP = 'repository_detail_panel_cleanup';
export const UIM_SNAPSHOT_LIST_LOAD = 'snapshot_list_load';
export const UIM_SNAPSHOT_SHOW_DETAILS_CLICK = 'snapshot_show_details_click';
export const UIM_SNAPSHOT_DETAIL_PANEL_SUMMARY_TAB = 'snapshot_detail_panel_summary_tab';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export const SnapshotRestoreHome: React.FunctionComponent<RouteComponentProps<Ma
onClick={() => onSectionChange(tab.id)}
isSelected={tab.id === section}
key={tab.id}
data-test-subj="tab"
data-test-subj={tab.id.toLowerCase() + '_tab'}
>
{tab.name}
</EuiTab>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
EuiButton,
EuiButtonEmpty,
EuiCallOut,
EuiCodeEditor,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
Expand All @@ -19,6 +18,8 @@ import {
EuiLink,
EuiSpacer,
EuiTitle,
EuiCodeBlock,
EuiText,
} from '@elastic/eui';

import 'brace/theme/textmate';
Expand All @@ -28,12 +29,17 @@ import { documentationLinksService } from '../../../../services/documentation';
import {
useLoadRepository,
verifyRepository as verifyRepositoryRequest,
cleanupRepository as cleanupRepositoryRequest,
} from '../../../../services/http';
import { textService } from '../../../../services/text';
import { linkToSnapshots, linkToEditRepository } from '../../../../services/navigation';

import { REPOSITORY_TYPES } from '../../../../../../common/constants';
import { Repository, RepositoryVerification } from '../../../../../../common/types';
import {
Repository,
RepositoryVerification,
RepositoryCleanup,
} from '../../../../../../common/types';
import {
RepositoryDeleteProvider,
SectionError,
Expand Down Expand Up @@ -61,7 +67,9 @@ export const RepositoryDetails: React.FunctionComponent<Props> = ({
const { FormattedMessage } = i18n;
const { error, data: repositoryDetails } = useLoadRepository(repositoryName);
const [verification, setVerification] = useState<RepositoryVerification | undefined>(undefined);
const [cleanup, setCleanup] = useState<RepositoryCleanup | undefined>(undefined);
const [isLoadingVerification, setIsLoadingVerification] = useState<boolean>(false);
const [isLoadingCleanup, setIsLoadingCleanup] = useState<boolean>(false);

const verifyRepository = async () => {
setIsLoadingVerification(true);
Expand All @@ -70,11 +78,20 @@ export const RepositoryDetails: React.FunctionComponent<Props> = ({
setIsLoadingVerification(false);
};

// Reset verification state when repository name changes, either from adjust URL or clicking
const cleanupRepository = async () => {
setIsLoadingCleanup(true);
const { data } = await cleanupRepositoryRequest(repositoryName);
setCleanup(data.cleanup);
setIsLoadingCleanup(false);
};

// Reset verification state and cleanup when repository name changes, either from adjust URL or clicking
// into a different repository in table list.
useEffect(() => {
setVerification(undefined);
setIsLoadingVerification(false);
setCleanup(undefined);
setIsLoadingCleanup(false);
}, [repositoryName]);

const renderBody = () => {
Expand Down Expand Up @@ -231,6 +248,8 @@ export const RepositoryDetails: React.FunctionComponent<Props> = ({
<TypeDetails repository={repository} />
<EuiHorizontalRule />
{renderVerification()}
<EuiHorizontalRule />
{renderCleanup()}
</Fragment>
);
};
Expand Down Expand Up @@ -260,36 +279,13 @@ export const RepositoryDetails: React.FunctionComponent<Props> = ({
</EuiTitle>
<EuiSpacer size="s" />
{verification ? (
<EuiCodeEditor
mode="json"
theme="textmate"
width="100%"
isReadOnly
value={JSON.stringify(
<EuiCodeBlock language="json" inline={false} data-test-subj="verificationCodeBlock">
{JSON.stringify(
verification.valid ? verification.response : verification.error,
null,
2
)}
setOptions={{
showLineNumbers: false,
tabSize: 2,
maxLines: Infinity,
}}
editorProps={{
$blockScrolling: Infinity,
}}
showGutter={false}
minLines={6}
aria-label={
<FormattedMessage
id="xpack.snapshotRestore.repositoryDetails.verificationDetails"
defaultMessage="Verification details repository '{name}'"
values={{
name,
}}
/>
}
/>
</EuiCodeBlock>
) : null}
<EuiSpacer size="m" />
<EuiButton onClick={verifyRepository} color="primary" isLoading={isLoadingVerification}>
Expand Down Expand Up @@ -318,6 +314,78 @@ export const RepositoryDetails: React.FunctionComponent<Props> = ({
</Fragment>
);

const renderCleanup = () => (
<>
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.snapshotRestore.repositoryDetails.cleanupTitle"
defaultMessage="Repository cleanup"
/>
</h3>
</EuiTitle>
<EuiSpacer size="s" />
<EuiText size="s">
<p>
<FormattedMessage
id="xpack.snapshotRestore.repositoryDetails.cleanupRepositoryMessage"
defaultMessage="You can clean up a repository to delete any unreferenced data from a snapshot. This
may provide storage space savings. Note: If you regularly delete snapshots, this
functionality will likely not be as beneficial and should be used less frequently."
/>
</p>
</EuiText>
{cleanup ? (
<>
<EuiSpacer size="s" />
{cleanup.cleaned ? (
<div>
<EuiTitle size="xs">
<h4>
<FormattedMessage
id="xpack.snapshotRestore.repositoryDetails.cleanupDetailsTitle"
defaultMessage="Details"
/>
</h4>
</EuiTitle>
<EuiCodeBlock language="json" inline={false} data-test-subj="cleanupCodeBlock">
{JSON.stringify(cleanup.response, null, 2)}
</EuiCodeBlock>
</div>
) : (
<EuiCallOut
color="danger"
iconType="alert"
title={i18n.translate('xpack.snapshotRestore.repositoryDetails.cleanupErrorTitle', {
defaultMessage: 'Sorry, there was an error cleaning the repository.',
})}
>
<p>
{cleanup.error
? JSON.stringify(cleanup.error)
: i18n.translate('xpack.snapshotRestore.repositoryDetails.cleanupUnknownError', {
defaultMessage: '503: Unknown error',
})}
</p>
</EuiCallOut>
)}
</>
) : null}
<EuiSpacer size="m" />
<EuiButton
onClick={cleanupRepository}
color="primary"
isLoading={isLoadingCleanup}
data-test-subj="cleanupRepositoryButton"
>
<FormattedMessage
id="xpack.snapshotRestore.repositoryDetails.cleanupButtonLabel"
defaultMessage="Clean up repository"
/>
</EuiButton>
</>
);

const renderFooter = () => {
return (
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export const RepositoryTable: React.FunctionComponent<Props> = ({
},
},
{
field: 'actions',
name: i18n.translate('xpack.snapshotRestore.repositoryList.table.actionsColumnTitle', {
defaultMessage: 'Actions',
}),
Expand Down Expand Up @@ -302,8 +303,8 @@ export const RepositoryTable: React.FunctionComponent<Props> = ({
rowProps={() => ({
'data-test-subj': 'row',
})}
cellProps={() => ({
'data-test-subj': 'cell',
cellProps={(item, field) => ({
'data-test-subj': `${field.name}_cell`,
})}
data-test-subj="repositoryTable"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
UIM_REPOSITORY_DELETE,
UIM_REPOSITORY_DELETE_MANY,
UIM_REPOSITORY_DETAIL_PANEL_VERIFY,
UIM_REPOSITORY_DETAIL_PANEL_CLEANUP,
} from '../../constants';
import { uiMetricService } from '../ui_metric';
import { httpService } from './http';
Expand Down Expand Up @@ -44,6 +45,20 @@ export const verifyRepository = async (name: Repository['name']) => {
return result;
};

export const cleanupRepository = async (name: Repository['name']) => {
const result = await sendRequest({
path: httpService.addBasePath(
`${API_BASE_PATH}repositories/${encodeURIComponent(name)}/cleanup`
),
method: 'post',
body: undefined,
});

const { trackUiMetric } = uiMetricService;
trackUiMetric(UIM_REPOSITORY_DETAIL_PANEL_CLEANUP);
return result;
};

export const useLoadRepositoryTypes = () => {
return useRequest({
path: httpService.addBasePath(`${API_BASE_PATH}repository_types`),
Expand Down

0 comments on commit 54f0c65

Please sign in to comment.