Skip to content

Commit

Permalink
[7.x] [Saved Objects] Allow exporting and importing hidden types (#90178
Browse files Browse the repository at this point in the history
) (#91351)

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
Bamieh and kibanamachine authored Feb 15, 2021
1 parent 9a20358 commit ccaded1
Show file tree
Hide file tree
Showing 44 changed files with 1,454 additions and 69 deletions.
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 @@ -31,11 +31,13 @@ export const registerDeleteRoute = (router: IRouter, { coreUsageData }: RouteDep
router.handleLegacyErrors(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();
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 = (
},
router.handleLegacyErrors(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 });
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 = (
},
router.handleLegacyErrors(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 @@ -92,7 +93,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();

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));

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
)}`;
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));

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

0 comments on commit ccaded1

Please sign in to comment.