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

Initial client-side support for sharing saved-objects phase 1.5 #69399

Merged
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
b155c98
Change "Copy to space" action
jportner Mar 31, 2020
282498e
Cleanup copy-to-space UI controls
jportner Mar 31, 2020
deb09ab
Add "Share to space" action
jportner May 19, 2020
ff61253
Allow `SavedObjectsManagementAction` to trigger a refresh on finish
jportner May 21, 2020
0677b83
Sort saved objects management table by `_id`
jportner May 26, 2020
1659bb4
Revert "Sort saved objects management table by `_id`"
jportner May 27, 2020
a509e36
Saved object management table: allow refresh of single object
jportner May 28, 2020
8f2241d
Change Share-to-space action
jportner May 29, 2020
20aae34
Add "Spaces" column to Saved Object Management table
jportner Jun 2, 2020
c574ff9
Update import conflict resolution for multi-namespace objects
jportner Jun 4, 2020
1c4ef45
Expose `FailedImport` from the Kibana Platform
jportner Jun 4, 2020
b51b91f
Refactor `summarizeCopyResult`
jportner Jun 4, 2020
c257b42
Change Saved Object Import APIs to return metadata
jportner Jun 9, 2020
04307ef
Return successful imports after processing import response
jportner Jun 9, 2020
70179f5
Reworked copy results
jportner Jun 10, 2020
9d9b177
Change the "Copy results" to use switches to control overwrite
jportner Jun 11, 2020
32d828e
Fix inaccurate import error count
jportner Jun 11, 2020
6b9dd67
Prevent resolveImportErrors from creating duplicate retries
jportner Jun 12, 2020
0ce208d
Change `idToOverwrite` to `destinationId`
jportner Jul 1, 2020
34f60d7
Change how resolveImportErrors checks for conflicts -- use retries
jportner Jul 1, 2020
f4886c6
Remove `blocking` field from `missing_references` errors
jportner Jul 2, 2020
02e7ff9
Fix resolveImportErrors integration tests
jportner Jul 2, 2020
dfde63a
Update Import and Copy dev docs
jportner Jul 8, 2020
59d3a2a
Add ambiguous conflict UI for copy to space
jportner Jul 14, 2020
8f6eb96
Add ambiguous conflict UI for import
jportner Jul 14, 2020
c4d6d2a
Add "Create a new copy" option UI for import
jportner Jul 16, 2020
664f252
Add "Create a new copy" option UI for copy to space
jportner Jul 16, 2020
16bef0b
Label changes
jportner Jul 16, 2020
91b4cb3
Change ambiguous conflict destinations - sort by `updatedAt`
jportner Jul 16, 2020
11017b3
Fix tooltip text size
jportner Jul 16, 2020
9f7842d
Add number badge to copy space result
jportner Jul 20, 2020
c1e26ff
Add "resolve all" option for dealing with copy-to-space conflicts
jportner Jul 20, 2020
7997902
Disable actions based on object's `namespaceType`
jportner Jul 21, 2020
b222f1b
Add `ignoreMissingReferences` option for retries
jportner Jul 21, 2020
9fb291f
Fix client-side import error resolutions
jportner Jul 22, 2020
1485789
Return missing_references and conflict errors for same objects
jportner Jul 22, 2020
f828bc8
Add optional `overwrite` attribute to import/copy results
jportner Jul 22, 2020
5a84572
Overhaul copy-to-space summary counts and icons
jportner Jul 23, 2020
e08f229
Hide "Shared spaces" column if no shared types are registered
jportner Jul 23, 2020
7a74a6a
Revert "Hide "Shared spaces" column if no shared types are registered"
jportner Aug 10, 2020
407b10f
Address the rest of 1st round of review comments
jportner Jul 23, 2020
dc10ba1
Minor UI tweaks
jportner Jul 30, 2020
12037fd
Add new import summary
jportner Aug 11, 2020
91932d1
Update Copy flyout based on review feedback
jportner Aug 12, 2020
7a8cddf
Update Import flyout based on review feedback
jportner Aug 12, 2020
8f2082c
Prevent users from copying to spaces where an object already exists
jportner Aug 13, 2020
92ea812
Address 2nd round of review comments
jportner Aug 13, 2020
1b34a2d
Rename CSS class for consistency
jportner Aug 13, 2020
d885e64
Tweak import and copy flyout options
jportner Aug 14, 2020
2350e2f
Changes for platform team review feedbck
jportner Aug 19, 2020
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
Prev Previous commit
Changes for platform team review feedbck
jportner committed Aug 19, 2020
commit 2350e2f28fe8c880a7fe5df5277a644935205b1d
7 changes: 6 additions & 1 deletion docs/api/saved-objects/find.asciidoc
Original file line number Diff line number Diff line change
@@ -44,7 +44,12 @@ experimental[] Retrieve a paginated set of {kib} saved objects by various condit
(Optional, array|string) The fields to return in the `attributes` key of the response.

