mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Fix sample data for share-capable objects (#116378)
This commit is contained in:
parent
f2b9acf67b
commit
61ab4a3c59
30 changed files with 743 additions and 278 deletions
|
@ -67,7 +67,6 @@ export class SampleDataSetCards extends React.Component {
|
|||
sampleDataSets: sampleDataSets.sort((a, b) => {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
}),
|
||||
processingStatus: {},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -82,6 +81,7 @@ export class SampleDataSetCards extends React.Component {
|
|||
|
||||
try {
|
||||
await installSampleDataSet(id, targetSampleDataSet.defaultIndex);
|
||||
await this.loadSampleDataSets(); // reload the list of sample data sets
|
||||
} catch (fetchError) {
|
||||
if (this._isMounted) {
|
||||
this.setState((prevState) => ({
|
||||
|
|
|
@ -19,7 +19,9 @@ export type {
|
|||
export { FeatureCatalogueCategory } from './services';
|
||||
|
||||
export type {
|
||||
AddDataTab,
|
||||
FeatureCatalogueEntry,
|
||||
FeatureCatalogueRegistry,
|
||||
FeatureCatalogueSolution,
|
||||
Environment,
|
||||
TutorialVariables,
|
||||
|
|
|
@ -7,8 +7,21 @@
|
|||
*/
|
||||
|
||||
export type { HomeServerPluginSetup, HomeServerPluginStart } from './plugin';
|
||||
export type { TutorialProvider } from './services';
|
||||
export type { SampleDatasetProvider, SampleDataRegistrySetup } from './services';
|
||||
export { EmbeddableTypes, TutorialsCategory } from './services';
|
||||
export type {
|
||||
AppLinkData,
|
||||
ArtifactsSchema,
|
||||
TutorialProvider,
|
||||
TutorialSchema,
|
||||
InstructionSetSchema,
|
||||
InstructionsSchema,
|
||||
TutorialContext,
|
||||
SampleDatasetProvider,
|
||||
SampleDataRegistrySetup,
|
||||
SampleDatasetDashboardPanel,
|
||||
SampleObject,
|
||||
ScopedTutorialContextFactory,
|
||||
} from './services';
|
||||
import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server';
|
||||
import { HomeServerPlugin } from './plugin';
|
||||
import { configSchema, ConfigSchema } from '../config';
|
||||
|
@ -23,10 +36,3 @@ export const config: PluginConfigDescriptor<ConfigSchema> = {
|
|||
export const plugin = (initContext: PluginInitializerContext) => new HomeServerPlugin(initContext);
|
||||
|
||||
export { INSTRUCTION_VARIANT } from '../common/instruction_variant';
|
||||
export { TutorialsCategory } from './services/tutorials';
|
||||
export type {
|
||||
ArtifactsSchema,
|
||||
TutorialSchema,
|
||||
InstructionSetSchema,
|
||||
InstructionsSchema,
|
||||
} from './services/tutorials';
|
||||
|
|
|
@ -9,10 +9,9 @@
|
|||
// provided to other plugins as APIs
|
||||
// should model the plugin lifecycle
|
||||
|
||||
export { TutorialsRegistry } from './tutorials';
|
||||
export type { TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials';
|
||||
export { TutorialsRegistry, TutorialsCategory } from './tutorials';
|
||||
|
||||
export { TutorialsCategory } from './tutorials';
|
||||
export type { TutorialsRegistrySetup, TutorialsRegistryStart } from './tutorials';
|
||||
|
||||
export type {
|
||||
InstructionSetSchema,
|
||||
|
@ -22,12 +21,19 @@ export type {
|
|||
ArtifactsSchema,
|
||||
TutorialSchema,
|
||||
TutorialProvider,
|
||||
TutorialContext,
|
||||
TutorialContextFactory,
|
||||
ScopedTutorialContextFactory,
|
||||
} from './tutorials';
|
||||
|
||||
export { SampleDataRegistry } from './sample_data';
|
||||
export { EmbeddableTypes, SampleDataRegistry } from './sample_data';
|
||||
|
||||
export type { SampleDataRegistrySetup, SampleDataRegistryStart } from './sample_data';
|
||||
|
||||
export type { SampleDatasetSchema, SampleDatasetProvider } from './sample_data';
|
||||
export type {
|
||||
AppLinkData,
|
||||
SampleDataRegistrySetup,
|
||||
SampleDataRegistryStart,
|
||||
SampleDatasetDashboardPanel,
|
||||
SampleDatasetProvider,
|
||||
SampleDatasetSchema,
|
||||
SampleObject,
|
||||
} from './sample_data';
|
||||
|
|
|
@ -10,7 +10,7 @@ import path from 'path';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { getSavedObjects } from './saved_objects';
|
||||
import { fieldMappings } from './field_mappings';
|
||||
import { SampleDatasetSchema, AppLinkSchema } from '../../lib/sample_dataset_registry_types';
|
||||
import { SampleDatasetSchema } from '../../lib/sample_dataset_registry_types';
|
||||
|
||||
const ecommerceName = i18n.translate('home.sampleData.ecommerceSpecTitle', {
|
||||
defaultMessage: 'Sample eCommerce orders',
|
||||
|
@ -18,7 +18,6 @@ const ecommerceName = i18n.translate('home.sampleData.ecommerceSpecTitle', {
|
|||
const ecommerceDescription = i18n.translate('home.sampleData.ecommerceSpecDescription', {
|
||||
defaultMessage: 'Sample data, visualizations, and dashboards for tracking eCommerce orders.',
|
||||
});
|
||||
const initialAppLinks = [] as AppLinkSchema[];
|
||||
|
||||
export const ecommerceSpecProvider = function (): SampleDatasetSchema {
|
||||
return {
|
||||
|
@ -28,7 +27,6 @@ export const ecommerceSpecProvider = function (): SampleDatasetSchema {
|
|||
previewImagePath: '/plugins/home/assets/sample_data_resources/ecommerce/dashboard.png',
|
||||
darkPreviewImagePath: '/plugins/home/assets/sample_data_resources/ecommerce/dashboard_dark.png',
|
||||
overviewDashboard: '722b74f0-b882-11e8-a6d9-e546fe2bba5f',
|
||||
appLinks: initialAppLinks,
|
||||
defaultIndex: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
savedObjects: getSavedObjects(),
|
||||
dataIndices: [
|
||||
|
|
|
@ -140,9 +140,10 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
{
|
||||
id: '9c6f83f0-bb4d-11e8-9c84-77068524bcab',
|
||||
type: 'visualization',
|
||||
updated_at: '2018-10-01T15:13:03.270Z',
|
||||
updated_at: '2021-10-28T15:07:24.077Z',
|
||||
version: '1',
|
||||
migrationVersion: {},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
migrationVersion: { visualization: '8.0.0' },
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.ecommerceSpec.salesCountMapTitle', {
|
||||
defaultMessage: '[eCommerce] Sales Count Map',
|
||||
|
@ -154,10 +155,16 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
version: 1,
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON:
|
||||
'{"index":"ff959d40-b880-11e8-a6d9-e546fe2bba5f","query":{"query":"","language":"kuery"},"filter":[]}',
|
||||
'{"query":{"query":"","language":"kuery"},"filter":[],"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}',
|
||||
},
|
||||
},
|
||||
references: [],
|
||||
references: [
|
||||
{
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
attributes: {
|
||||
|
|
|
@ -10,7 +10,7 @@ import path from 'path';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { getSavedObjects } from './saved_objects';
|
||||
import { fieldMappings } from './field_mappings';
|
||||
import { SampleDatasetSchema, AppLinkSchema } from '../../lib/sample_dataset_registry_types';
|
||||
import { SampleDatasetSchema } from '../../lib/sample_dataset_registry_types';
|
||||
|
||||
const flightsName = i18n.translate('home.sampleData.flightsSpecTitle', {
|
||||
defaultMessage: 'Sample flight data',
|
||||
|
@ -18,7 +18,6 @@ const flightsName = i18n.translate('home.sampleData.flightsSpecTitle', {
|
|||
const flightsDescription = i18n.translate('home.sampleData.flightsSpecDescription', {
|
||||
defaultMessage: 'Sample data, visualizations, and dashboards for monitoring flight routes.',
|
||||
});
|
||||
const initialAppLinks = [] as AppLinkSchema[];
|
||||
|
||||
export const flightsSpecProvider = function (): SampleDatasetSchema {
|
||||
return {
|
||||
|
@ -28,7 +27,6 @@ export const flightsSpecProvider = function (): SampleDatasetSchema {
|
|||
previewImagePath: '/plugins/home/assets/sample_data_resources/flights/dashboard.png',
|
||||
darkPreviewImagePath: '/plugins/home/assets/sample_data_resources/flights/dashboard_dark.png',
|
||||
overviewDashboard: '7adfa750-4c81-11e8-b3d7-01146121b73d',
|
||||
appLinks: initialAppLinks,
|
||||
defaultIndex: 'd3d7af60-4c81-11e8-b3d7-01146121b73d',
|
||||
savedObjects: getSavedObjects(),
|
||||
dataIndices: [
|
||||
|
|
|
@ -10,7 +10,7 @@ import path from 'path';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { getSavedObjects } from './saved_objects';
|
||||
import { fieldMappings } from './field_mappings';
|
||||
import { SampleDatasetSchema, AppLinkSchema } from '../../lib/sample_dataset_registry_types';
|
||||
import { SampleDatasetSchema } from '../../lib/sample_dataset_registry_types';
|
||||
|
||||
const logsName = i18n.translate('home.sampleData.logsSpecTitle', {
|
||||
defaultMessage: 'Sample web logs',
|
||||
|
@ -18,7 +18,6 @@ const logsName = i18n.translate('home.sampleData.logsSpecTitle', {
|
|||
const logsDescription = i18n.translate('home.sampleData.logsSpecDescription', {
|
||||
defaultMessage: 'Sample data, visualizations, and dashboards for monitoring web logs.',
|
||||
});
|
||||
const initialAppLinks = [] as AppLinkSchema[];
|
||||
|
||||
export const GLOBE_ICON_PATH = '/plugins/home/assets/sample_data_resources/logs/icon.svg';
|
||||
export const logsSpecProvider = function (): SampleDatasetSchema {
|
||||
|
@ -29,7 +28,6 @@ export const logsSpecProvider = function (): SampleDatasetSchema {
|
|||
previewImagePath: '/plugins/home/assets/sample_data_resources/logs/dashboard.png',
|
||||
darkPreviewImagePath: '/plugins/home/assets/sample_data_resources/logs/dashboard_dark.png',
|
||||
overviewDashboard: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b',
|
||||
appLinks: initialAppLinks,
|
||||
defaultIndex: '90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||
savedObjects: getSavedObjects(),
|
||||
dataIndices: [
|
||||
|
|
|
@ -14,9 +14,10 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
{
|
||||
id: '06cf9c40-9ee8-11e7-8711-e7a007dcef99',
|
||||
type: 'visualization',
|
||||
updated_at: '2018-08-29T13:22:17.617Z',
|
||||
updated_at: '2021-10-28T15:07:36.622Z',
|
||||
version: '1',
|
||||
migrationVersion: {},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
migrationVersion: { visualization: '8.0.0' },
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.visitorsMapTitle', {
|
||||
defaultMessage: '[Logs] Visitors Map',
|
||||
|
@ -28,10 +29,16 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
version: 1,
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON:
|
||||
'{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","filter":[],"query":{"query":"","language":"kuery"}}',
|
||||
'{"filter":[],"query":{"query":"","language":"kuery"},"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}',
|
||||
},
|
||||
},
|
||||
references: [],
|
||||
references: [
|
||||
{
|
||||
id: '90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'cb099a20-ea66-11eb-9425-113343a037e3',
|
||||
|
@ -88,25 +95,32 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
{
|
||||
id: '69a34b00-9ee8-11e7-8711-e7a007dcef99',
|
||||
type: 'visualization',
|
||||
updated_at: '2018-08-29T13:24:46.136Z',
|
||||
updated_at: '2021-10-28T14:38:21.435Z',
|
||||
version: '2',
|
||||
migrationVersion: {},
|
||||
coreMigrationVersion: '8.0.0',
|
||||
migrationVersion: { visualization: '8.0.0' },
|
||||
attributes: {
|
||||
title: i18n.translate('home.sampleData.logsSpec.goalsTitle', {
|
||||
defaultMessage: '[Logs] Goals',
|
||||
}),
|
||||
visState:
|
||||
'{"title":"[Logs] Goals","type":"gauge","params":{"type":"gauge","addTooltip":true,"addLegend":false,"gauge":{"verticalSplit":false,"extendRange":true,"percentageMode":false,"gaugeType":"Arc","gaugeStyle":"Full","backStyle":"Full","orientation":"vertical","colorSchema":"Green to Red","gaugeColorMode":"Labels","colorsRange":[{"from":0,"to":500},{"from":500,"to":1000},{"from":1000,"to":1500}],"invertColors":true,"labels":{"show":false,"color":"black"},"scale":{"show":true,"labels":false,"color":"#333"},"type":"meter","style":{"bgWidth":0.9,"width":0.9,"mask":false,"bgMask":false,"maskBars":50,"bgFill":"#eee","bgColor":false,"subText":"visitors","fontSize":60,"labelColor":true}},"isDisplayWarning":false},"aggs":[{"id":"1","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}}]}',
|
||||
'{"title":"[Logs] Goals","type":"gauge","params":{"type":"gauge","addTooltip":true,"addLegend":false,"gauge":{"extendRange":true,"percentageMode":false,"gaugeType":"Arc","gaugeStyle":"Full","backStyle":"Full","orientation":"vertical","colorSchema":"Green to Red","gaugeColorMode":"Labels","colorsRange":[{"from":0,"to":500},{"from":500,"to":1000},{"from":1000,"to":1500}],"invertColors":true,"labels":{"show":false,"color":"black"},"scale":{"show":true,"labels":false,"color":"#333"},"type":"meter","style":{"bgWidth":0.9,"width":0.9,"mask":false,"bgMask":false,"maskBars":50,"bgFill":"#eee","bgColor":false,"subText":"visitors","fontSize":60,"labelColor":true},"alignment":"horizontal"},"isDisplayWarning":false},"aggs":[{"id":"1","enabled":true,"type":"cardinality","schema":"metric","params":{"field":"clientip","customLabel":"Unique Visitors"}}]}',
|
||||
uiStateJSON:
|
||||
'{"vis":{"defaultColors":{"0 - 500":"rgb(165,0,38)","500 - 1000":"rgb(255,255,190)","1000 - 1500":"rgb(0,104,55)"},"colors":{"75 - 100":"#629E51","50 - 75":"#EAB839","0 - 50":"#E24D42","0 - 100":"#E24D42","200 - 300":"#7EB26D","500 - 1000":"#E5AC0E","0 - 500":"#E24D42","1000 - 1500":"#7EB26D"},"legendOpen":true}}',
|
||||
description: '',
|
||||
version: 1,
|
||||
kibanaSavedObjectMeta: {
|
||||
searchSourceJSON:
|
||||
'{"index":"90943e30-9a47-11e8-b64d-95841ca0b247","filter":[],"query":{"query":"","language":"kuery"}}',
|
||||
'{"filter":[],"query":{"query":"","language":"kuery"},"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index"}',
|
||||
},
|
||||
},
|
||||
references: [],
|
||||
references: [
|
||||
{
|
||||
id: '90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
|
||||
type: 'index-pattern',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '7cbd2350-2223-11e8-b802-5bcf64c2cfb4',
|
||||
|
@ -366,13 +380,13 @@ export const getSavedObjects = (): SavedObject[] => [
|
|||
{
|
||||
id: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b',
|
||||
type: 'dashboard',
|
||||
updated_at: '2021-07-21T21:43:43.870Z',
|
||||
updated_at: '2021-10-28T15:07:36.622Z',
|
||||
version: '3',
|
||||
references: [
|
||||
{
|
||||
id: '06cf9c40-9ee8-11e7-8711-e7a007dcef99',
|
||||
name: '4:panel_4',
|
||||
type: 'map',
|
||||
type: 'visualization',
|
||||
},
|
||||
{
|
||||
id: '4eb6e500-e1c7-11e7-b6d5-4dc382ef7f5b',
|
||||
|
|
|
@ -10,7 +10,12 @@ export { SampleDataRegistry } from './sample_data_registry';
|
|||
|
||||
export type { SampleDataRegistrySetup, SampleDataRegistryStart } from './sample_data_registry';
|
||||
|
||||
export { EmbeddableTypes } from './lib/sample_dataset_registry_types';
|
||||
|
||||
export type {
|
||||
SampleDatasetSchema,
|
||||
AppLinkData,
|
||||
SampleDatasetDashboardPanel,
|
||||
SampleDatasetProvider,
|
||||
SampleDatasetSchema,
|
||||
SampleObject,
|
||||
} from './lib/sample_dataset_registry_types';
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const mockBuildNode = jest.fn();
|
||||
|
||||
jest.mock('@kbn/es-query', () => {
|
||||
return {
|
||||
nodeTypes: {
|
||||
function: {
|
||||
buildNode: mockBuildNode,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* 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 { mockBuildNode } from './find_sample_objects.test.mock';
|
||||
|
||||
import type { SavedObject, SavedObjectsFindResponse } from 'src/core/server';
|
||||
import { savedObjectsClientMock, loggingSystemMock } from 'src/core/server/mocks';
|
||||
import { findSampleObjects } from './find_sample_objects';
|
||||
|
||||
describe('findSampleObjects', () => {
|
||||
function setup() {
|
||||
const mockClient = savedObjectsClientMock.create();
|
||||
const mockLogger = loggingSystemMock.createLogger();
|
||||
return {
|
||||
client: mockClient,
|
||||
logger: mockLogger,
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockBuildNode.mockReset();
|
||||
});
|
||||
|
||||
it('searches for objects and returns expected results', async () => {
|
||||
const { client, logger } = setup();
|
||||
const obj1 = { type: 'obj-type-1', id: 'obj-id-1' };
|
||||
const obj2 = { type: 'obj-type-2', id: 'obj-id-2' };
|
||||
const obj3 = { type: 'obj-type-3', id: 'obj-id-3' };
|
||||
const obj4 = { type: 'obj-type-3', id: 'obj-id-4' };
|
||||
const objects = [obj1, obj2, obj3, obj4];
|
||||
const params = { client, logger, objects };
|
||||
|
||||
client.bulkGet.mockResolvedValue({
|
||||
saved_objects: [
|
||||
obj1, // bulkGet success for obj1
|
||||
{ ...obj2, error: { statusCode: 403 } }, // bulkGet failure - will not attempt to find by originId since the error is not 404
|
||||
{ ...obj3, error: { statusCode: 404 } }, // bulkGet failure - will attempt to find by originId since the error is 404
|
||||
{ ...obj4, error: { statusCode: 404 } }, // bulkGet failure - will attempt to find by originId since the error is 404
|
||||
] as SavedObject[],
|
||||
});
|
||||
client.find.mockResolvedValue({
|
||||
saved_objects: [{ type: obj4.type, id: 'obj-id-x', originId: obj4.id }], // find success for obj4
|
||||
total: 1,
|
||||
} as SavedObjectsFindResponse);
|
||||
const result = await findSampleObjects(params);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ ...obj1, foundObjectId: obj1.id },
|
||||
{ ...obj2, foundObjectId: undefined },
|
||||
{ ...obj3, foundObjectId: undefined },
|
||||
{ ...obj4, foundObjectId: 'obj-id-x' },
|
||||
]);
|
||||
expect(client.bulkGet).toHaveBeenCalledWith(objects);
|
||||
expect(mockBuildNode).toHaveBeenCalledTimes(3);
|
||||
expect(mockBuildNode).toHaveBeenNthCalledWith(1, 'is', `${obj3.type}.originId`, obj3.id);
|
||||
expect(mockBuildNode).toHaveBeenNthCalledWith(2, 'is', `${obj4.type}.originId`, obj4.id);
|
||||
expect(mockBuildNode).toHaveBeenNthCalledWith(3, 'or', expect.any(Array));
|
||||
expect(client.find).toHaveBeenCalledWith(expect.objectContaining({ type: ['obj-type-3'] })); // obj3 and obj4 have the same type; the type param is deduplicated
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips find if there are no objects left to search for', async () => {
|
||||
const { client, logger } = setup();
|
||||
const obj1 = { type: 'obj-type-1', id: 'obj-id-1' };
|
||||
const obj2 = { type: 'obj-type-2', id: 'obj-id-2' };
|
||||
const objects = [obj1, obj2];
|
||||
const params = { client, logger, objects };
|
||||
|
||||
client.bulkGet.mockResolvedValue({
|
||||
saved_objects: [
|
||||
obj1, // bulkGet success for obj1
|
||||
{ ...obj2, error: { statusCode: 403 } }, // bulkGet failure - will not attempt to find by originId since the error is not 404
|
||||
] as SavedObject[],
|
||||
});
|
||||
const result = await findSampleObjects(params);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ ...obj1, foundObjectId: obj1.id },
|
||||
{ ...obj2, foundObjectId: undefined },
|
||||
]);
|
||||
expect(client.bulkGet).toHaveBeenCalledWith(objects);
|
||||
expect(mockBuildNode).not.toHaveBeenCalled();
|
||||
expect(client.find).not.toHaveBeenCalled();
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('logs expected warnings', async () => {
|
||||
const { client, logger } = setup();
|
||||
const obj1 = { type: 'obj-type-1', id: 'obj-id-1' };
|
||||
const objects = [obj1];
|
||||
const params = { client, logger, objects };
|
||||
|
||||
client.bulkGet.mockResolvedValue({
|
||||
saved_objects: [
|
||||
{ ...obj1, error: { statusCode: 404 } }, // bulkGet failure - will attempt to find by originId since the error is 404
|
||||
] as SavedObject[],
|
||||
});
|
||||
client.find.mockResolvedValue({
|
||||
saved_objects: [
|
||||
{ type: obj1.type, id: 'obj-id-x', originId: obj1.id }, // find success for obj4
|
||||
{ type: obj1.type, id: 'obj-id-y', originId: obj1.id }, // find success for obj4
|
||||
],
|
||||
total: 10001,
|
||||
} as SavedObjectsFindResponse);
|
||||
const result = await findSampleObjects(params);
|
||||
expect(result).toEqual([{ ...obj1, foundObjectId: 'obj-id-x' }]); // obj-id-y is ignored
|
||||
expect(client.bulkGet).toHaveBeenCalledWith(objects);
|
||||
expect(mockBuildNode).toHaveBeenCalledTimes(2);
|
||||
expect(mockBuildNode).toHaveBeenNthCalledWith(1, 'is', `${obj1.type}.originId`, obj1.id);
|
||||
expect(mockBuildNode).toHaveBeenNthCalledWith(2, 'or', expect.any(Array));
|
||||
expect(client.find).toHaveBeenCalledWith(expect.objectContaining({ type: ['obj-type-1'] }));
|
||||
expect(logger.warn).toHaveBeenCalledTimes(2);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
'findSampleObjects got 10001 results, only using the first 10000'
|
||||
);
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
'Found two sample objects with the same origin "obj-id-1" (previously found "obj-id-x", ignoring "obj-id-y")'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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 * as esKuery from '@kbn/es-query';
|
||||
import type { Logger, SavedObjectsClientContract } from 'src/core/server';
|
||||
|
||||
const MAX_OBJECTS_TO_FIND = 10000; // we only expect up to a few dozen, search for 10k to be safe; anything over this is ignored
|
||||
|
||||
export interface FindSampleObjectsParams {
|
||||
client: SavedObjectsClientContract;
|
||||
logger: Logger;
|
||||
objects: SampleObject[];
|
||||
}
|
||||
|
||||
export interface SampleObject {
|
||||
type: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface FindSampleObjectsResponseObject {
|
||||
type: string;
|
||||
id: string;
|
||||
/** Contains a string if this sample data object was found, or undefined if it was not. */
|
||||
foundObjectId: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of objects in a sample dataset, this function attempts to find if those objects exist in the current space.
|
||||
* It attempts to find objects with an origin of the sample data (e.g., matching `id` or `originId`).
|
||||
*/
|
||||
export async function findSampleObjects({ client, logger, objects }: FindSampleObjectsParams) {
|
||||
const bulkGetResponse = await client.bulkGet(objects);
|
||||
|
||||
let resultsMap = new Map<string, string>();
|
||||
const objectsToFind: SampleObject[] = [];
|
||||
objects.forEach((object, i) => {
|
||||
const bulkGetResult = bulkGetResponse.saved_objects[i];
|
||||
if (!bulkGetResult.error) {
|
||||
const { type, id } = object;
|
||||
const key = getObjKey(type, id);
|
||||
resultsMap.set(key, id);
|
||||
} else if (bulkGetResult.error.statusCode === 404) {
|
||||
objectsToFind.push(object);
|
||||
}
|
||||
});
|
||||
|
||||
if (objectsToFind.length > 0) {
|
||||
const options = {
|
||||
type: getUniqueTypes(objectsToFind),
|
||||
filter: createKueryFilter(objectsToFind),
|
||||
fields: ['title'], // we don't want to return all source fields, so we have to specify at least one source field
|
||||
perPage: MAX_OBJECTS_TO_FIND,
|
||||
};
|
||||
const findResponse = await client.find(options);
|
||||
if (findResponse.total > MAX_OBJECTS_TO_FIND) {
|
||||
// As of this writing, it is not possible to encounter this scenario when using Kibana import or copy-to-space, because at most one
|
||||
// object can exist in a given space. However, as of today, when objects are shareable you _could_ get Kibana into a state where
|
||||
// multiple objects of the same origin exist in the same space.
|
||||
// #116677 describes solutions to fully mitigate this edge case in the future.
|
||||
logger.warn(
|
||||
`findSampleObjects got ${findResponse.total} results, only using the first ${MAX_OBJECTS_TO_FIND}`
|
||||
);
|
||||
}
|
||||
resultsMap = findResponse.saved_objects.reduce((acc, { type, id, originId }) => {
|
||||
const key = getObjKey(type, originId!);
|
||||
const existing = acc.get(key);
|
||||
if (existing) {
|
||||
// As of this writing, it is not possible to encounter this scenario when using Kibana import or copy-to-space, because at most one
|
||||
// object can exist in a given space. However, as of today, when objects are shareable you _could_ get Kibana into a state where
|
||||
// multiple objects of the same origin exist in the same space.
|
||||
// #116677 describes solutions to fully mitigate this edge case in the future.
|
||||
logger.warn(
|
||||
`Found two sample objects with the same origin "${originId}" (previously found "${existing}", ignoring "${id}")`
|
||||
);
|
||||
return acc;
|
||||
}
|
||||
return acc.set(key, id);
|
||||
}, resultsMap);
|
||||
}
|
||||
|
||||
return objects.map<FindSampleObjectsResponseObject>(({ type, id }) => {
|
||||
const key = getObjKey(type, id);
|
||||
return { type, id, foundObjectId: resultsMap.get(key) };
|
||||
});
|
||||
}
|
||||
|
||||
function getUniqueTypes(objects: SampleObject[]) {
|
||||
return [...new Set(objects.map(({ type }) => type))];
|
||||
}
|
||||
|
||||
function createKueryFilter(objects: SampleObject[]) {
|
||||
const { buildNode } = esKuery.nodeTypes.function;
|
||||
const kueryNodes = objects.map(({ type, id }) => buildNode('is', `${type}.originId`, id)); // the repository converts this node into "and (type is ..., originId is ...)"
|
||||
return buildNode('or', kueryNodes);
|
||||
}
|
||||
|
||||
function getObjKey(type: string, id: string) {
|
||||
return `${type}:${id}`;
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import type { SampleDatasetSchema } from './sample_dataset_schema';
|
||||
export type { SampleDatasetSchema, AppLinkSchema, DataIndexSchema } from './sample_dataset_schema';
|
||||
export type { SampleDatasetSchema, DataIndexSchema } from './sample_dataset_schema';
|
||||
|
||||
export enum DatasetStatusTypes {
|
||||
NOT_INSTALLED = 'not_installed',
|
||||
|
@ -28,3 +28,34 @@ export enum EmbeddableTypes {
|
|||
VISUALIZE_EMBEDDABLE_TYPE = 'visualization',
|
||||
}
|
||||
export type SampleDatasetProvider = () => SampleDatasetSchema;
|
||||
|
||||
/** This type is used to identify an object in a sample dataset. */
|
||||
export interface SampleObject {
|
||||
/** The type of the sample object. */
|
||||
type: string;
|
||||
/** The ID of the sample object. */
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This type is used by consumers to register a new app link for a sample dataset.
|
||||
*/
|
||||
export interface AppLinkData {
|
||||
/**
|
||||
* The sample object that is used for this app link's path; if the path does not use an object ID, set this to null.
|
||||
*/
|
||||
sampleObject: SampleObject | null;
|
||||
/**
|
||||
* Function that returns the path for this app link. Note that the `objectId` can be different than the given `sampleObject.id`, depending
|
||||
* on how the sample data was installed. If the `sampleObject` is null, the `objectId` argument will be an empty string.
|
||||
*/
|
||||
getPath: (objectId: string) => string;
|
||||
/**
|
||||
* The label for this app link.
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The icon for this app link.
|
||||
*/
|
||||
icon: string;
|
||||
}
|
||||
|
|
|
@ -48,13 +48,6 @@ const dataIndexSchema = schema.object({
|
|||
|
||||
export type DataIndexSchema = TypeOf<typeof dataIndexSchema>;
|
||||
|
||||
const appLinkSchema = schema.object({
|
||||
path: schema.string(),
|
||||
label: schema.string(),
|
||||
icon: schema.string(),
|
||||
});
|
||||
export type AppLinkSchema = TypeOf<typeof appLinkSchema>;
|
||||
|
||||
export const sampleDataSchema = schema.object({
|
||||
id: schema.string({
|
||||
validate(value: string) {
|
||||
|
@ -71,7 +64,6 @@ export const sampleDataSchema = schema.object({
|
|||
|
||||
// saved object id of main dashboard for sample data set
|
||||
overviewDashboard: schema.string(),
|
||||
appLinks: schema.arrayOf(appLinkSchema, { defaultValue: [] }),
|
||||
|
||||
// saved object id of default index-pattern for sample data set
|
||||
defaultIndex: schema.string(),
|
||||
|
|
13
src/plugins/home/server/services/sample_data/lib/utils.ts
Normal file
13
src/plugins/home/server/services/sample_data/lib/utils.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 type { SampleObject } from './sample_dataset_registry_types';
|
||||
|
||||
export function getUniqueObjectTypes(objects: SampleObject[]) {
|
||||
return [...new Set(objects.map(({ type }) => type))];
|
||||
}
|
|
@ -6,13 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type {
|
||||
IRouter,
|
||||
Logger,
|
||||
IScopedClusterClient,
|
||||
SavedObjectsBulkCreateObject,
|
||||
} from 'src/core/server';
|
||||
import { IRouter, Logger, IScopedClusterClient } from 'src/core/server';
|
||||
import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types';
|
||||
import { createIndexName } from '../lib/create_index_name';
|
||||
import {
|
||||
|
@ -22,6 +18,8 @@ import {
|
|||
} from '../lib/translate_timestamp';
|
||||
import { loadData } from '../lib/load_data';
|
||||
import { SampleDataUsageTracker } from '../usage/usage';
|
||||
import { getSavedObjectsClient } from './utils';
|
||||
import { getUniqueObjectTypes } from '../lib/utils';
|
||||
|
||||
const insertDataIntoIndex = (
|
||||
dataIndexConfig: any,
|
||||
|
@ -143,35 +141,31 @@ export function createInstallRoute(
|
|||
}
|
||||
}
|
||||
|
||||
let createResults;
|
||||
const { getImporter } = context.core.savedObjects;
|
||||
const objectTypes = getUniqueObjectTypes(sampleDataset.savedObjects);
|
||||
const savedObjectsClient = getSavedObjectsClient(context, objectTypes);
|
||||
const importer = getImporter(savedObjectsClient);
|
||||
|
||||
const savedObjects = sampleDataset.savedObjects.map(({ version, ...obj }) => obj);
|
||||
const readStream = Readable.from(savedObjects);
|
||||
|
||||
try {
|
||||
const { getClient, typeRegistry } = context.core.savedObjects;
|
||||
|
||||
const includedHiddenTypes = sampleDataset.savedObjects
|
||||
.map((object) => object.type)
|
||||
.filter((supportedType) => typeRegistry.isHidden(supportedType));
|
||||
|
||||
const client = getClient({ includedHiddenTypes });
|
||||
|
||||
const savedObjects = sampleDataset.savedObjects as SavedObjectsBulkCreateObject[];
|
||||
createResults = await client.bulkCreate(
|
||||
savedObjects.map(({ version, ...savedObject }) => savedObject),
|
||||
{ overwrite: true }
|
||||
);
|
||||
const { errors = [] } = await importer.import({
|
||||
readStream,
|
||||
overwrite: true,
|
||||
createNewCopies: false,
|
||||
});
|
||||
if (errors.length > 0) {
|
||||
const errMsg = `sample_data install errors while loading saved objects. Errors: ${JSON.stringify(
|
||||
errors.map(({ type, id, error }) => ({ type, id, error })) // discard other fields
|
||||
)}`;
|
||||
logger.warn(errMsg);
|
||||
return res.customError({ body: errMsg, statusCode: 500 });
|
||||
}
|
||||
} catch (err) {
|
||||
const errMsg = `bulkCreate failed, error: ${err.message}`;
|
||||
const errMsg = `import failed, error: ${err.message}`;
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
const errors = createResults.saved_objects.filter((savedObjectCreateResult) => {
|
||||
return Boolean(savedObjectCreateResult.error);
|
||||
});
|
||||
if (errors.length > 0) {
|
||||
const errMsg = `sample_data install errors while loading saved objects. Errors: ${JSON.stringify(
|
||||
errors
|
||||
)}`;
|
||||
logger.warn(errMsg);
|
||||
return res.customError({ body: errMsg, statusCode: 403 });
|
||||
}
|
||||
usageTracker.addInstall(params.id);
|
||||
|
||||
// FINALLY
|
||||
|
|
|
@ -6,75 +6,122 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { IRouter } from 'src/core/server';
|
||||
import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types';
|
||||
import type { IRouter, Logger, RequestHandlerContext } from 'src/core/server';
|
||||
import type { AppLinkData, SampleDatasetSchema } from '../lib/sample_dataset_registry_types';
|
||||
import { createIndexName } from '../lib/create_index_name';
|
||||
import type { FindSampleObjectsResponseObject } from '../lib/find_sample_objects';
|
||||
import { findSampleObjects } from '../lib/find_sample_objects';
|
||||
import { getUniqueObjectTypes } from '../lib/utils';
|
||||
import { getSavedObjectsClient } from './utils';
|
||||
|
||||
const NOT_INSTALLED = 'not_installed';
|
||||
const INSTALLED = 'installed';
|
||||
const UNKNOWN = 'unknown';
|
||||
|
||||
export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSchema[]) => {
|
||||
router.get({ path: '/api/sample_data', validate: false }, async (context, req, res) => {
|
||||
const registeredSampleDatasets = sampleDatasets.map((sampleDataset) => {
|
||||
return {
|
||||
id: sampleDataset.id,
|
||||
name: sampleDataset.name,
|
||||
description: sampleDataset.description,
|
||||
previewImagePath: sampleDataset.previewImagePath,
|
||||
darkPreviewImagePath: sampleDataset.darkPreviewImagePath,
|
||||
overviewDashboard: sampleDataset.overviewDashboard,
|
||||
appLinks: sampleDataset.appLinks,
|
||||
defaultIndex: sampleDataset.defaultIndex,
|
||||
dataIndices: sampleDataset.dataIndices.map(({ id }) => ({ id })),
|
||||
status: sampleDataset.status,
|
||||
statusMsg: sampleDataset.statusMsg,
|
||||
};
|
||||
});
|
||||
const isInstalledPromises = registeredSampleDatasets.map(async (sampleDataset) => {
|
||||
for (let i = 0; i < sampleDataset.dataIndices.length; i++) {
|
||||
const dataIndexConfig = sampleDataset.dataIndices[i];
|
||||
const index = createIndexName(sampleDataset.id, dataIndexConfig.id);
|
||||
try {
|
||||
const { body: indexExists } =
|
||||
await context.core.elasticsearch.client.asCurrentUser.indices.exists({
|
||||
index,
|
||||
});
|
||||
if (!indexExists) {
|
||||
sampleDataset.status = NOT_INSTALLED;
|
||||
return;
|
||||
export const createListRoute = (
|
||||
router: IRouter,
|
||||
sampleDatasets: SampleDatasetSchema[],
|
||||
appLinksMap: Map<string, AppLinkData[]>,
|
||||
logger: Logger
|
||||
) => {
|
||||
router.get({ path: '/api/sample_data', validate: false }, async (context, _req, res) => {
|
||||
const allExistingObjects = await findExistingSampleObjects(context, logger, sampleDatasets);
|
||||
|
||||
const registeredSampleDatasets = await Promise.all(
|
||||
sampleDatasets.map(async (sampleDataset) => {
|
||||
const existingObjects = allExistingObjects.get(sampleDataset.id)!;
|
||||
const findObjectId = (type: string, id: string) =>
|
||||
existingObjects.find((object) => object.type === type && object.id === id)
|
||||
?.foundObjectId ?? id;
|
||||
|
||||
const appLinks = (appLinksMap.get(sampleDataset.id) ?? []).map((data) => {
|
||||
const { sampleObject, getPath, label, icon } = data;
|
||||
if (sampleObject === null) {
|
||||
return { path: getPath(''), label, icon };
|
||||
}
|
||||
const objectId = findObjectId(sampleObject.type, sampleObject.id);
|
||||
return { path: getPath(objectId), label, icon };
|
||||
});
|
||||
const sampleDataStatus = await getSampleDatasetStatus(
|
||||
context,
|
||||
allExistingObjects,
|
||||
sampleDataset
|
||||
);
|
||||
|
||||
const { body: count } = await context.core.elasticsearch.client.asCurrentUser.count({
|
||||
index,
|
||||
});
|
||||
if (count.count === 0) {
|
||||
sampleDataset.status = NOT_INSTALLED;
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
sampleDataset.status = UNKNOWN;
|
||||
sampleDataset.statusMsg = err.message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
await context.core.savedObjects.client.get('dashboard', sampleDataset.overviewDashboard);
|
||||
} catch (err) {
|
||||
if (context.core.savedObjects.client.errors.isNotFoundError(err)) {
|
||||
sampleDataset.status = NOT_INSTALLED;
|
||||
return;
|
||||
}
|
||||
return {
|
||||
id: sampleDataset.id,
|
||||
name: sampleDataset.name,
|
||||
description: sampleDataset.description,
|
||||
previewImagePath: sampleDataset.previewImagePath,
|
||||
darkPreviewImagePath: sampleDataset.darkPreviewImagePath,
|
||||
overviewDashboard: findObjectId('dashboard', sampleDataset.overviewDashboard),
|
||||
appLinks,
|
||||
defaultIndex: findObjectId('index-pattern', sampleDataset.defaultIndex),
|
||||
dataIndices: sampleDataset.dataIndices.map(({ id }) => ({ id })),
|
||||
...sampleDataStatus,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
sampleDataset.status = UNKNOWN;
|
||||
sampleDataset.statusMsg = err.message;
|
||||
return;
|
||||
}
|
||||
|
||||
sampleDataset.status = INSTALLED;
|
||||
});
|
||||
|
||||
await Promise.all(isInstalledPromises);
|
||||
return res.ok({ body: registeredSampleDatasets });
|
||||
});
|
||||
};
|
||||
|
||||
type ExistingSampleObjects = Map<string, FindSampleObjectsResponseObject[]>;
|
||||
async function findExistingSampleObjects(
|
||||
context: RequestHandlerContext,
|
||||
logger: Logger,
|
||||
sampleDatasets: SampleDatasetSchema[]
|
||||
) {
|
||||
const objects = sampleDatasets
|
||||
.map(({ savedObjects }) => savedObjects.map(({ type, id }) => ({ type, id })))
|
||||
.flat();
|
||||
const objectTypes = getUniqueObjectTypes(objects);
|
||||
const client = getSavedObjectsClient(context, objectTypes);
|
||||
const findSampleObjectsResult = await findSampleObjects({ client, logger, objects });
|
||||
|
||||
let objectCounter = 0;
|
||||
return sampleDatasets.reduce<ExistingSampleObjects>((acc, { id, savedObjects }) => {
|
||||
const datasetResults = savedObjects.map(() => findSampleObjectsResult[objectCounter++]);
|
||||
return acc.set(id, datasetResults);
|
||||
}, new Map());
|
||||
}
|
||||
|
||||
// TODO: introduce PARTIALLY_INSTALLED status (#116677)
|
||||
async function getSampleDatasetStatus(
|
||||
context: RequestHandlerContext,
|
||||
existingSampleObjects: ExistingSampleObjects,
|
||||
sampleDataset: SampleDatasetSchema
|
||||
): Promise<{ status: string; statusMsg?: string }> {
|
||||
const dashboard = existingSampleObjects
|
||||
.get(sampleDataset.id)!
|
||||
.find(({ type, id }) => type === 'dashboard' && id === sampleDataset.overviewDashboard);
|
||||
if (!dashboard?.foundObjectId) {
|
||||
return { status: NOT_INSTALLED };
|
||||
}
|
||||
|
||||
for (let i = 0; i < sampleDataset.dataIndices.length; i++) {
|
||||
const dataIndexConfig = sampleDataset.dataIndices[i];
|
||||
const index = createIndexName(sampleDataset.id, dataIndexConfig.id);
|
||||
try {
|
||||
const { body: indexExists } =
|
||||
await context.core.elasticsearch.client.asCurrentUser.indices.exists({
|
||||
index,
|
||||
});
|
||||
if (!indexExists) {
|
||||
return { status: NOT_INSTALLED };
|
||||
}
|
||||
|
||||
const { body: count } = await context.core.elasticsearch.client.asCurrentUser.count({
|
||||
index,
|
||||
});
|
||||
if (count.count === 0) {
|
||||
return { status: NOT_INSTALLED };
|
||||
}
|
||||
} catch (err) {
|
||||
return { status: UNKNOWN, statusMsg: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
return { status: INSTALLED };
|
||||
}
|
||||
|
|
|
@ -6,16 +6,20 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { isBoom } from '@hapi/boom';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import _ from 'lodash';
|
||||
import { IRouter } from 'src/core/server';
|
||||
import type { IRouter, Logger } from 'src/core/server';
|
||||
import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types';
|
||||
import { createIndexName } from '../lib/create_index_name';
|
||||
import { SampleDataUsageTracker } from '../usage/usage';
|
||||
import { findSampleObjects } from '../lib/find_sample_objects';
|
||||
import { getUniqueObjectTypes } from '../lib/utils';
|
||||
import { getSavedObjectsClient } from './utils';
|
||||
|
||||
export function createUninstallRoute(
|
||||
router: IRouter,
|
||||
sampleDatasets: SampleDatasetSchema[],
|
||||
logger: Logger,
|
||||
usageTracker: SampleDataUsageTracker
|
||||
): void {
|
||||
router.delete(
|
||||
|
@ -25,16 +29,7 @@ export function createUninstallRoute(
|
|||
params: schema.object({ id: schema.string() }),
|
||||
},
|
||||
},
|
||||
async (
|
||||
{
|
||||
core: {
|
||||
elasticsearch: { client: esClient },
|
||||
savedObjects: { getClient: getSavedObjectsClient, typeRegistry },
|
||||
},
|
||||
},
|
||||
request,
|
||||
response
|
||||
) => {
|
||||
async (context, request, response) => {
|
||||
const sampleDataset = sampleDatasets.find(({ id }) => id === request.params.id);
|
||||
|
||||
if (!sampleDataset) {
|
||||
|
@ -46,41 +41,46 @@ export function createUninstallRoute(
|
|||
const index = createIndexName(sampleDataset.id, dataIndexConfig.id);
|
||||
|
||||
try {
|
||||
await esClient.asCurrentUser.indices.delete({
|
||||
index,
|
||||
});
|
||||
// TODO: don't delete the index if sample data exists in other spaces (#116677)
|
||||
await context.core.elasticsearch.client.asCurrentUser.indices.delete({ index });
|
||||
} catch (err) {
|
||||
return response.customError({
|
||||
statusCode: err.status,
|
||||
body: {
|
||||
message: `Unable to delete sample data index "${index}", error: ${err.message}`,
|
||||
},
|
||||
});
|
||||
// if the index doesn't exist, ignore the error and proceed
|
||||
if (err.body.status !== 404) {
|
||||
return response.customError({
|
||||
statusCode: err.body.status,
|
||||
body: {
|
||||
message: `Unable to delete sample data index "${index}", error: ${err.body.error.type}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const includedHiddenTypes = sampleDataset.savedObjects
|
||||
.map((object) => object.type)
|
||||
.filter((supportedType) => typeRegistry.isHidden(supportedType));
|
||||
const objects = sampleDataset.savedObjects.map(({ type, id }) => ({ type, id }));
|
||||
const objectTypes = getUniqueObjectTypes(objects);
|
||||
const client = getSavedObjectsClient(context, objectTypes);
|
||||
const findSampleObjectsResult = await findSampleObjects({ client, logger, objects });
|
||||
|
||||
const savedObjectsClient = getSavedObjectsClient({ includedHiddenTypes });
|
||||
|
||||
const deletePromises = sampleDataset.savedObjects.map(({ type, id }) =>
|
||||
savedObjectsClient.delete(type, id)
|
||||
const objectsToDelete = findSampleObjectsResult.filter(({ foundObjectId }) => foundObjectId);
|
||||
const deletePromises = objectsToDelete.map(({ type, foundObjectId }) =>
|
||||
client.delete(type, foundObjectId!).catch((err) => {
|
||||
// if the object doesn't exist, ignore the error and proceed
|
||||
if (isBoom(err) && err.output.statusCode === 404) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.all(deletePromises);
|
||||
} catch (err) {
|
||||
// ignore 404s since users could have deleted some of the saved objects via the UI
|
||||
if (_.get(err, 'output.statusCode') !== 404) {
|
||||
return response.customError({
|
||||
statusCode: err.status,
|
||||
body: {
|
||||
message: `Unable to delete sample dataset saved objects, error: ${err.message}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
return response.customError({
|
||||
statusCode: err.body.status,
|
||||
body: {
|
||||
message: `Unable to delete sample dataset saved objects, error: ${err.body.error.type}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// track the usage operation in a non-blocking way
|
||||
|
|
17
src/plugins/home/server/services/sample_data/routes/utils.ts
Normal file
17
src/plugins/home/server/services/sample_data/routes/utils.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 type { RequestHandlerContext } from 'src/core/server';
|
||||
|
||||
export function getSavedObjectsClient(context: RequestHandlerContext, objectTypes: string[]) {
|
||||
const { getClient, typeRegistry } = context.core.savedObjects;
|
||||
const includedHiddenTypes = objectTypes.filter((supportedType) =>
|
||||
typeRegistry.isHidden(supportedType)
|
||||
);
|
||||
return getClient({ includedHiddenTypes });
|
||||
}
|
|
@ -11,8 +11,8 @@ import { SavedObject } from 'src/core/public';
|
|||
import {
|
||||
SampleDatasetProvider,
|
||||
SampleDatasetSchema,
|
||||
AppLinkSchema,
|
||||
SampleDatasetDashboardPanel,
|
||||
AppLinkData,
|
||||
} from './lib/sample_dataset_registry_types';
|
||||
import { sampleDataSchema } from './lib/sample_dataset_schema';
|
||||
|
||||
|
@ -27,6 +27,7 @@ import { registerSampleDatasetWithIntegration } from './lib/register_with_integr
|
|||
export class SampleDataRegistry {
|
||||
constructor(private readonly initContext: PluginInitializerContext) {}
|
||||
private readonly sampleDatasets: SampleDatasetSchema[] = [];
|
||||
private readonly appLinksMap = new Map<string, AppLinkData[]>();
|
||||
|
||||
private registerSampleDataSet(specProvider: SampleDatasetProvider) {
|
||||
let value: SampleDatasetSchema;
|
||||
|
@ -69,14 +70,10 @@ export class SampleDataRegistry {
|
|||
this.initContext.logger.get('sample_data', 'usage')
|
||||
);
|
||||
const router = core.http.createRouter();
|
||||
createListRoute(router, this.sampleDatasets);
|
||||
createInstallRoute(
|
||||
router,
|
||||
this.sampleDatasets,
|
||||
this.initContext.logger.get('sampleData'),
|
||||
usageTracker
|
||||
);
|
||||
createUninstallRoute(router, this.sampleDatasets, usageTracker);
|
||||
const logger = this.initContext.logger.get('sampleData');
|
||||
createListRoute(router, this.sampleDatasets, this.appLinksMap, logger);
|
||||
createInstallRoute(router, this.sampleDatasets, logger, usageTracker);
|
||||
createUninstallRoute(router, this.sampleDatasets, logger, usageTracker);
|
||||
|
||||
this.registerSampleDataSet(flightsSpecProvider);
|
||||
this.registerSampleDataSet(logsSpecProvider);
|
||||
|
@ -100,7 +97,7 @@ export class SampleDataRegistry {
|
|||
sampleDataset.savedObjects = sampleDataset.savedObjects.concat(savedObjects);
|
||||
},
|
||||
|
||||
addAppLinksToSampleDataset: (id: string, appLinks: AppLinkSchema[]) => {
|
||||
addAppLinksToSampleDataset: (id: string, appLinks: AppLinkData[]) => {
|
||||
const sampleDataset = this.sampleDatasets.find((dataset) => {
|
||||
return dataset.id === id;
|
||||
});
|
||||
|
@ -109,9 +106,8 @@ export class SampleDataRegistry {
|
|||
throw new Error(`Unable to find sample dataset with id: ${id}`);
|
||||
}
|
||||
|
||||
sampleDataset.appLinks = sampleDataset.appLinks
|
||||
? sampleDataset.appLinks.concat(appLinks)
|
||||
: [];
|
||||
const existingAppLinks = this.appLinksMap.get(id) ?? [];
|
||||
this.appLinksMap.set(id, [...existingAppLinks, ...appLinks]);
|
||||
},
|
||||
|
||||
replacePanelInSampleDatasetDashboard: ({
|
||||
|
|
|
@ -19,6 +19,7 @@ export type {
|
|||
ArtifactsSchema,
|
||||
TutorialSchema,
|
||||
TutorialProvider,
|
||||
TutorialContext,
|
||||
TutorialContextFactory,
|
||||
ScopedTutorialContextFactory,
|
||||
} from './lib/tutorials_registry_types';
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import type { Response } from 'superagent';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
|
@ -15,81 +16,142 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const es = getService('es');
|
||||
|
||||
const MILLISECOND_IN_WEEK = 1000 * 60 * 60 * 24 * 7;
|
||||
const SPACES = ['default', 'other'];
|
||||
const FLIGHTS_OVERVIEW_DASHBOARD_ID = '7adfa750-4c81-11e8-b3d7-01146121b73d'; // default ID of the flights overview dashboard
|
||||
const FLIGHTS_CANVAS_APPLINK_PATH =
|
||||
'/app/canvas#/workpad/workpad-a474e74b-aedc-47c3-894a-db77e62c41e0'; // includes default ID of the flights canvas applink path
|
||||
|
||||
describe('sample data apis', () => {
|
||||
before(async () => {
|
||||
await esArchiver.emptyKibanaIndex();
|
||||
});
|
||||
describe('list', () => {
|
||||
it('should return list of sample data sets with installed status', async () => {
|
||||
const resp = await supertest.get(`/api/sample_data`).set('kbn-xsrf', 'kibana').expect(200);
|
||||
|
||||
expect(resp.body).to.be.an('array');
|
||||
expect(resp.body.length).to.be.above(0);
|
||||
expect(resp.body[0].status).to.be('not_installed');
|
||||
});
|
||||
after(async () => {
|
||||
await esArchiver.emptyKibanaIndex();
|
||||
});
|
||||
|
||||
describe('install', () => {
|
||||
it('should return 404 if id does not match any sample data sets', async () => {
|
||||
await supertest.post(`/api/sample_data/xxxx`).set('kbn-xsrf', 'kibana').expect(404);
|
||||
});
|
||||
for (const space of SPACES) {
|
||||
const apiPath = `/s/${space}/api/sample_data`;
|
||||
|
||||
it('should return 200 if success', async () => {
|
||||
const resp = await supertest
|
||||
.post(`/api/sample_data/flights`)
|
||||
.set('kbn-xsrf', 'kibana')
|
||||
.expect(200);
|
||||
describe(`list in the ${space} space (before install)`, () => {
|
||||
it('should return list of sample data sets with installed status', async () => {
|
||||
const resp = await supertest.get(apiPath).set('kbn-xsrf', 'kibana').expect(200);
|
||||
|
||||
expect(resp.body).to.eql({
|
||||
elasticsearchIndicesCreated: { kibana_sample_data_flights: 13059 },
|
||||
kibanaSavedObjectsLoaded: 11,
|
||||
const flightsData = findFlightsData(resp);
|
||||
expect(flightsData.status).to.be('not_installed');
|
||||
// Check and make sure the sample dataset reflects the default object IDs, because no sample data objects exist.
|
||||
// Instead of checking each object ID, we check the dashboard and canvas app link as representatives.
|
||||
expect(flightsData.overviewDashboard).to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID);
|
||||
expect(flightsData.appLinks[0].path).to.be(FLIGHTS_CANVAS_APPLINK_PATH);
|
||||
});
|
||||
});
|
||||
|
||||
it('should load elasticsearch index containing sample data with dates relative to current time', async () => {
|
||||
const resp = await es.search<{ timestamp: string }>({
|
||||
index: 'kibana_sample_data_flights',
|
||||
describe(`install in the ${space} space`, () => {
|
||||
it('should return 404 if id does not match any sample data sets', async () => {
|
||||
await supertest.post(`${apiPath}/xxxx`).set('kbn-xsrf', 'kibana').expect(404);
|
||||
});
|
||||
|
||||
const doc = resp.hits.hits[0];
|
||||
const docMilliseconds = Date.parse(doc._source!.timestamp);
|
||||
const nowMilliseconds = Date.now();
|
||||
const delta = Math.abs(nowMilliseconds - docMilliseconds);
|
||||
expect(delta).to.be.lessThan(MILLISECOND_IN_WEEK * 4);
|
||||
});
|
||||
it('should return 200 if success', async () => {
|
||||
const resp = await supertest
|
||||
.post(`${apiPath}/flights`)
|
||||
.set('kbn-xsrf', 'kibana')
|
||||
.expect(200);
|
||||
|
||||
describe('parameters', () => {
|
||||
it('should load elasticsearch index containing sample data with dates relative to now parameter', async () => {
|
||||
const nowString = `2000-01-01T00:00:00`;
|
||||
await supertest
|
||||
.post(`/api/sample_data/flights?now=${nowString}`)
|
||||
.set('kbn-xsrf', 'kibana');
|
||||
expect(resp.body).to.eql({
|
||||
elasticsearchIndicesCreated: { kibana_sample_data_flights: 13059 },
|
||||
kibanaSavedObjectsLoaded: 11,
|
||||
});
|
||||
});
|
||||
|
||||
it('should load elasticsearch index containing sample data with dates relative to current time', async () => {
|
||||
const resp = await es.search<{ timestamp: string }>({
|
||||
index: 'kibana_sample_data_flights',
|
||||
});
|
||||
|
||||
const doc = resp.hits.hits[0];
|
||||
const docMilliseconds = Date.parse(doc._source!.timestamp);
|
||||
const nowMilliseconds = Date.parse(nowString);
|
||||
const nowMilliseconds = Date.now();
|
||||
const delta = Math.abs(nowMilliseconds - docMilliseconds);
|
||||
expect(delta).to.be.lessThan(MILLISECOND_IN_WEEK * 4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('uninstall', () => {
|
||||
it('should uninstall sample data', async () => {
|
||||
await supertest.delete(`/api/sample_data/flights`).set('kbn-xsrf', 'kibana').expect(204);
|
||||
});
|
||||
describe('parameters', () => {
|
||||
it('should load elasticsearch index containing sample data with dates relative to now parameter', async () => {
|
||||
const nowString = `2000-01-01T00:00:00`;
|
||||
await supertest.post(`${apiPath}/flights?now=${nowString}`).set('kbn-xsrf', 'kibana');
|
||||
|
||||
it('should remove elasticsearch index containing sample data', async () => {
|
||||
const resp = await es.indices.exists({
|
||||
index: 'kibana_sample_data_flights',
|
||||
const resp = await es.search<{ timestamp: string }>({
|
||||
index: 'kibana_sample_data_flights',
|
||||
});
|
||||
|
||||
const doc = resp.hits.hits[0];
|
||||
const docMilliseconds = Date.parse(doc._source!.timestamp);
|
||||
const nowMilliseconds = Date.parse(nowString);
|
||||
const delta = Math.abs(nowMilliseconds - docMilliseconds);
|
||||
expect(delta).to.be.lessThan(MILLISECOND_IN_WEEK * 4);
|
||||
});
|
||||
});
|
||||
expect(resp).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`list in the ${space} space (after install)`, () => {
|
||||
it('should return list of sample data sets with installed status', async () => {
|
||||
const resp = await supertest.get(apiPath).set('kbn-xsrf', 'kibana').expect(200);
|
||||
|
||||
const flightsData = findFlightsData(resp);
|
||||
expect(flightsData.status).to.be('installed');
|
||||
// Check and make sure the sample dataset reflects the existing object IDs in each space.
|
||||
// Instead of checking each object ID, we check the dashboard and canvas app link as representatives.
|
||||
if (space === 'default') {
|
||||
expect(flightsData.overviewDashboard).to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID);
|
||||
expect(flightsData.appLinks[0].path).to.be(FLIGHTS_CANVAS_APPLINK_PATH);
|
||||
} else {
|
||||
// the sample data objects installed in the 'other' space had their IDs regenerated upon import
|
||||
expect(flightsData.overviewDashboard).not.to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID);
|
||||
expect(flightsData.appLinks[0].path).not.to.be(FLIGHTS_CANVAS_APPLINK_PATH);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
for (const space of SPACES) {
|
||||
const apiPath = `/s/${space}/api/sample_data`;
|
||||
|
||||
describe(`uninstall in the ${space} space`, () => {
|
||||
it('should uninstall sample data', async () => {
|
||||
// Note: the second time this happens, the index has already been removed, but the uninstall works anyway
|
||||
await supertest.delete(`${apiPath}/flights`).set('kbn-xsrf', 'kibana').expect(204);
|
||||
});
|
||||
|
||||
it('should remove elasticsearch index containing sample data', async () => {
|
||||
const resp = await es.indices.exists({
|
||||
index: 'kibana_sample_data_flights',
|
||||
});
|
||||
expect(resp).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`list in the ${space} space (after uninstall)`, () => {
|
||||
it('should return list of sample data sets with installed status', async () => {
|
||||
const resp = await supertest.get(apiPath).set('kbn-xsrf', 'kibana').expect(200);
|
||||
|
||||
const flightsData = findFlightsData(resp);
|
||||
expect(flightsData.status).to.be('not_installed');
|
||||
// Check and make sure the sample dataset reflects the default object IDs, because no sample data objects exist.
|
||||
// Instead of checking each object ID, we check the dashboard and canvas app link as representatives.
|
||||
expect(flightsData.overviewDashboard).to.be(FLIGHTS_OVERVIEW_DASHBOARD_ID);
|
||||
expect(flightsData.appLinks[0].path).to.be(FLIGHTS_CANVAS_APPLINK_PATH);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function findFlightsData(response: Response) {
|
||||
expect(response.body).to.be.an('array');
|
||||
expect(response.body.length).to.be.above(0);
|
||||
// @ts-expect-error Binding element 'id' implicitly has an 'any' type.
|
||||
const flightsData = response.body.find(({ id }) => id === 'flights');
|
||||
if (!flightsData) {
|
||||
throw new Error('Could not find flights data');
|
||||
}
|
||||
return flightsData;
|
||||
}
|
||||
|
|
|
@ -28,11 +28,16 @@ export function loadSampleData(
|
|||
return savedObject;
|
||||
});
|
||||
}
|
||||
const getPath = (objectId: string) => `/app/canvas#/workpad/${objectId}`;
|
||||
|
||||
addSavedObjectsToSampleDataset('ecommerce', updateCanvasWorkpadTimestamps(ecommerceSavedObjects));
|
||||
addAppLinksToSampleDataset('ecommerce', [
|
||||
{
|
||||
path: '/app/canvas#/workpad/workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e',
|
||||
sampleObject: {
|
||||
type: 'canvas-workpad',
|
||||
id: 'workpad-e08b9bdb-ec14-4339-94c4-063bddfd610e',
|
||||
},
|
||||
getPath,
|
||||
icon: 'canvasApp',
|
||||
label,
|
||||
},
|
||||
|
@ -41,7 +46,11 @@ export function loadSampleData(
|
|||
addSavedObjectsToSampleDataset('flights', updateCanvasWorkpadTimestamps(flightsSavedObjects));
|
||||
addAppLinksToSampleDataset('flights', [
|
||||
{
|
||||
path: '/app/canvas#/workpad/workpad-a474e74b-aedc-47c3-894a-db77e62c41e0',
|
||||
sampleObject: {
|
||||
type: 'canvas-workpad',
|
||||
id: 'workpad-a474e74b-aedc-47c3-894a-db77e62c41e0',
|
||||
},
|
||||
getPath,
|
||||
icon: 'canvasApp',
|
||||
label,
|
||||
},
|
||||
|
@ -50,7 +59,11 @@ export function loadSampleData(
|
|||
addSavedObjectsToSampleDataset('logs', updateCanvasWorkpadTimestamps(webLogsSavedObjects));
|
||||
addAppLinksToSampleDataset('logs', [
|
||||
{
|
||||
path: '/app/canvas#/workpad/workpad-ad72a4e9-b422-480c-be6d-a64a0b79541d',
|
||||
sampleObject: {
|
||||
type: 'canvas-workpad',
|
||||
id: 'workpad-ad72a4e9-b422-480c-be6d-a64a0b79541d',
|
||||
},
|
||||
getPath,
|
||||
icon: 'canvasApp',
|
||||
label,
|
||||
},
|
||||
|
|
|
@ -348,7 +348,6 @@ const wsState: any = {
|
|||
maxValuesPerDoc: 1,
|
||||
minDocCount: 3,
|
||||
},
|
||||
indexPatternRefName: 'indexPattern_0',
|
||||
};
|
||||
|
||||
export function registerEcommerceSampleData(sampleDataRegistry: SampleDataRegistrySetup) {
|
||||
|
@ -365,16 +364,11 @@ export function registerEcommerceSampleData(sampleDataRegistry: SampleDataRegist
|
|||
numVertices: 12,
|
||||
version: 1,
|
||||
wsState: JSON.stringify(JSON.stringify(wsState)),
|
||||
legacyIndexPatternRef: 'kibana_sample_data_ecommerce',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'indexPattern_0',
|
||||
type: 'index-pattern',
|
||||
id: 'kibana_sample_data_ecommerce',
|
||||
},
|
||||
],
|
||||
references: [],
|
||||
migrationVersion: {
|
||||
'graph-workspace': '7.0.0',
|
||||
'graph-workspace': '7.11.0',
|
||||
},
|
||||
updated_at: '2020-01-09T16:40:36.122Z',
|
||||
},
|
||||
|
@ -383,7 +377,11 @@ export function registerEcommerceSampleData(sampleDataRegistry: SampleDataRegist
|
|||
export function registerEcommerceSampleDataLink(sampleDataRegistry: SampleDataRegistrySetup) {
|
||||
sampleDataRegistry.addAppLinksToSampleDataset(datasetId, [
|
||||
{
|
||||
path: createWorkspacePath('46fa9d30-319c-11ea-bbe4-818d9c786051'),
|
||||
sampleObject: {
|
||||
type: 'graph-workspace',
|
||||
id: '46fa9d30-319c-11ea-bbe4-818d9c786051',
|
||||
},
|
||||
getPath: createWorkspacePath,
|
||||
label: i18n.translate('xpack.graph.sampleData.label', { defaultMessage: 'Graph' }),
|
||||
icon: APP_ICON,
|
||||
},
|
||||
|
|
|
@ -1602,7 +1602,6 @@ const wsState: any = {
|
|||
maxValuesPerDoc: 1,
|
||||
minDocCount: 3,
|
||||
},
|
||||
indexPatternRefName: 'indexPattern_0',
|
||||
};
|
||||
|
||||
export function registerFlightsSampleData(sampleDataRegistry: SampleDataRegistrySetup) {
|
||||
|
@ -1619,16 +1618,11 @@ export function registerFlightsSampleData(sampleDataRegistry: SampleDataRegistry
|
|||
numVertices: 91,
|
||||
version: 1,
|
||||
wsState: JSON.stringify(JSON.stringify(wsState)),
|
||||
legacyIndexPatternRef: 'kibana_sample_data_flights',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'indexPattern_0',
|
||||
type: 'index-pattern',
|
||||
id: 'kibana_sample_data_flights',
|
||||
},
|
||||
],
|
||||
references: [],
|
||||
migrationVersion: {
|
||||
'graph-workspace': '7.0.0',
|
||||
'graph-workspace': '7.11.0',
|
||||
},
|
||||
updated_at: '2020-01-09T15:55:24.013Z',
|
||||
},
|
||||
|
@ -1637,7 +1631,11 @@ export function registerFlightsSampleData(sampleDataRegistry: SampleDataRegistry
|
|||
export function registerFlightsSampleDataLink(sampleDataRegistry: SampleDataRegistrySetup) {
|
||||
sampleDataRegistry.addAppLinksToSampleDataset(datasetId, [
|
||||
{
|
||||
path: createWorkspacePath('5dc018d0-32f8-11ea-bbe4-818d9c786051'),
|
||||
sampleObject: {
|
||||
type: 'graph-workspace',
|
||||
id: '5dc018d0-32f8-11ea-bbe4-818d9c786051',
|
||||
},
|
||||
getPath: createWorkspacePath,
|
||||
label: i18n.translate('xpack.graph.sampleData.label', { defaultMessage: 'Graph' }),
|
||||
icon: APP_ICON,
|
||||
},
|
||||
|
|
|
@ -419,7 +419,6 @@ const wsState: any = {
|
|||
maxValuesPerDoc: 1,
|
||||
minDocCount: 3,
|
||||
},
|
||||
indexPatternRefName: 'indexPattern_0',
|
||||
};
|
||||
|
||||
export function registerLogsSampleData(sampleDataRegistry: SampleDataRegistrySetup) {
|
||||
|
@ -436,16 +435,11 @@ export function registerLogsSampleData(sampleDataRegistry: SampleDataRegistrySet
|
|||
numVertices: 27,
|
||||
version: 1,
|
||||
wsState: JSON.stringify(JSON.stringify(wsState)),
|
||||
legacyIndexPatternRef: 'kibana_sample_data_logs',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
name: 'indexPattern_0',
|
||||
type: 'index-pattern',
|
||||
id: 'kibana_sample_data_logs',
|
||||
},
|
||||
],
|
||||
references: [],
|
||||
migrationVersion: {
|
||||
'graph-workspace': '7.0.0',
|
||||
'graph-workspace': '7.11.0',
|
||||
},
|
||||
updated_at: '2020-01-09T16:40:36.122Z',
|
||||
},
|
||||
|
@ -454,7 +448,11 @@ export function registerLogsSampleData(sampleDataRegistry: SampleDataRegistrySet
|
|||
export function registerLogsSampleDataLink(sampleDataRegistry: SampleDataRegistrySetup) {
|
||||
sampleDataRegistry.addAppLinksToSampleDataset(datasetId, [
|
||||
{
|
||||
path: createWorkspacePath('e2141080-32fa-11ea-bbe4-818d9c786051'),
|
||||
sampleObject: {
|
||||
type: 'graph-workspace',
|
||||
id: 'e2141080-32fa-11ea-bbe4-818d9c786051',
|
||||
},
|
||||
getPath: createWorkspacePath,
|
||||
label: i18n.translate('xpack.graph.sampleData.label', { defaultMessage: 'Graph' }),
|
||||
icon: APP_ICON,
|
||||
},
|
||||
|
|
|
@ -158,7 +158,8 @@ export class InfraServerPlugin implements Plugin<InfraPluginSetup> {
|
|||
|
||||
plugins.home.sampleData.addAppLinksToSampleDataset('logs', [
|
||||
{
|
||||
path: `/app/logs`,
|
||||
sampleObject: null, // indicates that there is no sample object associated with this app link's path
|
||||
getPath: () => `/app/logs`,
|
||||
label: logsSampleDataLinkLabel,
|
||||
icon: 'logsApp',
|
||||
},
|
||||
|
|
|
@ -77,7 +77,11 @@ export class MapsPlugin implements Plugin {
|
|||
|
||||
home.sampleData.addAppLinksToSampleDataset('ecommerce', [
|
||||
{
|
||||
path: getFullPath('2c9c1f60-1909-11e9-919b-ffe5949a18d2'),
|
||||
sampleObject: {
|
||||
type: MAP_SAVED_OBJECT_TYPE,
|
||||
id: '2c9c1f60-1909-11e9-919b-ffe5949a18d2',
|
||||
},
|
||||
getPath: getFullPath,
|
||||
label: sampleDataLinkLabel,
|
||||
icon: APP_ICON,
|
||||
},
|
||||
|
@ -99,7 +103,11 @@ export class MapsPlugin implements Plugin {
|
|||
|
||||
home.sampleData.addAppLinksToSampleDataset('flights', [
|
||||
{
|
||||
path: getFullPath('5dd88580-1906-11e9-919b-ffe5949a18d2'),
|
||||
sampleObject: {
|
||||
type: MAP_SAVED_OBJECT_TYPE,
|
||||
id: '5dd88580-1906-11e9-919b-ffe5949a18d2',
|
||||
},
|
||||
getPath: getFullPath,
|
||||
label: sampleDataLinkLabel,
|
||||
icon: APP_ICON,
|
||||
},
|
||||
|
@ -120,7 +128,11 @@ export class MapsPlugin implements Plugin {
|
|||
home.sampleData.addSavedObjectsToSampleDataset('logs', getWebLogsSavedObjects());
|
||||
home.sampleData.addAppLinksToSampleDataset('logs', [
|
||||
{
|
||||
path: getFullPath('de71f4f0-1902-11e9-919b-ffe5949a18d2'),
|
||||
sampleObject: {
|
||||
type: MAP_SAVED_OBJECT_TYPE,
|
||||
id: 'de71f4f0-1902-11e9-919b-ffe5949a18d2',
|
||||
},
|
||||
getPath: getFullPath,
|
||||
label: sampleDataLinkLabel,
|
||||
icon: APP_ICON,
|
||||
},
|
||||
|
|
|
@ -15,10 +15,16 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup)
|
|||
defaultMessage: 'ML jobs',
|
||||
});
|
||||
const { addAppLinksToSampleDataset } = plugins.home.sampleData;
|
||||
const getCreateJobPath = (jobId: string, dataViewId: string) =>
|
||||
`/app/ml/modules/check_view_or_create?id=${jobId}&index=${dataViewId}`;
|
||||
|
||||
addAppLinksToSampleDataset('ecommerce', [
|
||||
{
|
||||
path: '/app/ml/modules/check_view_or_create?id=sample_data_ecommerce&index=ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
sampleObject: {
|
||||
type: 'index-pattern',
|
||||
id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f',
|
||||
},
|
||||
getPath: (objectId) => getCreateJobPath('sample_data_ecommerce', objectId),
|
||||
label: sampleDataLinkLabel,
|
||||
icon: 'machineLearningApp',
|
||||
},
|
||||
|
@ -26,7 +32,11 @@ export function initSampleDataSets(mlLicense: MlLicense, plugins: PluginsSetup)
|
|||
|
||||
addAppLinksToSampleDataset('logs', [
|
||||
{
|
||||
path: '/app/ml/modules/check_view_or_create?id=sample_data_weblogs&index=90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||
sampleObject: {
|
||||
type: 'index-pattern',
|
||||
id: '90943e30-9a47-11e8-b64d-95841ca0b247',
|
||||
},
|
||||
getPath: (objectId) => getCreateJobPath('sample_data_weblogs', objectId),
|
||||
label: sampleDataLinkLabel,
|
||||
icon: 'machineLearningApp',
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue