Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete objects belonging to removed space #23640

Merged
merged 2 commits into from
Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SavedObjectsRepository #deleteByNamespace requires namespace to be a string 1`] = `"namespace is required, and must be a string"`;

exports[`SavedObjectsRepository #deleteByNamespace requires namespace to be defined 1`] = `"namespace is required, and must be a string"`;
34 changes: 33 additions & 1 deletion src/server/saved_objects/service/lib/repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import { omit } from 'lodash';
import { getRootType } from '../../../mappings';
import { getRootType, getRootPropertiesObjects } from '../../../mappings';
import { getSearchDsl } from './search_dsl';
import { includedFields } from './included_fields';
import { decorateEsError } from './decorate_es_error';
Expand Down Expand Up @@ -245,6 +245,38 @@ export class SavedObjectsRepository {
);
}

/**
* Deletes all objects from the provided namespace.
*
* @param {string} namespace
* @returns {promise} - { took, timed_out, total, deleted, batches, version_conflicts, noops, retries, failures }
*/
async deleteByNamespace(namespace) {

if (!namespace || typeof namespace !== 'string') {
throw new TypeError(`namespace is required, and must be a string`);
}

const allTypes = Object.keys(getRootPropertiesObjects(this._mappings));

const typesToDelete = allTypes.filter(type => !this._schema.isNamespaceAgnostic(type));

const esOptions = {
index: this._index,
ignore: [404],
refresh: 'wait_for',
body: {
conflicts: 'proceed',
...getSearchDsl(this._mappings, this._schema, {
namespace,
type: typesToDelete,
})
}
};

return await this._writeToCluster('deleteByQuery', esOptions);
}

/**
* @param {object} [options={}]
* @property {(string|Array<string>)} [options.type]
Expand Down
66 changes: 66 additions & 0 deletions src/server/saved_objects/service/lib/repository.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,21 @@ describe('SavedObjectsRepository', () => {
}
};

const deleteByQueryResults = {
took: 27,
timed_out: false,
total: 23,
deleted: 23,
batches: 1,
version_conflicts: 0,
noops: 0,
retries: { bulk: 0, search: 0 },
throttled_millis: 0,
requests_per_second: -1,
throttled_until_millis: 0,
failures: []
};

const mappings = {
doc: {
properties: {
Expand All @@ -171,6 +186,20 @@ describe('SavedObjectsRepository', () => {
type: 'keyword'
}
}
},
'dashboard': {
properties: {
otherField: {
type: 'keyword'
}
}
},
'globaltype': {
properties: {
yetAnotherField: {
type: 'keyword'
}
}
}
}
}
Expand Down Expand Up @@ -708,6 +737,43 @@ describe('SavedObjectsRepository', () => {
});
});

describe('#deleteByNamespace', () => {
it('requires namespace to be defined', async () => {
callAdminCluster.returns(deleteByQueryResults);
expect(savedObjectsRepository.deleteByNamespace()).rejects.toThrowErrorMatchingSnapshot();
sinon.assert.notCalled(callAdminCluster);
sinon.assert.notCalled(onBeforeWrite);
});

it('requires namespace to be a string', async () => {
callAdminCluster.returns(deleteByQueryResults);
expect(savedObjectsRepository.deleteByNamespace(['namespace-1', 'namespace-2'])).rejects.toThrowErrorMatchingSnapshot();
sinon.assert.notCalled(callAdminCluster);
sinon.assert.notCalled(onBeforeWrite);
});

it('constructs a deleteByQuery call using all types that are namespace aware', async () => {
callAdminCluster.returns(deleteByQueryResults);
const result = await savedObjectsRepository.deleteByNamespace('my-namespace');

expect(result).toEqual(deleteByQueryResults);
sinon.assert.calledOnce(callAdminCluster);
sinon.assert.calledOnce(onBeforeWrite);

sinon.assert.calledWithExactly(getSearchDsl, mappings, schema, {
namespace: 'my-namespace',
type: ['index-pattern', 'dashboard']
});

sinon.assert.calledWithExactly(callAdminCluster, 'deleteByQuery', {
body: { conflicts: 'proceed' },
ignore: [404],
index: '.kibana-test',
refresh: 'wait_for'
});
});
});

describe('#find', () => {
it('waits until migrations are complete before proceeding', async () => {
migrator.awaitMigration = sinon.spy(async () => sinon.assert.notCalled(callAdminCluster));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,92 @@

exports[`ConfirmDeleteModal renders as expected 1`] = `
<EuiOverlayMask>
<EuiConfirmModal
buttonColor="danger"
cancelButtonText="Cancel"
confirmButtonText="Delete space"
defaultFocusedButton="cancel"
onCancel={[MockFunction]}
onConfirm={[Function]}
title="Delete space 'My Space'"
<EuiModal
className="euiModal--confirmation"
maxWidth={true}
onClose={[MockFunction]}
>
<p>
Deleting a space permanently removes the space and all of its contents. You can't undo this action.
</p>
<EuiFormRow
describedByIds={Array []}
error="Space names do not match."
fullWidth={false}
hasEmptyLabelSpace={false}
isInvalid={false}
label="Confirm space name"
>
<EuiFieldText
compressed={false}
fullWidth={false}
isLoading={false}
onChange={[Function]}
value=""
/>
</EuiFormRow>
<EuiCallOut
color="warning"
size="m"
>
<EuiModalHeader>
<EuiModalHeaderTitle
data-test-subj="confirmModalTitleText"
>
Delete space
'My Space'
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiText
data-test-subj="confirmModalBodyText"
grow={true}
>
You are about to delete your current space
<span>
(
<p>
Deleting a space permanently removes the space and
<strong>
My Space
all of its contents
</strong>
)
</span>
. You will be redirected to choose a different space if you continue.
. You can't undo this action.
</p>
<EuiFormRow
describedByIds={Array []}
error="Space names do not match."
fullWidth={false}
hasEmptyLabelSpace={false}
isInvalid={false}
label="Confirm space name"
>
<EuiFieldText
compressed={false}
disabled={false}
fullWidth={false}
isLoading={false}
onChange={[Function]}
value=""
/>
</EuiFormRow>
<EuiCallOut
color="warning"
size="m"
>
<EuiText
grow={true}
>
You are about to delete your current space
<span>
(
<strong>
My Space
</strong>
)
</span>
. You will be redirected to choose a different space if you continue.
</EuiText>
</EuiCallOut>
</EuiText>
</EuiCallOut>
</EuiConfirmModal>
</EuiModalBody>
<EuiModalFooter>
<EuiButtonEmpty
color="primary"
data-test-subj="confirmModalCancelButton"
iconSide="left"
isDisabled={false}
onClick={[MockFunction]}
type="button"
>
Cancel
</EuiButtonEmpty>
<EuiButton
color="danger"
data-test-subj="confirmModalConfirmButton"
fill={true}
iconSide="left"
isLoading={false}
onClick={[Function]}
type="button"
>
Delete space and all contents
</EuiButton>
</EuiModalFooter>
</EuiModal>
</EuiOverlayMask>
`;
Loading