`sort_field`::
(Optional, string) The field that sorts the response.
(Optional, string) The field that sorts the response. There are two kinds of fields: "root" fields that exist for all saved objects (such
as "updated_at"), and "type" fields that are specific to a given object type (e.g. those fields that are returned in the `attributes` key
of the response).
* If a single type is defined in the `type` parameter, both "type" fields and "root" fields are allowed, and validity checks are made in
that order.
* If multiple types are defined in the `type` parameter, only "root" fields are allowed.

`has_reference`::
(Optional, object) Filters to objects that have a relationship with the type and ID combination.
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ describe('#importSavedObjectsFromStream', () => {
importIdMap: new Map(),
});
getMockFn(regenerateIds).mockReturnValue(new Map());
getMockFn(validateReferences).mockResolvedValue({ errors: [] });
getMockFn(validateReferences).mockResolvedValue([]);
getMockFn(checkConflicts).mockResolvedValue({
errors: [],
filteredObjects: [],
@@ -228,9 +228,7 @@ describe('#importSavedObjectsFromStream', () => {
['baz', {}],
]),
});
getMockFn(validateReferences).mockResolvedValue({
errors: [errors[1]],
});
getMockFn(validateReferences).mockResolvedValue([errors[1]]);
getMockFn(checkConflicts).mockResolvedValue({
errors: [errors[2]],
filteredObjects,
@@ -277,7 +275,7 @@ describe('#importSavedObjectsFromStream', () => {

test('does not check conflicts or check origin conflicts', async () => {
const options = setupOptions(true);
getMockFn(validateReferences).mockResolvedValue({ errors: [] });
getMockFn(validateReferences).mockResolvedValue([]);

await importSavedObjectsFromStream(options);
expect(checkConflicts).not.toHaveBeenCalled();
@@ -296,7 +294,7 @@ describe('#importSavedObjectsFromStream', () => {
['bar', {}],
]),
});
getMockFn(validateReferences).mockResolvedValue({ errors: [errors[1]] });
getMockFn(validateReferences).mockResolvedValue([errors[1]]);
// this importIdMap is not composed with the one obtained from `collectSavedObjects`
const importIdMap = new Map().set(`id1`, { id: `newId1` });
getMockFn(regenerateIds).mockReturnValue(importIdMap);
@@ -429,7 +427,7 @@ describe('#importSavedObjectsFromStream', () => {
collectedObjects: [],
importIdMap: new Map(), // doesn't matter
});
getMockFn(validateReferences).mockResolvedValue({ errors: [errors[1]] });
getMockFn(validateReferences).mockResolvedValue([errors[1]]);
getMockFn(checkConflicts).mockResolvedValue({
errors: [errors[2]],
filteredObjects: [],
Original file line number Diff line number Diff line change
@@ -64,7 +64,7 @@ export async function importSavedObjectsFromStream({
savedObjectsClient,
namespace
);
errorAccumulator = [...errorAccumulator, ...validateReferencesResult.errors];
errorAccumulator = [...errorAccumulator, ...validateReferencesResult];

if (createNewCopies) {
importIdMap = regenerateIds(collectSavedObjectsResult.collectedObjects);
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@ describe('#importSavedObjectsFromStream', () => {
importIdMap: new Map(),
});
getMockFn(regenerateIds).mockReturnValue(new Map());
getMockFn(validateReferences).mockResolvedValue({ errors: [] });
getMockFn(validateReferences).mockResolvedValue([]);
getMockFn(checkConflicts).mockResolvedValue({
errors: [],
filteredObjects: [],
@@ -303,9 +303,7 @@ describe('#importSavedObjectsFromStream', () => {
collectedObjects: [], // doesn't matter
importIdMap: new Map(), // doesn't matter
});
getMockFn(validateReferences).mockResolvedValue({
errors: [errors[1]],
});
getMockFn(validateReferences).mockResolvedValue([errors[1]]);
getMockFn(checkConflicts).mockResolvedValue({
errors: [errors[2]],
filteredObjects: [],
@@ -371,9 +369,7 @@ describe('#importSavedObjectsFromStream', () => {
collectedObjects: [], // doesn't matter
importIdMap: new Map(), // doesn't matter
});
getMockFn(validateReferences).mockResolvedValue({
errors: [errors[1]],
});
getMockFn(validateReferences).mockResolvedValue([errors[1]]);
getMockFn(regenerateIds).mockReturnValue(
new Map([
['foo', { id: 'randomId1' }],
@@ -503,7 +499,7 @@ describe('#importSavedObjectsFromStream', () => {
collectedObjects: [],
importIdMap: new Map(), // doesn't matter
});
getMockFn(validateReferences).mockResolvedValue({ errors: [errors[1]] });
getMockFn(validateReferences).mockResolvedValue([errors[1]]);
getMockFn(createSavedObjects).mockResolvedValueOnce({
errors: [errors[2]],
createdObjects: [],
Original file line number Diff line number Diff line change
@@ -99,7 +99,7 @@ export async function resolveSavedObjectsImportErrors({
namespace,
retries
);
errorAccumulator = [...errorAccumulator, ...validateReferencesResult.errors];
errorAccumulator = [...errorAccumulator, ...validateReferencesResult];

if (createNewCopies) {
// In case any missing reference errors were resolved, ensure that we regenerate those object IDs as well
108 changes: 45 additions & 63 deletions src/core/server/saved_objects/import/validate_references.test.ts
Original file line number Diff line number Diff line change
@@ -254,11 +254,7 @@ describe('validateReferences()', () => {

test('returns empty when no objects are passed in', async () => {
const result = await validateReferences([], savedObjectsClient);
expect(result).toMatchInlineSnapshot(`
Object {
"errors": Array [],
}
`);
expect(result).toEqual([]);
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(0);
});

@@ -357,52 +353,50 @@ describe('validateReferences()', () => {
];
const result = await validateReferences(savedObjects, savedObjectsClient);
expect(result).toMatchInlineSnapshot(`
Object {
"errors": Array [
Object {
"error": Object {
"references": Array [
Object {
"id": "3",
"type": "index-pattern",
},
],
"type": "missing_references",
},
"id": "2",
"meta": Object {
"title": "My Visualization 2",
},
Array [
Object {
"error": Object {
"references": Array [
Object {
"id": "3",
"type": "index-pattern",
},
],
"type": "missing_references",
},
"id": "2",
"meta": Object {
"title": "My Visualization 2",
"type": "visualization",
},
Object {
"error": Object {
"references": Array [
Object {
"id": "5",
"type": "index-pattern",
},
Object {
"id": "6",
"type": "index-pattern",
},
Object {
"id": "7",
"type": "search",
},
],
"type": "missing_references",
},
"id": "4",
"meta": Object {
"title": "My Visualization 4",
},
"title": "My Visualization 2",
"type": "visualization",
},
Object {
"error": Object {
"references": Array [
Object {
"id": "5",
"type": "index-pattern",
},
Object {
"id": "6",
"type": "index-pattern",
},
Object {
"id": "7",
"type": "search",
},
],
"type": "missing_references",
},
"id": "4",
"meta": Object {
"title": "My Visualization 4",
"type": "visualization",
},
],
}
"title": "My Visualization 4",
"type": "visualization",
},
]
`);
expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(`
[MockFunction] {
@@ -479,7 +473,7 @@ describe('validateReferences()', () => {
},
];
const result = await validateReferences(savedObjects, savedObjectsClient, undefined, retries);
expect(result.errors).toHaveLength(0);
expect(result).toEqual([]);
expect(savedObjectsClient.bulkGet).not.toHaveBeenCalled();
});

@@ -509,11 +503,7 @@ describe('validateReferences()', () => {
},
];
const result = await validateReferences(savedObjects, savedObjectsClient);
expect(result).toMatchInlineSnapshot(`
Object {
"errors": Array [],
}
`);
expect(result).toEqual([]);
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(1);
});

@@ -539,11 +529,7 @@ describe('validateReferences()', () => {
},
];
const result = await validateReferences(savedObjects, savedObjectsClient);
expect(result).toMatchInlineSnapshot(`
Object {
"errors": Array [],
}
`);
expect(result).toEqual([]);
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(0);
});

@@ -568,11 +554,7 @@ describe('validateReferences()', () => {
},
];
const result = await validateReferences(savedObjects, savedObjectsClient);
expect(result).toMatchInlineSnapshot(`
Object {
"errors": Array [],
}
`);
expect(result).toEqual([]);
expect(savedObjectsClient.bulkGet).toHaveBeenCalledTimes(0);
});

4 changes: 1 addition & 3 deletions src/core/server/saved_objects/import/validate_references.ts
Original file line number Diff line number Diff line change
@@ -130,7 +130,5 @@ export async function validateReferences(
};
});

return {
errors: Object.values(errorMap),
};
return Object.values(errorMap);
}
Original file line number Diff line number Diff line change
@@ -47,9 +47,7 @@ export async function findObject(
type: string,
id: string
): Promise<SavedObjectWithMetadata> {
const response = await http.get<Record<string, any>>(
return await http.get<SavedObjectWithMetadata>(
`/api/kibana/management/saved_objects/${encodeURIComponent(type)}/${encodeURIComponent(id)}`
);

return response as SavedObjectWithMetadata;
}
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@
*/

import { HttpStart, SavedObjectsImportError } from 'src/core/public';
import { ImportMode } from '../management_section/objects_table/components/import_mode_control';

interface ImportResponse {
success: boolean;
@@ -28,8 +29,7 @@ interface ImportResponse {
export async function importFile(
http: HttpStart,
file: File,
createNewCopies: boolean,
overwrite: boolean
{ createNewCopies, overwrite }: ImportMode
) {
const formData = new FormData();
formData.append('file', file);
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ import {
} from 'src/core/public';

export interface FailedImport {
obj: Pick<SavedObjectsImportError, 'id' | 'type' | 'title' | 'meta' | 'overwrite'>;
obj: Omit<SavedObjectsImportError, 'error'>;
error:
| SavedObjectsImportConflictError
| SavedObjectsImportAmbiguousConflictError
Original file line number Diff line number Diff line change
@@ -178,7 +178,10 @@ describe('Flyout', () => {
component.setState({ file: mockFile, isLegacyFile: false });
await component.instance().import();

expect(importFileMock).toHaveBeenCalledWith(defaultProps.http, mockFile, false, true);
expect(importFileMock).toHaveBeenCalledWith(defaultProps.http, mockFile, {
createNewCopies: false,
overwrite: true,
});
expect(component.state()).toMatchObject({
conflictedIndexPatterns: undefined,
conflictedSavedObjectsLinkedToSavedSearches: undefined,
Original file line number Diff line number Diff line change
@@ -160,12 +160,11 @@ export class Flyout extends Component<FlyoutProps, FlyoutState> {
import = async () => {
const { http } = this.props;
const { file, importMode } = this.state;
const { createNewCopies, overwrite } = importMode;
this.setState({ status: 'loading', error: undefined });

// Import the file
try {
const response = await importFile(http, file!, createNewCopies, overwrite);
const response = await importFile(http, file!, importMode);
this.setState(processImportResponse(response), () => {
// Resolve import errors right away if there's no index patterns to match
// This will ask about overwriting each object, etc
@@ -912,9 +911,9 @@ export class Flyout extends Component<FlyoutProps, FlyoutState> {
const { conflictingRecord } = this.state;
if (conflictingRecord) {
const { conflict } = conflictingRecord;
const finish = (overwrite: boolean, destinationId?: string) =>
const onFinish = (overwrite: boolean, destinationId?: string) =>
conflictingRecord.done([overwrite, destinationId]);
confirmOverwriteModal = <OverwriteModal {...{ conflict, finish }} />;
confirmOverwriteModal = <OverwriteModal {...{ conflict, onFinish }} />;
}

return (
Loading