Allow User to Cleanup Repository from UI (#53047)
* 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>
This commit is contained in:
parent
51e51ca434
commit
10733b5415
|
@ -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');
|
||||
};
|
||||
|
@ -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'
|
||||
|
|
|
@ -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,
|
||||
|
@ -113,6 +123,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
|
|||
setLoadIndicesResponse,
|
||||
setAddPolicyResponse,
|
||||
setGetPolicyResponse,
|
||||
setCleanupRepositoryResponse,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -8,7 +8,6 @@ import {
|
|||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiCodeEditor,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
|
@ -19,6 +18,8 @@ import {
|
|||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
EuiCodeBlock,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import 'brace/theme/textmate';
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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 = () => {
|
||||
|
@ -231,6 +248,8 @@ export const RepositoryDetails: React.FunctionComponent<Props> = ({
|
|||
<TypeDetails repository={repository} />
|
||||
<EuiHorizontalRule />
|
||||
{renderVerification()}
|
||||
<EuiHorizontalRule />
|
||||
{renderCleanup()}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -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}>
|
||||
|
@ -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">
|
||||
|
|
|
@ -96,6 +96,7 @@ export const RepositoryTable: React.FunctionComponent<Props> = ({
|
|||
},
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
name: i18n.translate('xpack.snapshotRestore.repositoryList.table.actionsColumnTitle', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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';
|
||||
|
@ -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`),
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => {
|
||||
const ca = components.clientAction.factory;
|
||||
|
||||
Client.prototype.slm = components.clientAction.namespaceFactory();
|
||||
const slm = Client.prototype.slm.prototype;
|
||||
Client.prototype.sr = components.clientAction.namespaceFactory();
|
||||
const sr = Client.prototype.sr.prototype;
|
||||
|
||||
slm.policies = ca({
|
||||
sr.policies = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_slm/policy',
|
||||
|
@ -19,7 +19,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
|
|||
method: 'GET',
|
||||
});
|
||||
|
||||
slm.policy = ca({
|
||||
sr.policy = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_slm/policy/<%=name%>',
|
||||
|
@ -33,7 +33,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
|
|||
method: 'GET',
|
||||
});
|
||||
|
||||
slm.deletePolicy = ca({
|
||||
sr.deletePolicy = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_slm/policy/<%=name%>',
|
||||
|
@ -47,7 +47,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
|
|||
method: 'DELETE',
|
||||
});
|
||||
|
||||
slm.executePolicy = ca({
|
||||
sr.executePolicy = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_slm/policy/<%=name%>/_execute',
|
||||
|
@ -61,7 +61,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
|
|||
method: 'PUT',
|
||||
});
|
||||
|
||||
slm.updatePolicy = ca({
|
||||
sr.updatePolicy = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_slm/policy/<%=name%>',
|
||||
|
@ -75,7 +75,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
|
|||
method: 'PUT',
|
||||
});
|
||||
|
||||
slm.executeRetention = ca({
|
||||
sr.executeRetention = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_slm/_execute_retention',
|
||||
|
@ -83,4 +83,18 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
|
|||
],
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
sr.cleanupRepository = ca({
|
||||
urls: [
|
||||
{
|
||||
fmt: '/_snapshot/<%=name%>/_cleanup',
|
||||
req: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
method: 'POST',
|
||||
});
|
||||
};
|
|
@ -40,7 +40,7 @@ export const getAllHandler: RouterRouteHandler = async (
|
|||
// Get policies
|
||||
const policiesByName: {
|
||||
[key: string]: SlmPolicyEs;
|
||||
} = await callWithRequest('slm.policies', {
|
||||
} = await callWithRequest('sr.policies', {
|
||||
human: true,
|
||||
});
|
||||
|
||||
|
@ -62,7 +62,7 @@ export const getOneHandler: RouterRouteHandler = async (
|
|||
const { name } = req.params;
|
||||
const policiesByName: {
|
||||
[key: string]: SlmPolicyEs;
|
||||
} = await callWithRequest('slm.policy', {
|
||||
} = await callWithRequest('sr.policy', {
|
||||
name,
|
||||
human: true,
|
||||
});
|
||||
|
@ -82,7 +82,7 @@ export const getOneHandler: RouterRouteHandler = async (
|
|||
|
||||
export const executeHandler: RouterRouteHandler = async (req, callWithRequest) => {
|
||||
const { name } = req.params;
|
||||
const { snapshot_name: snapshotName } = await callWithRequest('slm.executePolicy', {
|
||||
const { snapshot_name: snapshotName } = await callWithRequest('sr.executePolicy', {
|
||||
name,
|
||||
});
|
||||
return { snapshotName };
|
||||
|
@ -98,7 +98,7 @@ export const deleteHandler: RouterRouteHandler = async (req, callWithRequest) =>
|
|||
|
||||
await Promise.all(
|
||||
policyNames.map(name => {
|
||||
return callWithRequest('slm.deletePolicy', { name })
|
||||
return callWithRequest('sr.deletePolicy', { name })
|
||||
.then(() => response.itemsDeleted.push(name))
|
||||
.catch(e =>
|
||||
response.errors.push({
|
||||
|
@ -122,7 +122,7 @@ export const createHandler: RouterRouteHandler = async (req, callWithRequest) =>
|
|||
|
||||
// Check that policy with the same name doesn't already exist
|
||||
try {
|
||||
const policyByName = await callWithRequest('slm.policy', { name });
|
||||
const policyByName = await callWithRequest('sr.policy', { name });
|
||||
if (policyByName[name]) {
|
||||
throw conflictError;
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ export const createHandler: RouterRouteHandler = async (req, callWithRequest) =>
|
|||
}
|
||||
|
||||
// Otherwise create new policy
|
||||
return await callWithRequest('slm.updatePolicy', {
|
||||
return await callWithRequest('sr.updatePolicy', {
|
||||
name,
|
||||
body: serializePolicy(policy),
|
||||
});
|
||||
|
@ -146,10 +146,10 @@ export const updateHandler: RouterRouteHandler = async (req, callWithRequest) =>
|
|||
|
||||
// Check that policy with the given name exists
|
||||
// If it doesn't exist, 404 will be thrown by ES and will be returned
|
||||
await callWithRequest('slm.policy', { name });
|
||||
await callWithRequest('sr.policy', { name });
|
||||
|
||||
// Otherwise update policy
|
||||
return await callWithRequest('slm.updatePolicy', {
|
||||
return await callWithRequest('sr.updatePolicy', {
|
||||
name,
|
||||
body: serializePolicy(policy),
|
||||
});
|
||||
|
@ -210,5 +210,5 @@ export const updateRetentionSettingsHandler: RouterRouteHandler = async (req, ca
|
|||
};
|
||||
|
||||
export const executeRetentionHandler: RouterRouteHandler = async (_req, callWithRequest) => {
|
||||
return await callWithRequest('slm.executeRetention');
|
||||
return await callWithRequest('sr.executeRetention');
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
RepositoryType,
|
||||
RepositoryVerification,
|
||||
SlmPolicyEs,
|
||||
RepositoryCleanup,
|
||||
} from '../../../common/types';
|
||||
|
||||
import { Plugins } from '../../shim';
|
||||
|
@ -34,6 +35,7 @@ export function registerRepositoriesRoutes(router: Router, plugins: Plugins) {
|
|||
router.get('repositories', getAllHandler);
|
||||
router.get('repositories/{name}', getOneHandler);
|
||||
router.get('repositories/{name}/verify', getVerificationHandler);
|
||||
router.post('repositories/{name}/cleanup', getCleanupHandler);
|
||||
router.put('repositories', createHandler);
|
||||
router.put('repositories/{name}', updateHandler);
|
||||
router.delete('repositories/{names}', deleteHandler);
|
||||
|
@ -74,7 +76,7 @@ export const getAllHandler: RouterRouteHandler = async (
|
|||
try {
|
||||
const policiesByName: {
|
||||
[key: string]: SlmPolicyEs;
|
||||
} = await callWithRequest('slm.policies', {
|
||||
} = await callWithRequest('sr.policies', {
|
||||
human: true,
|
||||
});
|
||||
const managedRepositoryPolicy = Object.entries(policiesByName)
|
||||
|
@ -172,6 +174,31 @@ export const getVerificationHandler: RouterRouteHandler = async (
|
|||
};
|
||||
};
|
||||
|
||||
export const getCleanupHandler: RouterRouteHandler = async (
|
||||
req,
|
||||
callWithRequest
|
||||
): Promise<{
|
||||
cleanup: RepositoryCleanup | {};
|
||||
}> => {
|
||||
const { name } = req.params;
|
||||
|
||||
const cleanupResults = await callWithRequest('sr.cleanupRepository', {
|
||||
name,
|
||||
}).catch(e => ({
|
||||
cleaned: false,
|
||||
error: e.response ? JSON.parse(e.response) : e,
|
||||
}));
|
||||
|
||||
return {
|
||||
cleanup: cleanupResults.error
|
||||
? cleanupResults
|
||||
: {
|
||||
cleaned: true,
|
||||
response: cleanupResults,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getTypesHandler: RouterRouteHandler = async () => {
|
||||
// In ECE/ESS, do not enable the default types
|
||||
const types: RepositoryType[] = isCloudEnabled ? [] : [...DEFAULT_REPOSITORY_TYPES];
|
||||
|
|
|
@ -38,7 +38,7 @@ export const getAllHandler: RouterRouteHandler = async (
|
|||
// Attempt to retrieve policies
|
||||
// This could fail if user doesn't have access to read SLM policies
|
||||
try {
|
||||
const policiesByName = await callWithRequest('slm.policies');
|
||||
const policiesByName = await callWithRequest('sr.policies');
|
||||
policies = Object.keys(policiesByName);
|
||||
} catch (e) {
|
||||
// Silently swallow error as policy names aren't required in UI
|
||||
|
|
|
@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { Legacy } from 'kibana';
|
||||
import { createRouter, Router } from '../../../server/lib/create_router';
|
||||
import { registerLicenseChecker } from '../../../server/lib/register_license_checker';
|
||||
import { elasticsearchJsPlugin } from './client/elasticsearch_slm';
|
||||
import { elasticsearchJsPlugin } from './client/elasticsearch_sr';
|
||||
import { CloudSetup } from '../../../../plugins/cloud/server';
|
||||
export interface Core {
|
||||
http: {
|
||||
|
|
|
@ -10965,7 +10965,6 @@
|
|||
"xpack.snapshotRestore.repositoryDetails.typeS3.serverSideEncryptionLabel": "サーバー側エコシステム",
|
||||
"xpack.snapshotRestore.repositoryDetails.typeS3.storageClassLabel": "ストレージクラス",
|
||||
"xpack.snapshotRestore.repositoryDetails.typeTitle": "レポジトリタイプ",
|
||||
"xpack.snapshotRestore.repositoryDetails.verificationDetails": "認証情報レポジトリ「{name}」",
|
||||
"xpack.snapshotRestore.repositoryDetails.verificationDetailsTitle": "詳細",
|
||||
"xpack.snapshotRestore.repositoryDetails.verificationTitle": "認証ステータス",
|
||||
"xpack.snapshotRestore.repositoryDetails.verifyButtonLabel": "レポジトリを検証",
|
||||
|
|
|
@ -11054,7 +11054,6 @@
|
|||
"xpack.snapshotRestore.repositoryDetails.typeS3.serverSideEncryptionLabel": "服务器端加密",
|
||||
"xpack.snapshotRestore.repositoryDetails.typeS3.storageClassLabel": "存储类",
|
||||
"xpack.snapshotRestore.repositoryDetails.typeTitle": "存储库类型",
|
||||
"xpack.snapshotRestore.repositoryDetails.verificationDetails": "验证详情存储库“{name}”",
|
||||
"xpack.snapshotRestore.repositoryDetails.verificationDetailsTitle": "详情",
|
||||
"xpack.snapshotRestore.repositoryDetails.verificationTitle": "验证状态",
|
||||
"xpack.snapshotRestore.repositoryDetails.verifyButtonLabel": "验证存储库",
|
||||
|
|
|
@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const pageObjects = getPageObjects(['common', 'snapshotRestore']);
|
||||
const log = getService('log');
|
||||
const es = getService('legacyEs');
|
||||
|
||||
describe('Home page', function() {
|
||||
this.tags('smoke');
|
||||
|
@ -26,5 +27,37 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
const repositoriesButton = await pageObjects.snapshotRestore.registerRepositoryButton();
|
||||
expect(await repositoriesButton.isDisplayed()).to.be(true);
|
||||
});
|
||||
|
||||
describe('Repositories Tab', async () => {
|
||||
before(async () => {
|
||||
await es.snapshot.createRepository({
|
||||
repository: 'my-repository',
|
||||
body: {
|
||||
type: 'fs',
|
||||
settings: {
|
||||
location: '/tmp/es-backups/',
|
||||
compress: true,
|
||||
},
|
||||
},
|
||||
verify: true,
|
||||
});
|
||||
await pageObjects.snapshotRestore.navToRepositories();
|
||||
});
|
||||
|
||||
it('cleanup repository', async () => {
|
||||
await pageObjects.snapshotRestore.viewRepositoryDetails('my-repository');
|
||||
await pageObjects.common.sleep(25000);
|
||||
const cleanupResponse = await pageObjects.snapshotRestore.performRepositoryCleanup();
|
||||
await pageObjects.common.sleep(25000);
|
||||
expect(cleanupResponse).to.contain('results');
|
||||
expect(cleanupResponse).to.contain('deleted_bytes');
|
||||
expect(cleanupResponse).to.contain('deleted_blobs');
|
||||
});
|
||||
after(async () => {
|
||||
await es.snapshot.deleteRepository({
|
||||
repository: 'my-repository',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -69,7 +69,7 @@ export default async function({ readConfigFile }) {
|
|||
esTestCluster: {
|
||||
license: 'trial',
|
||||
from: 'snapshot',
|
||||
serverArgs: [],
|
||||
serverArgs: ['path.repo=/tmp/'],
|
||||
},
|
||||
|
||||
kbnTestServer: {
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export function SnapshotRestorePageProvider({ getService }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
const retry = getService('retry');
|
||||
|
||||
return {
|
||||
async appTitleText() {
|
||||
|
@ -16,5 +16,50 @@ export function SnapshotRestorePageProvider({ getService }: FtrProviderContext)
|
|||
async registerRepositoryButton() {
|
||||
return await testSubjects.find('registerRepositoryButton');
|
||||
},
|
||||
async navToRepositories() {
|
||||
await testSubjects.click('repositories_tab');
|
||||
await retry.waitForWithTimeout(
|
||||
'Wait for register repository button to be on page',
|
||||
10000,
|
||||
async () => {
|
||||
return await testSubjects.isDisplayed('registerRepositoryButton');
|
||||
}
|
||||
);
|
||||
},
|
||||
async getRepoList() {
|
||||
const table = await testSubjects.find('repositoryTable');
|
||||
const rows = await table.findAllByCssSelector('[data-test-subj="row"]');
|
||||
return await Promise.all(
|
||||
rows.map(async row => {
|
||||
return {
|
||||
repoName: await (
|
||||
await row.findByCssSelector('[data-test-subj="Name_cell"]')
|
||||
).getVisibleText(),
|
||||
repoLink: await (
|
||||
await row.findByCssSelector('[data-test-subj="Name_cell"]')
|
||||
).findByCssSelector('a'),
|
||||
repoType: await (
|
||||
await row.findByCssSelector('[data-test-subj="Type_cell"]')
|
||||
).getVisibleText(),
|
||||
repoEdit: await row.findByCssSelector('[data-test-subj="editRepositoryButton"]'),
|
||||
repoDelete: await row.findByCssSelector('[data-test-subj="deleteRepositoryButton"]'),
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
async viewRepositoryDetails(name: string) {
|
||||
const repos = await this.getRepoList();
|
||||
if (repos.length === 1) {
|
||||
const repoToView = repos.filter(r => (r.repoName = name))[0];
|
||||
await repoToView.repoLink.click();
|
||||
}
|
||||
await retry.waitForWithTimeout(`Repo title should be ${name}`, 10000, async () => {
|
||||
return (await testSubjects.getVisibleText('title')) === name;
|
||||
});
|
||||
},
|
||||
async performRepositoryCleanup() {
|
||||
await testSubjects.click('cleanupRepositoryButton');
|
||||
return await testSubjects.getVisibleText('cleanupCodeBlock');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue