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

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Ahmad Bamieh 2021-02-14 19:05:36 +02:00 committed by GitHub
parent 3dbcc2713e
commit c1d1b2b453
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 1453 additions and 70 deletions

View file

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

View file

@ -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> | |

View file

@ -13,8 +13,7 @@ import { SavedObjectsClientContract } from './saved_objects/types';
import {
InternalSavedObjectsServiceStart,
ISavedObjectTypeRegistry,
ISavedObjectsExporter,
ISavedObjectsImporter,
SavedObjectsClientProviderOptions,
} from './saved_objects';
import {
InternalElasticsearchServiceStart,
@ -58,8 +57,6 @@ class CoreSavedObjectsRouteHandlerContext {
) {}
#scopedSavedObjectsClient?: SavedObjectsClientContract;
#typeRegistry?: ISavedObjectTypeRegistry;
#exporter?: ISavedObjectsExporter;
#importer?: ISavedObjectsImporter;
public get client() {
if (this.#scopedSavedObjectsClient == null) {
@ -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 {

View file

@ -49,6 +49,7 @@ import {
SavedObjectsServiceStart,
ISavedObjectsExporter,
ISavedObjectsImporter,
SavedObjectsClientProviderOptions,
} from './saved_objects';
import { CapabilitiesSetup, CapabilitiesStart } from './capabilities';
import { MetricsServiceSetup, MetricsServiceStart } from './metrics';
@ -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;

View file

@ -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(),

View file

@ -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();
const result = await client.delete(type, id, { force });
return res.ok({ body: result });
})
);

View file

@ -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, {
@ -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

View file

@ -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
@ -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,

View file

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

View file

@ -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);
@ -77,6 +81,7 @@ describe('POST /api/saved_objects/_export', () => {
],
},
];
exporter.exportByTypes.mockResolvedValueOnce(createListStream(sortedObjects));
const result = await supertest(httpSetup.server.listener)

View file

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

View file

