mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Dashboard] Make Dashboard Saved Objects multiple-isolated (#115817)
* Make Dashboard SO multiple-isolated * Fix integration tests * Fix Saved Objects API Integration Tests * Fix more tests * Fix even more tests Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6d6cb5c836
commit
edc43c0ff2
12 changed files with 107 additions and 61 deletions
|
@ -19,7 +19,8 @@ export const createDashboardSavedObjectType = ({
|
|||
}): SavedObjectsType => ({
|
||||
name: 'dashboard',
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
namespaceType: 'multiple-isolated',
|
||||
convertToMultiNamespaceTypeVersion: '8.0.0',
|
||||
management: {
|
||||
icon: 'dashboardApp',
|
||||
defaultSearchField: 'title',
|
||||
|
|
|
@ -202,7 +202,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357',
|
||||
uiCapabilitiesPath: 'dashboard.show',
|
||||
},
|
||||
namespaceType: 'single',
|
||||
namespaceType: 'multiple-isolated',
|
||||
});
|
||||
}));
|
||||
|
||||
|
|
|
@ -301,7 +301,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357',
|
||||
uiCapabilitiesPath: 'dashboard.show',
|
||||
},
|
||||
namespaceType: 'single',
|
||||
namespaceType: 'multiple-isolated',
|
||||
hiddenType: false,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -134,6 +134,7 @@
|
|||
"uiStateJSON": "{}",
|
||||
"version": 1
|
||||
},
|
||||
"namespaces": ["default"],
|
||||
"type": "dashboard",
|
||||
"updated_at": "2017-09-21T18:57:40.826Z"
|
||||
},
|
||||
|
@ -205,7 +206,7 @@
|
|||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "space_1:dashboard:space1-dashboard-id",
|
||||
"id": "dashboard:space1-dashboard-id",
|
||||
"index": ".kibana",
|
||||
"source": {
|
||||
"dashboard": {
|
||||
|
@ -228,7 +229,7 @@
|
|||
"uiStateJSON": "{}",
|
||||
"version": 1
|
||||
},
|
||||
"namespace": "space_1",
|
||||
"namespaces": ["space_1"],
|
||||
"type": "dashboard",
|
||||
"updated_at": "2017-09-21T18:57:40.826Z"
|
||||
},
|
||||
|
@ -300,7 +301,7 @@
|
|||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "space_2:dashboard:space2-dashboard-id",
|
||||
"id": "dashboard:space2-dashboard-id",
|
||||
"index": ".kibana",
|
||||
"source": {
|
||||
"dashboard": {
|
||||
|
@ -323,7 +324,7 @@
|
|||
"uiStateJSON": "{}",
|
||||
"version": 1
|
||||
},
|
||||
"namespace": "space_2",
|
||||
"namespaces": ["space_2"],
|
||||
"type": "dashboard",
|
||||
"updated_at": "2017-09-21T18:57:40.826Z"
|
||||
},
|
||||
|
|
|
@ -52,7 +52,7 @@ export const TEST_CASES: Record<string, ImportTestCase> = Object.freeze({
|
|||
expectedNewId: `${CID}3`,
|
||||
}),
|
||||
CONFLICT_4_OBJ: Object.freeze({ type: 'sharedtype', id: `${CID}4`, expectedNewId: `${CID}4a` }),
|
||||
NEW_SINGLE_NAMESPACE_OBJ: Object.freeze({ type: 'dashboard', id: 'new-dashboard-id' }),
|
||||
NEW_SINGLE_NAMESPACE_OBJ: Object.freeze({ type: 'isolatedtype', id: 'new-isolatedtype-id' }),
|
||||
NEW_MULTI_NAMESPACE_OBJ: Object.freeze({ type: 'sharedtype', id: 'new-sharedtype-id' }),
|
||||
NEW_NAMESPACE_AGNOSTIC_OBJ: Object.freeze({ type: 'globaltype', id: 'new-globaltype-id' }),
|
||||
});
|
||||
|
|
|
@ -130,7 +130,6 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
spaceId,
|
||||
singleRequest,
|
||||
responseBodyOverride: expectSavedObjectForbidden([
|
||||
'dashboard',
|
||||
'globaltype',
|
||||
'isolatedtype',
|
||||
'sharedtype',
|
||||
|
@ -152,11 +151,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
overwrite,
|
||||
spaceId,
|
||||
singleRequest,
|
||||
responseBodyOverride: expectSavedObjectForbidden([
|
||||
'dashboard',
|
||||
'globaltype',
|
||||
'isolatedtype',
|
||||
]),
|
||||
responseBodyOverride: expectSavedObjectForbidden(['globaltype', 'isolatedtype']),
|
||||
}),
|
||||
createTestDefinitions(group2, true, { overwrite, spaceId, singleRequest }),
|
||||
createTestDefinitions(group3, true, { overwrite, spaceId, singleRequest }),
|
||||
|
|
|
@ -56,15 +56,11 @@
|
|||
{
|
||||
"type": "_doc",
|
||||
"value": {
|
||||
"id": "space_2:dashboard:my_dashboard",
|
||||
"id": "isolatedtype:my_isolated_object",
|
||||
"index": ".kibana",
|
||||
"source": {
|
||||
"dashboard": {
|
||||
"description": "Space 2",
|
||||
"title": "This is the second test space"
|
||||
},
|
||||
"namespace": "space_2",
|
||||
"type": "dashboard",
|
||||
"type": "isolatedtype",
|
||||
"updated_at": "2017-09-21T18:49:16.270Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
|
@ -74,15 +70,11 @@
|
|||
{
|
||||
"type": "_doc",
|
||||
"value": {
|
||||
"id": "space_1:dashboard:my_dashboard",
|
||||
"id": "isolatedtype:my_isolated_object",
|
||||
"index": ".kibana",
|
||||
"source": {
|
||||
"dashboard": {
|
||||
"description": "Space 1",
|
||||
"title": "This is the second test space"
|
||||
},
|
||||
"namespace": "space_1",
|
||||
"type": "dashboard",
|
||||
"type": "isolatedtype",
|
||||
"updated_at": "2017-09-21T18:49:16.270Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
|
@ -92,14 +84,10 @@
|
|||
{
|
||||
"type": "_doc",
|
||||
"value": {
|
||||
"id": "dashboard:my_dashboard",
|
||||
"id": "isolatedtype:my_isolated_object",
|
||||
"index": ".kibana",
|
||||
"source": {
|
||||
"dashboard": {
|
||||
"description": "Default Space",
|
||||
"title": "This is the default test space"
|
||||
},
|
||||
"type": "dashboard",
|
||||
"type": "isolatedtype",
|
||||
"updated_at": "2017-09-21T18:49:16.270Z"
|
||||
},
|
||||
"type": "_doc"
|
||||
|
@ -109,9 +97,10 @@
|
|||
{
|
||||
"type": "_doc",
|
||||
"value": {
|
||||
"id": "dashboard:cts_dashboard",
|
||||
"id": "dashboard:cts_dashboard_default",
|
||||
"index": ".kibana",
|
||||
"source": {
|
||||
"originId": "cts_dashboard",
|
||||
"dashboard": {
|
||||
"description": "Copy to Space Dashboard from the default space",
|
||||
"title": "This is the default test space CTS dashboard"
|
||||
|
@ -130,7 +119,8 @@
|
|||
"name": "CTS Vis 3"
|
||||
}],
|
||||
"type": "dashboard",
|
||||
"updated_at": "2017-09-21T18:49:16.270Z"
|
||||
"updated_at": "2017-09-21T18:49:16.270Z",
|
||||
"namespaces": ["default"]
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
|
@ -227,9 +217,10 @@
|
|||
{
|
||||
"type": "_doc",
|
||||
"value": {
|
||||
"id": "space_1:dashboard:cts_dashboard",
|
||||
"id": "dashboard:cts_dashboard_space_1",
|
||||
"index": ".kibana",
|
||||
"source": {
|
||||
"originId": "cts_dashboard",
|
||||
"dashboard": {
|
||||
"description": "Copy to Space Dashboard from space_1 space",
|
||||
"title": "This is the space_1 test space CTS dashboard"
|
||||
|
@ -253,7 +244,7 @@
|
|||
],
|
||||
"type": "dashboard",
|
||||
"updated_at": "2017-09-21T18:49:16.270Z",
|
||||
"namespace": "space_1"
|
||||
"namespaces": ["space_1"]
|
||||
},
|
||||
"type": "_doc"
|
||||
}
|
||||
|
|
|
@ -29,6 +29,23 @@ export class Plugin {
|
|||
},
|
||||
},
|
||||
});
|
||||
core.savedObjects.registerType({
|
||||
name: 'isolatedtype',
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
management: {
|
||||
icon: 'beaker',
|
||||
importableAndExportable: true,
|
||||
getTitle(obj) {
|
||||
return obj.attributes.title;
|
||||
},
|
||||
},
|
||||
mappings: {
|
||||
properties: {
|
||||
title: { type: 'text' },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start() {
|
||||
|
|
|
@ -64,9 +64,8 @@ interface SpaceBucket {
|
|||
}
|
||||
|
||||
const INITIAL_COUNTS: Record<string, Record<string, number>> = {
|
||||
[DEFAULT_SPACE_ID]: { dashboard: 2, visualization: 3, 'index-pattern': 1 },
|
||||
space_1: { dashboard: 2, visualization: 3, 'index-pattern': 1 },
|
||||
space_2: { dashboard: 1 },
|
||||
[DEFAULT_SPACE_ID]: { dashboard: 1, visualization: 3, 'index-pattern': 1 },
|
||||
space_1: { dashboard: 1, visualization: 3, 'index-pattern': 1 },
|
||||
};
|
||||
const UUID_PATTERN = new RegExp(
|
||||
/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i
|
||||
|
@ -148,18 +147,23 @@ export function copyToSpaceTestSuiteFactory(
|
|||
(spaceId: string, destination: string, expectedDashboardCount: number) =>
|
||||
async (resp: TestResponse) => {
|
||||
const result = resp.body as CopyResponse;
|
||||
|
||||
const dashboardDestinationId = result[destination].successResults![0].destinationId;
|
||||
expect(dashboardDestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
|
||||
|
||||
expect(result).to.eql({
|
||||
[destination]: {
|
||||
success: true,
|
||||
successCount: 1,
|
||||
successResults: [
|
||||
{
|
||||
id: 'cts_dashboard',
|
||||
id: `cts_dashboard_${spaceId}`,
|
||||
type: 'dashboard',
|
||||
meta: {
|
||||
title: `This is the ${spaceId} test space CTS dashboard`,
|
||||
icon: 'dashboardApp',
|
||||
},
|
||||
destinationId: dashboardDestinationId,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -172,7 +176,7 @@ export function copyToSpaceTestSuiteFactory(
|
|||
};
|
||||
|
||||
const expectNoConflictsWithoutReferencesResult = (spaceId: string = DEFAULT_SPACE_ID) =>
|
||||
createExpectNoConflictsWithoutReferencesForSpace(spaceId, getDestinationWithoutConflicts(), 2);
|
||||
createExpectNoConflictsWithoutReferencesForSpace(spaceId, getDestinationWithoutConflicts(), 1);
|
||||
|
||||
const expectNoConflictsForNonExistentSpaceResult = (spaceId: string = DEFAULT_SPACE_ID) =>
|
||||
createExpectNoConflictsWithoutReferencesForSpace(spaceId, 'non_existent_space', 1);
|
||||
|
@ -191,6 +195,8 @@ export function copyToSpaceTestSuiteFactory(
|
|||
expect(vis2DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
|
||||
const vis3DestinationId = result[destination].successResults![3].destinationId;
|
||||
expect(vis3DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
|
||||
const dashboardDestinationId = result[destination].successResults![4].destinationId;
|
||||
expect(dashboardDestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID
|
||||
|
||||
expect(result).to.eql({
|
||||
[destination]: {
|
||||
|
@ -225,12 +231,13 @@ export function copyToSpaceTestSuiteFactory(
|
|||
destinationId: vis3DestinationId,
|
||||
},
|
||||
{
|
||||
id: 'cts_dashboard',
|
||||
id: `cts_dashboard_${spaceId}`,
|
||||
type: 'dashboard',
|
||||
meta: {
|
||||
icon: 'dashboardApp',
|
||||
title: `This is the ${spaceId} test space CTS dashboard`,
|
||||
},
|
||||
destinationId: dashboardDestinationId,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -238,7 +245,7 @@ export function copyToSpaceTestSuiteFactory(
|
|||
|
||||
// Query ES to ensure that we copied everything we expected
|
||||
await assertSpaceCounts(destination, {
|
||||
dashboard: 2,
|
||||
dashboard: 1,
|
||||
visualization: 3,
|
||||
'index-pattern': 1,
|
||||
});
|
||||
|
@ -353,13 +360,14 @@ export function copyToSpaceTestSuiteFactory(
|
|||
destinationId: `cts_vis_3_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId
|
||||
},
|
||||
{
|
||||
id: 'cts_dashboard',
|
||||
id: `cts_dashboard_${spaceId}`,
|
||||
type: 'dashboard',
|
||||
meta: {
|
||||
icon: 'dashboardApp',
|
||||
title: `This is the ${spaceId} test space CTS dashboard`,
|
||||
},
|
||||
overwrite: true,
|
||||
destinationId: `cts_dashboard_${destination}`, // this conflicted with another dashboard in the destination space because of a shared originId
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -367,7 +375,7 @@ export function copyToSpaceTestSuiteFactory(
|
|||
|
||||
// Query ES to ensure that we copied everything we expected
|
||||
await assertSpaceCounts(destination, {
|
||||
dashboard: 2,
|
||||
dashboard: 1,
|
||||
visualization: 5,
|
||||
'index-pattern': 1,
|
||||
});
|
||||
|
@ -403,8 +411,11 @@ export function copyToSpaceTestSuiteFactory(
|
|||
];
|
||||
const expectedErrors = [
|
||||
{
|
||||
error: { type: 'conflict' },
|
||||
id: 'cts_dashboard',
|
||||
error: {
|
||||
type: 'conflict',
|
||||
destinationId: `cts_dashboard_${destination}`, // this conflicted with another dashboard in the destination space because of a shared originId
|
||||
},
|
||||
id: `cts_dashboard_${spaceId}`,
|
||||
title: `This is the ${spaceId} test space CTS dashboard`,
|
||||
type: 'dashboard',
|
||||
meta: {
|
||||
|
@ -662,7 +673,7 @@ export function copyToSpaceTestSuiteFactory(
|
|||
)
|
||||
);
|
||||
|
||||
const dashboardObject = { type: 'dashboard', id: 'cts_dashboard' };
|
||||
const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` };
|
||||
|
||||
it(`should return ${tests.noConflictsWithoutReferences.statusCode} when copying to space without conflicts or references`, async () => {
|
||||
const destination = getDestinationWithoutConflicts();
|
||||
|
|
|
@ -65,28 +65,28 @@ export function deleteTestSuiteFactory(
|
|||
const expectedBuckets = [
|
||||
{
|
||||
key: 'default',
|
||||
doc_count: 8,
|
||||
doc_count: 7,
|
||||
countByType: {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{ key: 'visualization', doc_count: 3 },
|
||||
{ key: 'dashboard', doc_count: 2 },
|
||||
{ key: 'space', doc_count: 2 }, // since space objects are namespace-agnostic, they appear in the "default" agg bucket
|
||||
{ key: 'dashboard', doc_count: 1 },
|
||||
{ key: 'index-pattern', doc_count: 1 },
|
||||
// legacy-url-alias objects cannot exist for the default space
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
doc_count: 7,
|
||||
doc_count: 6,
|
||||
key: 'space_1',
|
||||
countByType: {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{ key: 'visualization', doc_count: 3 },
|
||||
{ key: 'dashboard', doc_count: 2 },
|
||||
{ key: 'dashboard', doc_count: 1 },
|
||||
{ key: 'index-pattern', doc_count: 1 },
|
||||
{ key: 'legacy-url-alias', doc_count: 1 }, // alias (1)
|
||||
],
|
||||
|
|
|
@ -43,7 +43,7 @@ const {
|
|||
export const TEST_CASE_OBJECTS: Record<string, { type: string; id: string }> = deepFreeze({
|
||||
SHAREABLE_TYPE: { type: 'sharedtype', id: CASES.EACH_SPACE.id }, // contains references to four other objects
|
||||
SHAREABLE_TYPE_DOES_NOT_EXIST: { type: 'sharedtype', id: 'does-not-exist' },
|
||||
NON_SHAREABLE_TYPE: { type: 'dashboard', id: 'my_dashboard' }, // one of these exists in each space
|
||||
NON_SHAREABLE_TYPE: { type: 'isolatedtype', id: 'my_isolated_object' }, // one of these exists in each space
|
||||
});
|
||||
// Expected results for each space are defined here since they are used in multiple test suites
|
||||
export const EXPECTED_RESULTS: Record<string, SavedObjectReferenceWithContext[]> = {
|
||||
|
|
|
@ -63,7 +63,7 @@ export function resolveCopyToSpaceConflictsSuite(
|
|||
};
|
||||
const getDashboardAtSpace = async (spaceId: string): Promise<SavedObject<any>> => {
|
||||
return supertestWithAuth
|
||||
.get(`${getUrlPrefix(spaceId)}/api/saved_objects/dashboard/cts_dashboard`)
|
||||
.get(`${getUrlPrefix(spaceId)}/api/saved_objects/dashboard/cts_dashboard_${spaceId}`)
|
||||
.then((response: any) => response.body);
|
||||
};
|
||||
|
||||
|
@ -124,12 +124,13 @@ export function resolveCopyToSpaceConflictsSuite(
|
|||
successCount: 1,
|
||||
successResults: [
|
||||
{
|
||||
id: 'cts_dashboard',
|
||||
id: `cts_dashboard_${sourceSpaceId}`,
|
||||
type: 'dashboard',
|
||||
meta: {
|
||||
title: `This is the ${sourceSpaceId} test space CTS dashboard`,
|
||||
icon: 'dashboardApp',
|
||||
},
|
||||
destinationId: `cts_dashboard_${destinationSpaceId}`, // this conflicted with another dashboard in the destination space because of a shared originId
|
||||
overwrite: true,
|
||||
},
|
||||
],
|
||||
|
@ -204,8 +205,11 @@ export function resolveCopyToSpaceConflictsSuite(
|
|||
successCount: 0,
|
||||
errors: [
|
||||
{
|
||||
error: { type: 'conflict' },
|
||||
id: 'cts_dashboard',
|
||||
error: {
|
||||
type: 'conflict',
|
||||
destinationId: `cts_dashboard_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId
|
||||
},
|
||||
id: `cts_dashboard_${sourceSpaceId}`,
|
||||
type: 'dashboard',
|
||||
title: `This is the ${sourceSpaceId} test space CTS dashboard`,
|
||||
meta: {
|
||||
|
@ -442,7 +446,7 @@ export function resolveCopyToSpaceConflictsSuite(
|
|||
)
|
||||
);
|
||||
|
||||
const dashboardObject = { type: 'dashboard', id: 'cts_dashboard' };
|
||||
const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` };
|
||||
const visualizationObject = { type: 'visualization', id: `cts_vis_3_${spaceId}` };
|
||||
const indexPatternObject = { type: 'index-pattern', id: `cts_ip_1_${spaceId}` };
|
||||
|
||||
|
@ -514,7 +518,15 @@ export function resolveCopyToSpaceConflictsSuite(
|
|||
objects: [dashboardObject],
|
||||
includeReferences: false,
|
||||
createNewCopies: false,
|
||||
retries: { [destination]: [{ ...dashboardObject, overwrite: true }] },
|
||||
retries: {
|
||||
[destination]: [
|
||||
{
|
||||
...dashboardObject,
|
||||
destinationId: `cts_dashboard_${destination}`,
|
||||
overwrite: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.expect(tests.withoutReferencesOverwriting.statusCode)
|
||||
.then(tests.withoutReferencesOverwriting.response);
|
||||
|
@ -530,7 +542,15 @@ export function resolveCopyToSpaceConflictsSuite(
|
|||
objects: [dashboardObject],
|
||||
includeReferences: false,
|
||||
createNewCopies: false,
|
||||
retries: { [destination]: [{ ...dashboardObject, overwrite: false }] },
|
||||
retries: {
|
||||
[destination]: [
|
||||
{
|
||||
...dashboardObject,
|
||||
destinationId: `cts_dashboard_${destination}`,
|
||||
overwrite: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.expect(tests.withoutReferencesNotOverwriting.statusCode)
|
||||
.then(tests.withoutReferencesNotOverwriting.response);
|
||||
|
@ -546,7 +566,17 @@ export function resolveCopyToSpaceConflictsSuite(
|
|||
objects: [dashboardObject],
|
||||
includeReferences: false,
|
||||
createNewCopies: false,
|
||||
retries: { [destination]: [{ ...dashboardObject, overwrite: true }] },
|
||||
retries: {
|
||||
[destination]: [
|
||||
{
|
||||
...dashboardObject,
|
||||
destinationId: `cts_dashboard_${destination}`,
|
||||
// realistically a retry wouldn't use a destinationId, because it wouldn't have an origin conflict with another
|
||||
// object in a non-existent space, but for the simplicity of testing we'll use this here
|
||||
overwrite: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.expect(tests.nonExistentSpace.statusCode)
|
||||
.then(tests.nonExistentSpace.response);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue