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

[Saved Objects] Allow exporting and importing hidden types #90178

Merged
merged 22 commits into from
Feb 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dcf79c6
initial implementation
Bamieh Feb 3, 2021
70796b2
Merge branch 'master' of github.com:elastic/kibana into saved_objects…
Bamieh Feb 3, 2021
c6c6719
revert testing changes
Bamieh Feb 3, 2021
9823639
some code review fixes
Bamieh Feb 5, 2021
d5a6ffa
remove unused types
Bamieh Feb 5, 2021
6a23d63
Merge branch 'master' of github.com:elastic/kibana into saved_objects…
Bamieh Feb 6, 2021
cd3d2c3
pr review updates + cleanup
Bamieh Feb 6, 2021
7f8aa13
Merge branch 'master' of github.com:elastic/kibana into saved_objects…
Bamieh Feb 8, 2021
36143d1
update all SOM routes to show includedHiddenTypes
Bamieh Feb 8, 2021
55a6974
fix tests
Bamieh Feb 8, 2021
96d7096
Merge branch 'master' into saved_objects/export_import_hidden_types
kibanamachine Feb 8, 2021
497b322
Merge branch 'master' of github.com:elastic/kibana into saved_objects…
Bamieh Feb 9, 2021
a3f8db3
Merge branch 'master' of github.com:elastic/kibana into saved_objects…
Bamieh Feb 10, 2021
34fc8d4
Merge branch 'master' of github.com:elastic/kibana into saved_objects…
Bamieh Feb 11, 2021
3b83707
add ftr tests
Bamieh Feb 11, 2021
74d32ff
return this.client if options undefined
Bamieh Feb 11, 2021
9581554
move to tests to plugin_functional
Bamieh Feb 11, 2021
3c2bef4
self codereview
Bamieh Feb 11, 2021
fcce36a
fix typo
Bamieh Feb 11, 2021
c437d55
tidying up things
Bamieh Feb 11, 2021
9b603aa
Merge branch 'master' of github.com:elastic/kibana into saved_objects…
Bamieh Feb 14, 2021
f8cf3fb
add FTR test for SOM UI
Bamieh Feb 14, 2021
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
Expand Up @@ -11,8 +11,9 @@ core: {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
exporter: ISavedObjectsExporter;
importer: ISavedObjectsImporter;
getClient: (options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract;
getExporter: (client: SavedObjectsClientContract) => ISavedObjectsExporter;
getImporter: (client: SavedObjectsClientContract) => ISavedObjectsImporter;
};
elasticsearch: {
client: IScopedClusterClient;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ export interface RequestHandlerContext

| Property | Type | Description |
| --- | --- | --- |
| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | <code>{</code><br/><code> savedObjects: {</code><br/><code> client: SavedObjectsClientContract;</code><br/><code> typeRegistry: ISavedObjectTypeRegistry;</code><br/><code> exporter: ISavedObjectsExporter;</code><br/><code> importer: ISavedObjectsImporter;</code><br/><code> };</code><br/><code> elasticsearch: {</code><br/><code> client: IScopedClusterClient;</code><br/><code> legacy: {</code><br/><code> client: ILegacyScopedClusterClient;</code><br/><code> };</code><br/><code> };</code><br/><code> uiSettings: {</code><br/><code> client: IUiSettingsClient;</code><br/><code> };</code><br/><code> }</code> | |
| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | <code>{</code><br/><code> savedObjects: {</code><br/><code> client: SavedObjectsClientContract;</code><br/><code> typeRegistry: ISavedObjectTypeRegistry;</code><br/><code> getClient: (options?: SavedObjectsClientProviderOptions) =&gt; SavedObjectsClientContract;</code><br/><code> getExporter: (client: SavedObjectsClientContract) =&gt; ISavedObjectsExporter;</code><br/><code> getImporter: (client: SavedObjectsClientContract) =&gt; ISavedObjectsImporter;</code><br/><code> };</code><br/><code> elasticsearch: {</code><br/><code> client: IScopedClusterClient;</code><br/><code> legacy: {</code><br/><code> client: ILegacyScopedClusterClient;</code><br/><code> };</code><br/><code> };</code><br/><code> uiSettings: {</code><br/><code> client: IUiSettingsClient;</code><br/><code> };</code><br/><code> }</code> | |

28 changes: 12 additions & 16 deletions src/core/server/core_route_handler_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { SavedObjectsClientContract } from './saved_objects/types';
import {
InternalSavedObjectsServiceStart,
ISavedObjectTypeRegistry,
ISavedObjectsExporter,
ISavedObjectsImporter,
SavedObjectsClientProviderOptions,
} from './saved_objects';
import {
InternalElasticsearchServiceStart,
Expand Down Expand Up @@ -58,8 +57,6 @@ class CoreSavedObjectsRouteHandlerContext {
) {}
#scopedSavedObjectsClient?: SavedObjectsClientContract;
#typeRegistry?: ISavedObjectTypeRegistry;
#exporter?: ISavedObjectsExporter;
#importer?: ISavedObjectsImporter;

public get client() {
if (this.#scopedSavedObjectsClient == null) {
Expand All @@ -75,19 +72,18 @@ class CoreSavedObjectsRouteHandlerContext {
return this.#typeRegistry;
}

public get exporter() {
if (this.#exporter == null) {
this.#exporter = this.savedObjectsStart.createExporter(this.client);
}
return this.#exporter;
}
public getClient = (options?: SavedObjectsClientProviderOptions) => {
if (!options) return this.client;
return this.savedObjectsStart.getScopedClient(this.request, options);
};

public get importer() {
if (this.#importer == null) {
this.#importer = this.savedObjectsStart.createImporter(this.client);
}
return this.#importer;
}
public getExporter = (client: SavedObjectsClientContract) => {
return this.savedObjectsStart.createExporter(client);
};

public getImporter = (client: SavedObjectsClientContract) => {
return this.savedObjectsStart.createImporter(client);
};
}

class CoreUiSettingsRouteHandlerContext {
Expand Down
6 changes: 4 additions & 2 deletions src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
SavedObjectsServiceStart,
ISavedObjectsExporter,
ISavedObjectsImporter,
SavedObjectsClientProviderOptions,
} from './saved_objects';
import { CapabilitiesSetup, CapabilitiesStart } from './capabilities';
import { MetricsServiceSetup, MetricsServiceStart } from './metrics';
Expand Down Expand Up @@ -415,8 +416,9 @@ export interface RequestHandlerContext {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
exporter: ISavedObjectsExporter;
importer: ISavedObjectsImporter;
getClient: (options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract;
getExporter: (client: SavedObjectsClientContract) => ISavedObjectsExporter;
getImporter: (client: SavedObjectsClientContract) => ISavedObjectsImporter;
};
elasticsearch: {
client: IScopedClusterClient;
Expand Down
5 changes: 3 additions & 2 deletions src/core/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ function createCoreRequestHandlerContextMock() {
savedObjects: {
client: savedObjectsClientMock.create(),
typeRegistry: savedObjectsTypeRegistryMock.create(),
exporter: savedObjectsServiceMock.createExporter(),
importer: savedObjectsServiceMock.createImporter(),
getClient: savedObjectsClientMock.create,
getExporter: savedObjectsServiceMock.createExporter,
getImporter: savedObjectsServiceMock.createImporter,
},
elasticsearch: {
client: elasticsearchServiceMock.createScopedClusterClient(),
Expand Down
4 changes: 3 additions & 1 deletion src/core/server/saved_objects/routes/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ export const registerDeleteRoute = (router: IRouter, { coreUsageData }: RouteDep
catchAndReturnBoomErrors(async (context, req, res) => {
const { type, id } = req.params;
const { force } = req.query;
const { getClient } = context.core.savedObjects;

const usageStatsClient = coreUsageData.getClient();
usageStatsClient.incrementSavedObjectsDelete({ request: req }).catch(() => {});

const result = await context.core.savedObjects.client.delete(type, id, { force });
const client = getClient();
Bamieh marked this conversation as resolved.
Show resolved Hide resolved
const result = await client.delete(type, id, { force });
return res.ok({ body: result });
})
);
Expand Down
13 changes: 9 additions & 4 deletions src/core/server/saved_objects/routes/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ export const registerExportRoute = (
},
catchAndReturnBoomErrors(async (context, req, res) => {
const cleaned = cleanOptions(req.body);
const supportedTypes = context.core.savedObjects.typeRegistry
.getImportableAndExportableTypes()
.map((t) => t.name);
const { typeRegistry, getExporter, getClient } = context.core.savedObjects;
const supportedTypes = typeRegistry.getImportableAndExportableTypes().map((t) => t.name);

let options: EitherExportOptions;
try {
options = validateOptions(cleaned, {
Expand All @@ -181,7 +181,12 @@ export const registerExportRoute = (
});
}

const exporter = context.core.savedObjects.exporter;
const includedHiddenTypes = supportedTypes.filter((supportedType) =>
typeRegistry.isHidden(supportedType)
);

const client = getClient({ includedHiddenTypes });
Bamieh marked this conversation as resolved.
Show resolved Hide resolved
const exporter = getExporter(client);

const usageStatsClient = coreUsageData.getClient();
usageStatsClient
Expand Down
11 changes: 10 additions & 1 deletion src/core/server/saved_objects/routes/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const registerImportRoute = (
},
catchAndReturnBoomErrors(async (context, req, res) => {
const { overwrite, createNewCopies } = req.query;
const { getClient, getImporter, typeRegistry } = context.core.savedObjects;

const usageStatsClient = coreUsageData.getClient();
usageStatsClient
Expand All @@ -84,7 +85,15 @@ export const registerImportRoute = (
});
}

const { importer } = context.core.savedObjects;
const supportedTypes = typeRegistry.getImportableAndExportableTypes().map((t) => t.name);

const includedHiddenTypes = supportedTypes.filter((supportedType) =>
typeRegistry.isHidden(supportedType)
);

const client = getClient({ includedHiddenTypes });
const importer = getImporter(client);

try {
const result = await importer.import({
readStream,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ describe('DELETE /api/saved_objects/{type}/{id}', () => {

beforeEach(async () => {
({ server, httpSetup, handlerContext } = await setupServer());
savedObjectsClient = handlerContext.savedObjects.client;
savedObjectsClient = handlerContext.savedObjects.getClient();
handlerContext.savedObjects.getClient = jest.fn().mockImplementation(() => savedObjectsClient);

const router = httpSetup.createRouter('/api/saved_objects/');
coreUsageStatsClient = coreUsageStatsClientMock.create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@ describe('POST /api/saved_objects/_export', () => {
handlerContext.savedObjects.typeRegistry.getImportableAndExportableTypes.mockReturnValue(
allowedTypes.map(createExportableType)
);
exporter = handlerContext.savedObjects.exporter;
exporter = handlerContext.savedObjects.getExporter();

const router = httpSetup.createRouter('/api/saved_objects/');
handlerContext.savedObjects.getExporter = jest
.fn()
.mockImplementation(() => exporter as ReturnType<typeof savedObjectsExporterMock.create>);

coreUsageStatsClient = coreUsageStatsClientMock.create();
coreUsageStatsClient.incrementSavedObjectsExport.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail
const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient);
Expand Down Expand Up @@ -77,6 +81,7 @@ describe('POST /api/saved_objects/_export', () => {
],
},
];

exporter.exportByTypes.mockResolvedValueOnce(createListStream(sortedObjects));

const result = await supertest(httpSetup.server.listener)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ describe(`POST ${URL}`, () => {
typeRegistry: handlerContext.savedObjects.typeRegistry,
importSizeLimit: 10000,
});
handlerContext.savedObjects.importer.import.mockImplementation((options) =>
importer.import(options)
);
handlerContext.savedObjects.getImporter = jest
.fn()
.mockImplementation(() => importer as jest.Mocked<SavedObjectsImporter>);

const router = httpSetup.createRouter('/internal/saved_objects/');
coreUsageStatsClient = coreUsageStatsClientMock.create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,18 @@ describe(`POST ${URL}`, () => {
} as any)
);

savedObjectsClient = handlerContext.savedObjects.client;
savedObjectsClient = handlerContext.savedObjects.getClient();
savedObjectsClient.checkConflicts.mockResolvedValue({ errors: [] });

const importer = new SavedObjectsImporter({
savedObjectsClient,
typeRegistry: handlerContext.savedObjects.typeRegistry,
importSizeLimit: 10000,
});
handlerContext.savedObjects.importer.resolveImportErrors.mockImplementation((options) =>
importer.resolveImportErrors(options)
);

handlerContext.savedObjects.getImporter = jest
.fn()
.mockImplementation(() => importer as jest.Mocked<SavedObjectsImporter>);

const router = httpSetup.createRouter('/api/saved_objects/');
coreUsageStatsClient = coreUsageStatsClientMock.create();
Expand Down
14 changes: 13 additions & 1 deletion src/core/server/saved_objects/routes/resolve_import_errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { extname } from 'path';
import { Readable } from 'stream';
import { schema } from '@kbn/config-schema';
import { chain } from 'lodash';
import { IRouter } from '../../http';
import { CoreUsageDataSetup } from '../../core_usage_data';
import { SavedObjectConfig } from '../saved_objects_config';
Expand Down Expand Up @@ -91,7 +92,18 @@ export const registerResolveImportErrorsRoute = (
});
}

const { importer } = context.core.savedObjects;
const { getClient, getImporter, typeRegistry } = context.core.savedObjects;

const includedHiddenTypes = chain(req.body.retries)
.map('type')
.uniq()
.filter(
(type) => typeRegistry.isHidden(type) && typeRegistry.isImportableAndExportable(type)
)
.value();
Bamieh marked this conversation as resolved.
Show resolved Hide resolved

const client = getClient({ includedHiddenTypes });
const importer = getImporter(client);

try {
const result = await importer.resolveImportErrors({
Expand Down
5 changes: 3 additions & 2 deletions src/core/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1924,8 +1924,9 @@ export interface RequestHandlerContext {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
exporter: ISavedObjectsExporter;
importer: ISavedObjectsImporter;
getClient: (options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract;
getExporter: (client: SavedObjectsClientContract) => ISavedObjectsExporter;
getImporter: (client: SavedObjectsClientContract) => ISavedObjectsImporter;
};
elasticsearch: {
client: IScopedClusterClient;
Expand Down
14 changes: 11 additions & 3 deletions src/plugins/home/server/services/sample_data/routes/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,15 @@ export function createInstallRoute(

let createResults;
try {
createResults = await context.core.savedObjects.client.bulkCreate(
const { getClient, typeRegistry } = context.core.savedObjects;

const includedHiddenTypes = sampleDataset.savedObjects
.map((object) => object.type)
.filter((supportedType) => typeRegistry.isHidden(supportedType));
Bamieh marked this conversation as resolved.
Show resolved Hide resolved

const client = getClient({ includedHiddenTypes });

createResults = await client.bulkCreate(
sampleDataset.savedObjects.map(({ version, ...savedObject }) => savedObject),
{ overwrite: true }
);
Expand All @@ -156,8 +164,8 @@ export function createInstallRoute(
return Boolean(savedObjectCreateResult.error);
});
if (errors.length > 0) {
const errMsg = `sample_data install errors while loading saved objects. Errors: ${errors.join(
','
const errMsg = `sample_data install errors while loading saved objects. Errors: ${JSON.stringify(
errors
Bamieh marked this conversation as resolved.
Show resolved Hide resolved
)}`;
logger.warn(errMsg);
return res.customError({ body: errMsg, statusCode: 403 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function createUninstallRoute(
client: { callAsCurrentUser },
},
},
savedObjects: { client: savedObjectsClient },
savedObjects: { getClient: getSavedObjectsClient, typeRegistry },
},
},
request,
Expand Down Expand Up @@ -61,6 +61,12 @@ export function createUninstallRoute(
}
}

const includedHiddenTypes = sampleDataset.savedObjects
.map((object) => object.type)
.filter((supportedType) => typeRegistry.isHidden(supportedType));
Bamieh marked this conversation as resolved.
Show resolved Hide resolved

const savedObjectsClient = getSavedObjectsClient({ includedHiddenTypes });

const deletePromises = sampleDataset.savedObjects.map(({ type, id }) =>
savedObjectsClient.delete(type, id)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,10 @@ const CountIndicators: FC<{ importItems: ImportItem[] }> = ({ importItems }) =>
{errorCount && (
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h4 className="savedObjectsManagementImportSummary__errorCount">
<h4
data-test-subj="importSavedObjectsErrorsCount"
className="savedObjectsManagementImportSummary__errorCount"
>
<FormattedMessage
id="savedObjectsManagement.importSummary.errorCountHeader"
defaultMessage="{errorCount} error"
Expand Down
21 changes: 14 additions & 7 deletions src/plugins/saved_objects_management/server/routes/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,24 @@ export const registerFindRoute = (
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { query } = req;
const managementService = await managementServicePromise;
const { client } = context.core.savedObjects;
const searchTypes = Array.isArray(req.query.type) ? req.query.type : [req.query.type];
const includedFields = Array.isArray(req.query.fields)
? req.query.fields
: [req.query.fields];
const { getClient, typeRegistry } = context.core.savedObjects;

const searchTypes = Array.isArray(query.type) ? query.type : [query.type];
const includedFields = Array.isArray(query.fields) ? query.fields : [query.fields];

const importAndExportableTypes = searchTypes.filter((type) =>
managementService.isImportAndExportable(type)
typeRegistry.isImportableAndExportable(type)
);

const includedHiddenTypes = importAndExportableTypes.filter((type) =>
typeRegistry.isHidden(type)
);

const client = getClient({ includedHiddenTypes });
const searchFields = new Set<string>();

importAndExportableTypes.forEach((type) => {
const searchField = managementService.getDefaultSearchField(type);
if (searchField) {
Expand All @@ -64,7 +71,7 @@ export const registerFindRoute = (
});

const findResponse = await client.find<any>({
...req.query,
...query,
fields: undefined,
searchFields: [...searchFields],
});
Expand Down
8 changes: 6 additions & 2 deletions src/plugins/saved_objects_management/server/routes/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@ export const registerGetRoute = (
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { type, id } = req.params;
const managementService = await managementServicePromise;
const { client } = context.core.savedObjects;
const { getClient, typeRegistry } = context.core.savedObjects;
const includedHiddenTypes = [type].filter(
(entry) => typeRegistry.isHidden(entry) && typeRegistry.isImportableAndExportable(entry)
);

const { type, id } = req.params;
const client = getClient({ includedHiddenTypes });
const findResponse = await client.get<any>(type, id);

const enhancedSavedObject = injectMetaAttributes(findResponse, managementService);
Expand Down
Loading