@ -66,7 +66,7 @@ describe(`POST ${URL}`, () => {
} as any)
);
savedObjectsClient = handlerContext.savedObjects.client;
savedObjectsClient = handlerContext.savedObjects.getClient();
savedObjectsClient.checkConflicts.mockResolvedValue({ errors: [] });
const importer = new SavedObjectsImporter({
@ -74,9 +74,10 @@ describe(`POST ${URL}`, () => {
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();

View file

@ -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';
@ -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();
const client = getClient({ includedHiddenTypes });
const importer = getImporter(client);
try {
const result = await importer.resolveImportErrors({

View file

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

View file

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

View file

@ -33,7 +33,7 @@ export function createUninstallRoute(
client: { callAsCurrentUser },
},
},
savedObjects: { client: savedObjectsClient },
savedObjects: { getClient: getSavedObjectsClient, typeRegistry },
},
},
request,
@ -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)
);

View file

@ -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"

View file

@ -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) {
@ -64,7 +71,7 @@ export const registerFindRoute = (
});
const findResponse = await client.find<any>({
...req.query,
...query,
fields: undefined,
searchFields: [...searchFields],
});

View file

@ -26,10 +26,14 @@ export const registerGetRoute = (
},
},
router.handleLegacyErrors(async (context, req, res) => {
const managementService = await managementServicePromise;
const { client } = context.core.savedObjects;
const { type, id } = req.params;
const managementService = await managementServicePromise;
const { getClient, typeRegistry } = context.core.savedObjects;
const includedHiddenTypes = [type].filter(
(entry) => typeRegistry.isHidden(entry) && typeRegistry.isImportableAndExportable(entry)
);
const client = getClient({ includedHiddenTypes });
const findResponse = await client.get<any>(type, id);
const enhancedSavedObject = injectMetaAttributes(findResponse, managementService);

View file

@ -8,6 +8,7 @@
import { schema } from '@kbn/config-schema';
import { IRouter } from 'src/core/server';
import { chain } from 'lodash';
import { findRelationships } from '../lib';
import { ISavedObjectsManagement } from '../services';
@ -31,12 +32,21 @@ export const registerRelationshipsRoute = (
},
router.handleLegacyErrors(async (context, req, res) => {
const managementService = await managementServicePromise;
const { client } = context.core.savedObjects;
const { getClient, typeRegistry } = context.core.savedObjects;
const { type, id } = req.params;
const { size } = req.query;
const savedObjectTypes = Array.isArray(req.query.savedObjectTypes)
? req.query.savedObjectTypes
: [req.query.savedObjectTypes];
const { size, savedObjectTypes: maybeArraySavedObjectTypes } = req.query;
const savedObjectTypes = Array.isArray(maybeArraySavedObjectTypes)
? maybeArraySavedObjectTypes
: [maybeArraySavedObjectTypes];
const includedHiddenTypes = chain(maybeArraySavedObjectTypes)
.uniq()
.filter(
(entry) => typeRegistry.isHidden(entry) && typeRegistry.isImportableAndExportable(entry)
)
.value();
const client = getClient({ includedHiddenTypes });
const findRelationsResponse = await findRelationships({
type,

View file

@ -8,6 +8,7 @@
import { schema } from '@kbn/config-schema';
import { IRouter, SavedObjectsFindOptions } from 'src/core/server';
import { chain } from 'lodash';
import { findAll } from '../lib';
export const registerScrollForCountRoute = (router: IRouter) => {
@ -30,18 +31,27 @@ export const registerScrollForCountRoute = (router: IRouter) => {
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { client } = context.core.savedObjects;
const { getClient, typeRegistry } = context.core.savedObjects;
const { typesToInclude, searchString, references } = req.body;
const includedHiddenTypes = chain(typesToInclude)
.uniq()
.filter(
(type) => typeRegistry.isHidden(type) && typeRegistry.isImportableAndExportable(type)
)
.value();
const client = getClient({ includedHiddenTypes });
const findOptions: SavedObjectsFindOptions = {
type: req.body.typesToInclude,
type: typesToInclude,
perPage: 1000,
};
if (req.body.searchString) {
findOptions.search = `${req.body.searchString}*`;
if (searchString) {
findOptions.search = `${searchString}*`;
findOptions.searchFields = ['title'];
}
if (req.body.references) {
findOptions.hasReference = req.body.references;
if (references) {
findOptions.hasReference = references;
findOptions.hasReferenceOperator = 'OR';
}
@ -54,7 +64,7 @@ export const registerScrollForCountRoute = (router: IRouter) => {
return accum;
}, {} as Record<string, number>);
for (const type of req.body.typesToInclude) {
for (const type of typesToInclude) {
if (!counts[type]) {
counts[type] = 0;
}

View file

@ -8,6 +8,7 @@
import { schema } from '@kbn/config-schema';
import { IRouter } from 'src/core/server';
import { chain } from 'lodash';
import { findAll } from '../lib';
export const registerScrollForExportRoute = (router: IRouter) => {
@ -21,10 +22,20 @@ export const registerScrollForExportRoute = (router: IRouter) => {
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { client } = context.core.savedObjects;
const { typesToInclude } = req.body;
const { getClient, typeRegistry } = context.core.savedObjects;
const includedHiddenTypes = chain(typesToInclude)
.uniq()
.filter(
(type) => typeRegistry.isHidden(type) && typeRegistry.isImportableAndExportable(type)
)
.value();
const client = getClient({ includedHiddenTypes });
const objects = await findAll(client, {
perPage: 1000,
type: req.body.typesToInclude,
type: typesToInclude,
});
return res.ok({

View file

@ -0,0 +1,29 @@
{
"type": "doc",
"value": {
"index": ".kibana",
"id": "test-hidden-importable-exportable:ff3733a0-9fty-11e7-ahb3-3dcb94193fab",
"source": {
"type": "test-hidden-importable-exportable",
"updated_at": "2021-02-11T18:51:23.794Z",
"test-hidden-importable-exportable": {
"title": "Hidden Saved object type that is importable/exportable."
}
}
}
}
{
"type": "doc",
"value": {
"index": ".kibana",
"id": "test-hidden-non-importable-exportable:op3767a1-9rcg-53u7-jkb3-3dnb74193awc",
"source": {
"type": "test-hidden-non-importable-exportable",
"updated_at": "2021-02-11T18:51:23.794Z",
"test-hidden-non-importable-exportable": {
"title": "Hidden Saved object type that is not importable/exportable."
}
}
}
}

View file

@ -0,0 +1,513 @@
{
"type": "index",
"value": {
"index": ".kibana",
"settings": {
"index": {
"number_of_shards": "1",
"auto_expand_replicas": "0-1",
"number_of_replicas": "0"
}
},
"mappings": {
"dynamic": "strict",
"properties": {
"test-export-transform": {
"properties": {
"title": { "type": "text" },
"enabled": { "type": "boolean" }
}
},
"test-export-add": {
"properties": {
"title": { "type": "text" }
}
},
"test-export-add-dep": {
"properties": {
"title": { "type": "text" }
}
},
"test-export-transform-error": {
"properties": {
"title": { "type": "text" }
}
},
"test-export-invalid-transform": {
"properties": {
"title": { "type": "text" }
}
},
"apm-telemetry": {
"properties": {
"has_any_services": {
"type": "boolean"
},
"services_per_agent": {
"properties": {
"go": {
"type": "long",
"null_value": 0
},
"java": {
"type": "long",
"null_value": 0
},
"js-base": {
"type": "long",
"null_value": 0
},
"nodejs": {
"type": "long",
"null_value": 0
},
"python": {
"type": "long",
"null_value": 0
},
"ruby": {
"type": "long",
"null_value": 0
}
}
}
}
},
"canvas-workpad": {
"dynamic": "false",
"properties": {
"@created": {
"type": "date"
},
"@timestamp": {
"type": "date"
},
"id": {
"type": "text",
"index": false
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
},
"config": {
"dynamic": "true",
"properties": {
"accessibility:disableAnimations": {
"type": "boolean"
},
"buildNum": {
"type": "keyword"
},
"dateFormat:tz": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"defaultIndex": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"telemetry:optIn": {
"type": "boolean"
}
}
},
"dashboard": {
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"map": {
"properties": {
"bounds": {
"type": "geo_shape",
"tree": "quadtree"
},
"description": {
"type": "text"
},
"layerListJSON": {
"type": "text"
},
"mapStateJSON": {
"type": "text"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"graph-workspace": {
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"numLinks": {
"type": "integer"
},
"numVertices": {
"type": "integer"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
},
"wsState": {
"type": "text"
}
}
},
"index-pattern": {
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
},
"type": {
"type": "keyword"
},
"typeMeta": {
"type": "keyword"
}
}
},
"kql-telemetry": {
"properties": {
"optInCount": {
"type": "long"
},
"optOutCount": {
"type": "long"
}
}
},
"migrationVersion": {
"dynamic": "true",
"properties": {
"index-pattern": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"space": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"namespace": {
"type": "keyword"
},
"search": {
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"server": {
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"space": {
"properties": {
"_reserved": {
"type": "boolean"
},
"color": {
"type": "keyword"
},
"description": {
"type": "text"
},
"disabledFeatures": {
"type": "keyword"
},
"initials": {
"type": "keyword"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"spaceId": {
"type": "keyword"
},
"telemetry": {
"properties": {
"enabled": {
"type": "boolean"
}
}
},
"timelion-sheet": {
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
},
"url": {
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 2048
}
}
}
}
},
"visualization": {
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
},
"references": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
},
"type": "nested"
},
"test-hidden-non-importable-exportable": {
"properties": {
"title": {
"type": "text"
}
}
},
"test-hidden-importable-exportable": {
"properties": {
"title": {
"type": "text"
}
}
}
}
}
}
}

View file

@ -315,6 +315,18 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv
})
);
}
async getImportErrorsCount() {
log.debug(`Toggling overwriteAll`);
const errorCountNode = await testSubjects.find('importSavedObjectsErrorsCount');
const errorCountText = await errorCountNode.getVisibleText();
const match = errorCountText.match(/(\d)+/);
if (!match) {
throw Error(`unable to parse error count from text ${errorCountText}`);
}
return +match[1];
}
}
return new SavedObjectsPage();

View file

@ -30,6 +30,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('./test_suites/application_links'),
require.resolve('./test_suites/data_plugin'),
require.resolve('./test_suites/saved_objects_management'),
require.resolve('./test_suites/saved_objects_hidden_type'),
],
services: {
...functionalConfig.get('services'),

View file

@ -0,0 +1,8 @@
{
"id": "savedObjectsHiddenType",
"version": "0.0.1",
"kibanaVersion": "kibana",
"configPath": ["saved_objects_hidden_type"],
"server": true,
"ui": false
}

View file

@ -0,0 +1,14 @@
{
"name": "saved_objects_hidden_type",
"version": "1.0.0",
"main": "target/test/plugin_functional/plugins/saved_objects_hidden_type",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"license": "Apache-2.0",
"scripts": {
"kbn": "node ../../../../scripts/kbn.js",
"build": "rm -rf './target' && ../../../../node_modules/.bin/tsc"
}
}

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { SavedObjectsHiddenTypePlugin } from './plugin';
export const plugin = () => new SavedObjectsHiddenTypePlugin();

View file

@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Plugin, CoreSetup } from 'kibana/server';
export class SavedObjectsHiddenTypePlugin implements Plugin {
public setup({ savedObjects }: CoreSetup, deps: {}) {
// example of a SO type that is hidden and importableAndExportable
savedObjects.registerType({
name: 'test-hidden-importable-exportable',
hidden: true,
namespaceType: 'single',
mappings: {
properties: {
title: { type: 'text' },
},
},
management: {
importableAndExportable: true,
},
});
// example of a SO type that is hidden and not importableAndExportable
savedObjects.registerType({
name: 'test-hidden-non-importable-exportable',
hidden: true,
namespaceType: 'single',
mappings: {
properties: {
title: { type: 'text' },
},
},
management: {
importableAndExportable: false,
},
});
}
public start() {}
public stop() {}
}

View file

@ -0,0 +1,16 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./target",
"skipLibCheck": true
},
"include": [
"index.ts",
"server/**/*.ts",
"../../../../typings/**/*",
],
"exclude": [],
"references": [
{ "path": "../../../../src/core/tsconfig.json" }
]
}

View file

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('delete', () => {
before(() =>
esArchiver.load(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
after(() =>
esArchiver.unload(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
it('should return generic 404 when trying to delete a doc with importableAndExportable types', async () =>
await supertest
.delete(
`/api/saved_objects/test-hidden-importable-exportable/ff3733a0-9fty-11e7-ahb3-3dcb94193fab`
)
.set('kbn-xsrf', 'true')
.expect(404)
.then((resp) => {
expect(resp.body).to.eql({
statusCode: 404,
error: 'Not Found',
message:
'Saved object [test-hidden-importable-exportable/ff3733a0-9fty-11e7-ahb3-3dcb94193fab] not found',
});
}));
it('returns empty response for non importableAndExportable types', async () =>
await supertest
.delete(
`/api/saved_objects/test-hidden-non-importable-exportable/op3767a1-9rcg-53u7-jkb3-3dnb74193awc`
)
.set('kbn-xsrf', 'true')
.expect(404)
.then((resp) => {
expect(resp.body).to.eql({
statusCode: 404,
error: 'Not Found',
message:
'Saved object [test-hidden-non-importable-exportable/op3767a1-9rcg-53u7-jkb3-3dnb74193awc] not found',
});
}));
});
}

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
function ndjsonToObject(input: string): string[] {
return input.split('\n').map((str) => JSON.parse(str));
}
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('export', () => {
before(() =>
esArchiver.load(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
after(() =>
esArchiver.unload(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
it('exports objects with importableAndExportable types', async () =>
await supertest
.post('/api/saved_objects/_export')
.set('kbn-xsrf', 'true')
.send({
type: ['test-hidden-importable-exportable'],
})
.expect(200)
.then((resp) => {
const objects = ndjsonToObject(resp.text);
expect(objects).to.have.length(2);
expect(objects[0]).to.have.property('id', 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab');
expect(objects[0]).to.have.property('type', 'test-hidden-importable-exportable');
}));
it('excludes objects with non importableAndExportable types', async () =>
await supertest
.post('/api/saved_objects/_export')
.set('kbn-xsrf', 'true')
.send({
type: ['test-hidden-non-importable-exportable'],
})
.then((resp) => {
expect(resp.body).to.eql({
statusCode: 400,
error: 'Bad Request',
message:
'Trying to export non-exportable type(s): test-hidden-non-importable-exportable',
});
}));
});
}

View file

@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('find', () => {
before(() =>
esArchiver.load(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
after(() =>
esArchiver.unload(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
it('returns empty response for importableAndExportable types', async () =>
await supertest
.get('/api/saved_objects/_find?type=test-hidden-importable-exportable&fields=title')
.set('kbn-xsrf', 'true')
.expect(200)
.then((resp) => {
expect(resp.body).to.eql({
page: 1,
per_page: 20,
total: 0,
saved_objects: [],
});
}));
it('returns empty response for non importableAndExportable types', async () =>
await supertest
.get('/api/saved_objects/_find?type=test-hidden-non-importable-exportable&fields=title')
.set('kbn-xsrf', 'true')
.expect(200)
.then((resp) => {
expect(resp.body).to.eql({
page: 1,
per_page: 20,
total: 0,
saved_objects: [],
});
}));
});
}

View file

@ -0,0 +1,88 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('import', () => {
before(() =>
esArchiver.load(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
after(() =>
esArchiver.unload(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
it('imports objects with importableAndExportable type', async () => {
const fileBuffer = Buffer.from(
'{"id":"some-id-1","type":"test-hidden-importable-exportable","attributes":{"title":"my title"},"references":[]}',
'utf8'
);
await supertest
.post('/api/saved_objects/_import')
.set('kbn-xsrf', 'true')
.attach('file', fileBuffer, 'export.ndjson')
.expect(200)
.then((resp) => {
expect(resp.body).to.eql({
successCount: 1,
success: true,
warnings: [],
successResults: [
{
type: 'test-hidden-importable-exportable',
id: 'some-id-1',
meta: {
title: 'my title',
},
},
],
});
});
});
it('does not import objects with non importableAndExportable type', async () => {
const fileBuffer = Buffer.from(
'{"id":"some-id-1","type":"test-hidden-non-importable-exportable","attributes":{"title":"my title"},"references":[]}',
'utf8'
);
await supertest
.post('/api/saved_objects/_import')
.set('kbn-xsrf', 'true')
.attach('file', fileBuffer, 'export.ndjson')
.expect(200)
.then((resp) => {
expect(resp.body).to.eql({
successCount: 0,
success: false,
warnings: [],
errors: [
{
id: 'some-id-1',
type: 'test-hidden-non-importable-exportable',
title: 'my title',
meta: {
title: 'my title',
},
error: {
type: 'unsupported_type',
},
},
],
});
});
});
});
}

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
describe('Saved objects with hidden type', function () {
loadTestFile(require.resolve('./import'));
loadTestFile(require.resolve('./export'));
loadTestFile(require.resolve('./resolve_import_errors'));
loadTestFile(require.resolve('./find'));
loadTestFile(require.resolve('./delete'));
loadTestFile(require.resolve('./interface/saved_objects_management'));
});
}

View file

@ -0,0 +1 @@
{"attributes": { "title": "Hidden Saved object type that is importable/exportable." }, "id":"ff3733a0-9fty-11e7-ahb3-3dcb94193fab", "references":[], "type":"test-hidden-importable-exportable", "version":1}

View file

@ -0,0 +1 @@
{"attributes": { "title": "Hidden Saved object type that is not importable/exportable." },"id":"op3767a1-9rcg-53u7-jkb3-3dnb74193awc","references":[],"type":"test-hidden-non-importable-exportable","version":1}

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import path from 'path';
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../../services';
export default function ({ getPageObjects, getService }: PluginFunctionalProviderContext) {
const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects']);
const esArchiver = getService('esArchiver');
const fixturePaths = {
hiddenImportable: path.join(__dirname, 'exports', '_import_hidden_importable.ndjson'),
hiddenNonImportable: path.join(__dirname, 'exports', '_import_hidden_non_importable.ndjson'),
};
describe('Saved objects management Interface', () => {
before(() => esArchiver.emptyKibanaIndex());
beforeEach(async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
});
describe('importable/exportable hidden type', () => {
it('imports objects successfully', async () => {
await PageObjects.savedObjects.importFile(fixturePaths.hiddenImportable);
await PageObjects.savedObjects.checkImportSucceeded();
});
it('shows test-hidden-importable-exportable in table', async () => {
await PageObjects.savedObjects.searchForObject('type:(test-hidden-importable-exportable)');
const results = await PageObjects.savedObjects.getTableSummary();
expect(results.length).to.be(1);
const { title } = results[0];
expect(title).to.be(
'test-hidden-importable-exportable [id=ff3733a0-9fty-11e7-ahb3-3dcb94193fab]'
);
});
});
describe('non-importable/exportable hidden type', () => {
it('fails to import object', async () => {
await PageObjects.savedObjects.importFile(fixturePaths.hiddenNonImportable);
await PageObjects.savedObjects.checkImportSucceeded();
const errorsCount = await PageObjects.savedObjects.getImportErrorsCount();
expect(errorsCount).to.be(1);
});
});
});
}

View file

@ -0,0 +1,112 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('export', () => {
before(() =>
esArchiver.load(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
after(() =>
esArchiver.unload(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
it('resolves objects with importableAndExportable types', async () => {
const fileBuffer = Buffer.from(
'{"id":"ff3733a0-9fty-11e7-ahb3-3dcb94193fab","type":"test-hidden-importable-exportable","attributes":{"title":"new title!"},"references":[]}',
'utf8'
);
await supertest
.post('/api/saved_objects/_resolve_import_errors')
.set('kbn-xsrf', 'true')
.field(
'retries',
JSON.stringify([
{
id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab',
type: 'test-hidden-importable-exportable',
overwrite: true,
},
])
)
.attach('file', fileBuffer, 'import.ndjson')
.expect(200)
.then((resp) => {
expect(resp.body).to.eql({
successCount: 1,
success: true,
warnings: [],
successResults: [
{
type: 'test-hidden-importable-exportable',
id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab',
meta: {
title: 'new title!',
},
overwrite: true,
},
],
});
});
});
it('rejects objects with non importableAndExportable types', async () => {
const fileBuffer = Buffer.from(
'{"id":"op3767a1-9rcg-53u7-jkb3-3dnb74193awc","type":"test-hidden-non-importable-exportable","attributes":{"title":"new title!"},"references":[]}',
'utf8'
);
await supertest
.post('/api/saved_objects/_resolve_import_errors')
.set('kbn-xsrf', 'true')
.field(
'retries',
JSON.stringify([
{
id: 'op3767a1-9rcg-53u7-jkb3-3dnb74193awc',
type: 'test-hidden-non-importable-exportable',
overwrite: true,
},
])
)
.attach('file', fileBuffer, 'import.ndjson')
.expect(200)
.then((resp) => {
expect(resp.body).to.eql({
successCount: 0,
success: false,
warnings: [],
errors: [
{
id: 'op3767a1-9rcg-53u7-jkb3-3dnb74193awc',
type: 'test-hidden-non-importable-exportable',
title: 'new title!',
meta: {
title: 'new title!',
},
error: {
type: 'unsupported_type',
},
overwrite: true,
},
],
});
});
});
});
}

View file

@ -0,0 +1,77 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('find', () => {
describe('saved objects with hidden type', () => {
before(() =>
esArchiver.load(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
after(() =>
esArchiver.unload(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
it('returns saved objects with importableAndExportable types', async () =>
await supertest
.get(
'/api/kibana/management/saved_objects/_find?type=test-hidden-importable-exportable&fields=title'
)
.set('kbn-xsrf', 'true')
.expect(200)
.then((resp) => {
expect(resp.body).to.eql({
page: 1,
per_page: 20,
total: 1,
saved_objects: [
{
type: 'test-hidden-importable-exportable',
id: 'ff3733a0-9fty-11e7-ahb3-3dcb94193fab',
attributes: {
title: 'Hidden Saved object type that is importable/exportable.',
},
references: [],
updated_at: '2021-02-11T18:51:23.794Z',
version: 'WzIsMl0=',
namespaces: ['default'],
score: 0,
meta: {
namespaceType: 'single',
},
},
],
});
}));
it('returns empty response for non importableAndExportable types', async () =>
await supertest
.get(
'/api/kibana/management/saved_objects/_find?type=test-hidden-non-importable-exportable'
)
.set('kbn-xsrf', 'true')
.expect(200)
.then((resp) => {
expect(resp.body).to.eql({
page: 1,
per_page: 20,
total: 0,
saved_objects: [],
});
}));
});
});
}

View file

@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('get', () => {
describe('saved objects with hidden type', () => {
before(() =>
esArchiver.load(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
after(() =>
esArchiver.unload(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
const hiddenTypeExportableImportable =
'test-hidden-importable-exportable/ff3733a0-9fty-11e7-ahb3-3dcb94193fab';
const hiddenTypeNonExportableImportable =
'test-hidden-non-importable-exportable/op3767a1-9rcg-53u7-jkb3-3dnb74193awc';
it('should return 200 for hidden types that are importableAndExportable', async () =>
await supertest
.get(`/api/kibana/management/saved_objects/${hiddenTypeExportableImportable}`)
.set('kbn-xsrf', 'true')
.expect(200)
.then((resp) => {
const { body } = resp;
const { type, id, meta } = body;
expect(type).to.eql('test-hidden-importable-exportable');
expect(id).to.eql('ff3733a0-9fty-11e7-ahb3-3dcb94193fab');
expect(meta).to.not.equal(undefined);
}));
it('should return 404 for hidden types that are not importableAndExportable', async () =>
await supertest
.get(`/api/kibana/management/saved_objects/${hiddenTypeNonExportableImportable}`)
.set('kbn-xsrf', 'true')
.expect(404));
});
});
}

View file

@ -10,6 +10,9 @@ import { PluginFunctionalProviderContext } from '../../services';
export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
describe('Saved Objects Management', function () {
loadTestFile(require.resolve('./find'));
loadTestFile(require.resolve('./scroll_count'));
loadTestFile(require.resolve('./get'));
loadTestFile(require.resolve('./export_transform'));
loadTestFile(require.resolve('./import_warnings'));
});

View file

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import expect from '@kbn/expect';
import { PluginFunctionalProviderContext } from '../../services';
export default function ({ getService }: PluginFunctionalProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const apiUrl = '/api/kibana/management/saved_objects/scroll/counts';
describe('scroll_count', () => {
describe('saved objects with hidden type', () => {
before(() =>
esArchiver.load(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
after(() =>
esArchiver.unload(
'../functional/fixtures/es_archiver/saved_objects_management/hidden_saved_objects'
)
);
it('only counts hidden types that are importableAndExportable', async () => {
const res = await supertest
.post(apiUrl)
.set('kbn-xsrf', 'true')
.send({
typesToInclude: [
'test-hidden-non-importable-exportable',
'test-hidden-importable-exportable',
],
})
.expect(200);
expect(res.body).to.eql({
'test-hidden-importable-exportable': 1,
'test-hidden-non-importable-exportable': 0,
});
});
});
});
}