diff --git a/changelogs/fragments/8587.yml b/changelogs/fragments/8587.yml new file mode 100644 index 000000000000..259c99b7fd22 --- /dev/null +++ b/changelogs/fragments/8587.yml @@ -0,0 +1,2 @@ +feat: +- Add support for otel sample data - logs, traces and metrics ([#8587](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8587)) \ No newline at end of file diff --git a/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap index 3230c2c5d8ca..e51688edf775 100644 --- a/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/sample_data_view_data_button.test.js.snap @@ -39,7 +39,7 @@ exports[`should render popover when appLinks is not empty 1`] = ` "onClick": [Function], }, Object { - "href": "rootapp/myAppPath", + "href": "rootundefined", "icon": { - return { - name: label, - icon: , - href: this.addBasePath(path), - onClick: createAppNavigationHandler(path), - }; - }); + const additionalItems = this.props.appLinks.map( + ({ path, label, icon, newPath, appendDatasourceToPath }) => { + // switch paths if new nav is enabled + let appPath = this.chrome.navGroup.getNavGroupEnabled() + ? this.addBasePath(newPath) + : this.addBasePath(path); + // append datasourceId to app path + if (this.isDataSourceEnabled && appendDatasourceToPath) { + appPath = `${appPath}?datasourceId=${this.props.dataSourceId}`; + } + return { + name: label, + icon: , + href: appPath, + onClick: createAppNavigationHandler(appPath), + }; + } + ); const panels = [ { id: 0, items: [ - { - name: i18n.translate('home.sampleDataSetCard.dashboardLinkLabel', { - defaultMessage: 'Dashboard', - }), - icon: , - href: prefixedDashboardPath, - onClick: createAppNavigationHandler(dashboardPath), - }, + ...(this.props.overviewDashboard !== '' + ? [ + { + name: i18n.translate('home.sampleDataSetCard.dashboardLinkLabel', { + defaultMessage: 'Dashboard', + }), + icon: , + href: prefixedDashboardPath, + onClick: createAppNavigationHandler(dashboardPath), + }, + ] + : []), ...additionalItems, ], }, diff --git a/src/plugins/home/public/application/components/sample_data_view_data_button.test.js b/src/plugins/home/public/application/components/sample_data_view_data_button.test.js index 11b0e617f764..32c7800226c0 100644 --- a/src/plugins/home/public/application/components/sample_data_view_data_button.test.js +++ b/src/plugins/home/public/application/components/sample_data_view_data_button.test.js @@ -36,6 +36,11 @@ import { SampleDataViewDataButton } from './sample_data_view_data_button'; jest.mock('../opensearch_dashboards_services', () => ({ getServices: () => ({ addBasePath: (path) => `root${path}`, + chrome: { + navGroup: { + getNavGroupEnabled: jest.fn().mockReturnValue(true), + }, + }, }), })); diff --git a/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces.png b/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces.png new file mode 100644 index 000000000000..80eaa7beb2e2 Binary files /dev/null and b/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces.png differ diff --git a/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces_dark.png b/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces_dark.png new file mode 100644 index 000000000000..a01821ec0303 Binary files /dev/null and b/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces_dark.png differ diff --git a/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces_dark_new.png b/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces_dark_new.png new file mode 100644 index 000000000000..a01821ec0303 Binary files /dev/null and b/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces_dark_new.png differ diff --git a/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces_new.png b/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces_new.png new file mode 100644 index 000000000000..80eaa7beb2e2 Binary files /dev/null and b/src/plugins/home/public/assets/sample_data_resources/otel/otel_traces_new.png differ diff --git a/src/plugins/home/server/services/sample_data/data_sets/otel/index.ts b/src/plugins/home/server/services/sample_data/data_sets/otel/index.ts new file mode 100644 index 000000000000..6e83c85c5d4b --- /dev/null +++ b/src/plugins/home/server/services/sample_data/data_sets/otel/index.ts @@ -0,0 +1,101 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import path from 'path'; +import { AppLinkSchema, SampleDatasetSchema } from '../../lib/sample_dataset_registry_types'; +import { + appendDataSourceId, + getSavedObjectsWithDataSource, + overwriteSavedObjectsWithWorkspaceId, +} from '../util'; +import { logsFieldMappings } from './logs_field_mappings'; +import { metricsFieldMappings } from './metrics_field_mappings'; +import { servicesFieldMappings } from './services_field_mappings'; +import { tracesFieldMappings } from './traces_field_mappings'; + +const otelDataName = i18n.translate('home.sampleData.otelSpecTitle', { + defaultMessage: 'Sample Observability Logs, Traces, and Metrics', +}); +const otelDataDescription = i18n.translate('home.sampleData.otelSpecDescription', { + defaultMessage: + 'Correlated observability signals for an e-commerce application in OpenTelemetry standard.', +}); +const initialAppLinks: AppLinkSchema[] = [ + { + path: 'observability-traces#/traces', + icon: 'apmTrace', + label: 'View traces', + newPath: 'observability-traces-nav#/traces', + appendDatasourceToPath: true, + }, + { + path: 'observability-traces#/services', + icon: 'graphApp', + label: 'View services', + newPath: 'observability-services-nav#/services', + appendDatasourceToPath: true, + }, +]; + +export const otelSpecProvider = function (): SampleDatasetSchema { + return { + id: 'otel', + name: otelDataName, + description: otelDataDescription, + previewImagePath: '/plugins/home/assets/sample_data_resources/otel/otel_traces.png', + darkPreviewImagePath: '/plugins/home/assets/sample_data_resources/otel/otel_traces_dark.png', + hasNewThemeImages: true, + overviewDashboard: '', + getDataSourceIntegratedDashboard: appendDataSourceId(''), + appLinks: initialAppLinks, + defaultIndex: '', + getDataSourceIntegratedDefaultIndex: appendDataSourceId(''), + savedObjects: [], + getDataSourceIntegratedSavedObjects: (dataSourceId?: string, dataSourceTitle?: string) => + getSavedObjectsWithDataSource([], dataSourceId, dataSourceTitle), + getWorkspaceIntegratedSavedObjects: (workspaceId) => + overwriteSavedObjectsWithWorkspaceId([], workspaceId), + dataIndices: [ + { + id: 'otel-v1-apm-span-sample', + dataPath: path.join(__dirname, './sample_traces.json.gz'), + fields: tracesFieldMappings, + timeFields: ['startTime', 'endTime', 'traceGroupFields.endTime'], // TODO: add support for 'events.time' + currentTimeMarker: '2024-10-16T19:00:01', + preserveDayOfWeekTimeOfDay: false, + indexName: 'otel-v1-apm-span-sample', + }, + { + id: 'otel-v1-apm-service-map-sample', + dataPath: path.join(__dirname, './sample_service_map.json.gz'), + fields: servicesFieldMappings, + timeFields: [], + currentTimeMarker: '2024-10-16T19:00:01', + preserveDayOfWeekTimeOfDay: false, + indexName: 'otel-v1-apm-service-map-sample', + }, + { + id: 'ss4o_metrics-otel-sample', + dataPath: path.join(__dirname, './sample_metrics.json.gz'), + fields: metricsFieldMappings, + timeFields: ['@timestamp', 'exemplar.time', 'startTime', 'time', 'observedTimestamp'], + currentTimeMarker: '2024-10-16T19:00:01', + preserveDayOfWeekTimeOfDay: false, + indexName: 'ss4o_metrics-otel-sample', + }, + { + id: 'ss4o_logs-otel-sample', + dataPath: path.join(__dirname, './sample_logs.json.gz'), + fields: logsFieldMappings, + timeFields: ['time', 'observedTime'], + currentTimeMarker: '2024-10-16T19:00:01', + preserveDayOfWeekTimeOfDay: false, + indexName: 'ss4o_logs-otel-sample', + }, + ], + status: 'not_installed', + }; +}; diff --git a/src/plugins/home/server/services/sample_data/data_sets/otel/logs_field_mappings.ts b/src/plugins/home/server/services/sample_data/data_sets/otel/logs_field_mappings.ts new file mode 100644 index 000000000000..fd6bcecbf568 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/data_sets/otel/logs_field_mappings.ts @@ -0,0 +1,343 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const logsFieldMappings = { + body: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + droppedAttributesCount: { + type: 'long', + }, + flags: { + type: 'long', + }, + instrumentationScope: { + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + log: { + properties: { + attributes: { + properties: { + address: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + contentRoot: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + context: { + type: 'keyword', + ignore_above: 256, + }, + envName: { + type: 'keyword', + ignore_above: 256, + }, + otelServiceName: { + type: 'keyword', + ignore_above: 256, + }, + otelSpanID: { + type: 'keyword', + ignore_above: 256, + }, + otelTraceID: { + type: 'keyword', + ignore_above: 256, + }, + otelTraceSampled: { + type: 'boolean', + }, + productId: { + type: 'keyword', + ignore_above: 256, + }, + quantity: { + type: 'long', + }, + userId: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + observedTime: { + type: 'date', + }, + resource: { + properties: { + attributes: { + properties: { + 'container@id': { + type: 'keyword', + ignore_above: 256, + }, + 'docker@cli@cobra@command_path': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'host@arch': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'host@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'os@description': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'os@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'os@type': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'os@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@command': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@command_args': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@command_line': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@executable@path': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@owner': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@pid': { + type: 'long', + }, + 'process@runtime@description': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@runtime@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@runtime@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'service@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'service@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'telemetry@auto@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'telemetry@sdk@language': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'telemetry@sdk@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'telemetry@sdk@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + }, + }, + schemaUrl: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + serviceName: { + type: 'keyword', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + severityNumber: { + type: 'long', + }, + severityText: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + spanId: { + type: 'keyword', + ignore_above: 256, + }, + time: { + type: 'date', + }, + traceId: { + type: 'keyword', + ignore_above: 256, + }, +}; diff --git a/src/plugins/home/server/services/sample_data/data_sets/otel/metrics_field_mappings.ts b/src/plugins/home/server/services/sample_data/data_sets/otel/metrics_field_mappings.ts new file mode 100644 index 000000000000..fad89469bffe --- /dev/null +++ b/src/plugins/home/server/services/sample_data/data_sets/otel/metrics_field_mappings.ts @@ -0,0 +1,915 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const metricsFieldMappings = { + '@timestamp': { + type: 'date', + }, + aggregationTemporality: { + type: 'keyword', + ignore_above: 128, + }, + attributes: { + properties: { + data_stream: { + properties: { + dataset: { + type: 'keyword', + ignore_above: 128, + }, + namespace: { + type: 'keyword', + ignore_above: 128, + }, + type: { + type: 'keyword', + ignore_above: 56, + }, + }, + }, + instrumentationScope: { + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + version: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + metric: { + properties: { + attributes: { + properties: { + action: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'app@ads@ad_request_type': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'app@ads@ad_response_type': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'app@payment@currency': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'client-id': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + count: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + cpu: { + type: 'long', + }, + currency_code: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + daemon: { + type: 'boolean', + }, + device: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + direction: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + dropped: { + type: 'boolean', + }, + family: { + type: 'long', + }, + gc: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + generation: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'http@flavor': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'http@host': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'http@method': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'http@route': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'http@scheme': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'http@status_code': { + type: 'long', + }, + method: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'net@host@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'net@host@port': { + type: 'long', + }, + 'net@peer@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'net@peer@port': { + type: 'long', + }, + 'node-id': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + operation: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + partition: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + pool: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + processor: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + processorType: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + protocol: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'recommendation@type': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'rpc@grpc@status_code': { + type: 'long', + }, + 'rpc@method': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'rpc@service': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'rpc@system': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'service@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'span@kind': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'span@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + state: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + status: { + type: 'long', + }, + 'status@code': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + success: { + type: 'boolean', + }, + target: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + topic: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + type: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + }, + }, + resource: { + properties: { + attributes: { + properties: { + 'container@id': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'docker@cli@cobra@command_path': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'host@arch': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'host@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'os@description': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'os@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'os@type': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'os@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@command': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@command_args': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@command_line': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@executable@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@executable@path': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@owner': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@pid': { + type: 'long', + }, + 'process@runtime@description': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@runtime@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'process@runtime@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'service@instance@id': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'service@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'service@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'telemetry@auto@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'telemetry@sdk@language': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'telemetry@sdk@name': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'telemetry@sdk@version': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + }, + }, + }, + }, + bucketCount: { + type: 'long', + }, + bucketCounts: { + type: 'long', + }, + bucketCountsList: { + type: 'long', + }, + buckets: { + type: 'nested', + properties: { + count: { + type: 'long', + }, + max: { + type: 'float', + }, + min: { + type: 'float', + }, + sum: { + type: 'double', + }, + }, + }, + count: { + type: 'long', + }, + description: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + exemplar: { + properties: { + serviceName: { + type: 'keyword', + ignore_above: 256, + }, + spanId: { + type: 'keyword', + ignore_above: 256, + }, + time: { + type: 'date', + }, + traceId: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + exemplars: { + properties: { + attributes: { + properties: { + exemplar: { + properties: { + attributes: { + properties: { + 'net@sock@peer@addr': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + }, + }, + }, + }, + spanId: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + time: { + type: 'date', + }, + traceId: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + value: { + type: 'float', + }, + }, + }, + explicitBounds: { + type: 'float', + }, + explicitBoundsCount: { + type: 'float', + }, + explicitBoundsList: { + type: 'float', + }, + flags: { + type: 'long', + }, + instrumentationScope: { + properties: { + droppedAttributesCount: { + type: 'integer', + }, + name: { + type: 'keyword', + ignore_above: 256, + }, + schemaUrl: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + version: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + isMonotonic: { + type: 'boolean', + }, + kind: { + type: 'keyword', + ignore_above: 128, + }, + max: { + type: 'float', + }, + min: { + type: 'float', + }, + monotonic: { + type: 'boolean', + }, + name: { + type: 'keyword', + ignore_above: 256, + }, + negativeBuckets: { + type: 'nested', + properties: { + count: { + type: 'long', + }, + max: { + type: 'float', + }, + min: { + type: 'float', + }, + }, + }, + negativeOffset: { + type: 'integer', + }, + observedTimestamp: { + type: 'date_nanos', + }, + positiveBuckets: { + type: 'nested', + properties: { + count: { + type: 'long', + }, + max: { + type: 'float', + }, + min: { + type: 'float', + }, + }, + }, + positiveOffset: { + type: 'integer', + }, + quantileValuesCount: { + type: 'long', + }, + quantiles: { + properties: { + quantile: { + type: 'double', + }, + value: { + type: 'double', + }, + }, + }, + scale: { + type: 'long', + }, + schemaUrl: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + serviceName: { + type: 'keyword', + }, + startTime: { + type: 'date', + }, + sum: { + type: 'float', + }, + time: { + type: 'date', + }, + unit: { + type: 'keyword', + ignore_above: 128, + }, + value: { + type: 'float', + }, + 'value@double': { + type: 'double', + }, + 'value@int': { + type: 'integer', + }, + zeroCount: { + type: 'long', + }, +}; diff --git a/src/plugins/home/server/services/sample_data/data_sets/otel/sample_logs.json.gz b/src/plugins/home/server/services/sample_data/data_sets/otel/sample_logs.json.gz new file mode 100644 index 000000000000..88375837e226 Binary files /dev/null and b/src/plugins/home/server/services/sample_data/data_sets/otel/sample_logs.json.gz differ diff --git a/src/plugins/home/server/services/sample_data/data_sets/otel/sample_metrics.json.gz b/src/plugins/home/server/services/sample_data/data_sets/otel/sample_metrics.json.gz new file mode 100644 index 000000000000..e0fa991cfc16 Binary files /dev/null and b/src/plugins/home/server/services/sample_data/data_sets/otel/sample_metrics.json.gz differ diff --git a/src/plugins/home/server/services/sample_data/data_sets/otel/sample_service_map.json.gz b/src/plugins/home/server/services/sample_data/data_sets/otel/sample_service_map.json.gz new file mode 100644 index 000000000000..8e1c1ef60377 Binary files /dev/null and b/src/plugins/home/server/services/sample_data/data_sets/otel/sample_service_map.json.gz differ diff --git a/src/plugins/home/server/services/sample_data/data_sets/otel/sample_traces.json.gz b/src/plugins/home/server/services/sample_data/data_sets/otel/sample_traces.json.gz new file mode 100644 index 000000000000..5eb988d1785f Binary files /dev/null and b/src/plugins/home/server/services/sample_data/data_sets/otel/sample_traces.json.gz differ diff --git a/src/plugins/home/server/services/sample_data/data_sets/otel/services_field_mappings.ts b/src/plugins/home/server/services/sample_data/data_sets/otel/services_field_mappings.ts new file mode 100644 index 000000000000..a9a475d8d62f --- /dev/null +++ b/src/plugins/home/server/services/sample_data/data_sets/otel/services_field_mappings.ts @@ -0,0 +1,47 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const servicesFieldMappings = { + destination: { + properties: { + domain: { + type: 'keyword', + ignore_above: 1024, + }, + resource: { + type: 'keyword', + ignore_above: 1024, + }, + }, + }, + hashId: { + type: 'keyword', + ignore_above: 1024, + }, + kind: { + type: 'keyword', + ignore_above: 1024, + }, + serviceName: { + type: 'keyword', + ignore_above: 1024, + }, + target: { + properties: { + domain: { + type: 'keyword', + ignore_above: 1024, + }, + resource: { + type: 'keyword', + ignore_above: 1024, + }, + }, + }, + traceGroupName: { + type: 'keyword', + ignore_above: 1024, + }, +}; diff --git a/src/plugins/home/server/services/sample_data/data_sets/otel/traces_field_mappings.ts b/src/plugins/home/server/services/sample_data/data_sets/otel/traces_field_mappings.ts new file mode 100644 index 000000000000..5b4016997f46 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/data_sets/otel/traces_field_mappings.ts @@ -0,0 +1,599 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const tracesFieldMappings = { + droppedAttributesCount: { + type: 'long', + }, + droppedEventsCount: { + type: 'long', + }, + droppedLinksCount: { + type: 'long', + }, + durationInNanos: { + type: 'long', + }, + endTime: { + type: 'date_nanos', + }, + events: { + type: 'nested', + properties: { + attributes: { + properties: { + 'app@payment@transaction@id': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'app@quote@cost@total': { + type: 'float', + }, + 'app@shipping@cost@total': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'app@shipping@tracking@id': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + 'message@id': { + type: 'long', + }, + 'message@type': { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + droppedAttributesCount: { + type: 'long', + }, + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + time: { + type: 'date_nanos', + }, + }, + }, + instrumentationScope: { + properties: { + name: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + version: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + kind: { + type: 'keyword', + ignore_above: 128, + }, + links: { + type: 'nested', + properties: { + attributes: { + type: 'object', + }, + droppedAttributesCount: { + type: 'long', + }, + spanId: { + type: 'keyword', + ignore_above: 256, + }, + traceId: { + type: 'keyword', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + traceState: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, + }, + }, + name: { + type: 'keyword', + ignore_above: 1024, + }, + parentSpanId: { + type: 'keyword', + ignore_above: 256, + }, + resource: { + properties: { + attributes: { + properties: { + 'container@id': { + type: 'keyword', + }, + 'docker@cli@cobra@command_path': { + type: 'keyword', + }, + 'host@arch': { + type: 'keyword', + }, + 'host@name': { + type: 'keyword', + }, + 'os@description': { + type: 'keyword', + }, + 'os@name': { + type: 'keyword', + }, + 'os@type': { + type: 'keyword', + }, + 'os@version': { + type: 'keyword', + }, + 'process@command': { + type: 'keyword', + }, + 'process@command_args': { + type: 'keyword', + }, + 'process@command_line': { + type: 'keyword', + }, + 'process@executable@name': { + type: 'keyword', + }, + 'process@executable@path': { + type: 'keyword', + }, + 'process@owner': { + type: 'keyword', + }, + 'process@pid': { + type: 'keyword', + }, + 'process@runtime@description': { + type: 'keyword', + }, + 'process@runtime@name': { + type: 'keyword', + }, + 'process@runtime@version': { + type: 'keyword', + }, + 'service@instance@id': { + type: 'keyword', + }, + 'service@name': { + type: 'keyword', + }, + 'service@version': { + type: 'keyword', + }, + 'telemetry@auto@version': { + type: 'keyword', + }, + 'telemetry@sdk@language': { + type: 'keyword', + }, + 'telemetry@sdk@name': { + type: 'keyword', + }, + 'telemetry@sdk@version': { + type: 'keyword', + }, + }, + }, + }, + }, + serviceName: { + type: 'keyword', + }, + span: { + properties: { + attributes: { + properties: { + 'app@ads@ad_request_type': { + type: 'keyword', + }, + 'app@ads@ad_response_type': { + type: 'keyword', + }, + 'app@ads@category': { + type: 'keyword', + }, + 'app@ads@contextKeys': { + type: 'keyword', + }, + 'app@ads@contextKeys@count': { + type: 'keyword', + }, + 'app@ads@count': { + type: 'keyword', + }, + 'app@cart@items@count': { + type: 'keyword', + }, + 'app@currency@conversion@from': { + type: 'keyword', + }, + 'app@currency@conversion@to': { + type: 'keyword', + }, + 'app@email@recipient': { + type: 'keyword', + }, + 'app@featureflag@enabled': { + type: 'keyword', + }, + 'app@featureflag@name': { + type: 'keyword', + }, + 'app@filtered_products@count': { + type: 'keyword', + }, + 'app@filtered_products@list': { + type: 'keyword', + }, + 'app@order@amount': { + type: 'keyword', + }, + 'app@order@id': { + type: 'keyword', + }, + 'app@order@items@count': { + type: 'keyword', + }, + 'app@payment@amount': { + type: 'keyword', + }, + 'app@payment@card_type': { + type: 'keyword', + }, + 'app@payment@card_valid': { + type: 'keyword', + }, + 'app@payment@charged': { + type: 'keyword', + }, + 'app@product@id': { + type: 'keyword', + }, + 'app@product@name': { + type: 'keyword', + }, + 'app@product@quantity': { + type: 'keyword', + }, + 'app@products@count': { + type: 'keyword', + }, + 'app@products_recommended@count': { + type: 'keyword', + }, + 'app@quote@cost@total': { + type: 'keyword', + }, + 'app@quote@items@count': { + type: 'keyword', + }, + 'app@recommendation@cache_enabled': { + type: 'keyword', + }, + 'app@shipping@amount': { + type: 'keyword', + }, + 'app@shipping@cost@total': { + type: 'keyword', + }, + 'app@shipping@items@count': { + type: 'keyword', + }, + 'app@shipping@tracking@id': { + type: 'keyword', + }, + 'app@shipping@zip_code': { + type: 'keyword', + }, + 'app@synthetic_request': { + type: 'keyword', + }, + 'app@user@currency': { + type: 'keyword', + }, + 'app@user@id': { + type: 'keyword', + }, + busy_ns: { + type: 'keyword', + }, + 'code@filepath': { + type: 'keyword', + }, + 'code@function': { + type: 'keyword', + }, + 'code@lineno': { + type: 'keyword', + }, + 'code@namespace': { + type: 'keyword', + }, + 'db@instance': { + type: 'keyword', + }, + 'db@name': { + type: 'keyword', + }, + 'db@redis@database_index': { + type: 'keyword', + }, + 'db@redis@flags': { + type: 'keyword', + }, + 'db@statement': { + type: 'keyword', + }, + 'db@system': { + type: 'keyword', + }, + 'db@type': { + type: 'keyword', + }, + 'db@url': { + type: 'keyword', + }, + decode_time_microseconds: { + type: 'keyword', + }, + 'http@client_ip': { + type: 'keyword', + }, + 'http@flavor': { + type: 'keyword', + }, + 'http@host': { + type: 'keyword', + }, + 'http@method': { + type: 'keyword', + }, + 'http@request_content_length': { + type: 'keyword', + }, + 'http@request_content_length_uncompressed': { + type: 'keyword', + }, + 'http@response_content_length': { + type: 'keyword', + }, + 'http@route': { + type: 'keyword', + }, + 'http@scheme': { + type: 'keyword', + }, + 'http@status_code': { + type: 'keyword', + }, + 'http@status_text': { + type: 'keyword', + }, + 'http@target': { + type: 'keyword', + }, + 'http@url': { + type: 'keyword', + }, + 'http@user_agent': { + type: 'keyword', + }, + idle_ns: { + type: 'keyword', + }, + idle_time_microseconds: { + type: 'keyword', + }, + 'messaging@client_id': { + type: 'keyword', + }, + 'messaging@destination@kind': { + type: 'keyword', + }, + 'messaging@destination@name': { + type: 'keyword', + }, + 'messaging@kafka@consumer@group': { + type: 'keyword', + }, + 'messaging@kafka@destination@partition': { + type: 'keyword', + }, + 'messaging@kafka@message@offset': { + type: 'keyword', + }, + 'messaging@message@payload_size_bytes': { + type: 'keyword', + }, + 'messaging@operation': { + type: 'keyword', + }, + 'messaging@system': { + type: 'keyword', + }, + 'net@host@ip': { + type: 'keyword', + }, + 'net@host@name': { + type: 'keyword', + }, + 'net@host@port': { + type: 'keyword', + }, + 'net@peer@ip': { + type: 'keyword', + }, + 'net@peer@name': { + type: 'keyword', + }, + 'net@peer@port': { + type: 'keyword', + }, + 'net@sock@host@addr': { + type: 'keyword', + }, + 'net@sock@peer@addr': { + type: 'keyword', + }, + 'net@sock@peer@port': { + type: 'keyword', + }, + 'net@transport': { + type: 'keyword', + }, + 'peer@service': { + type: 'keyword', + }, + 'phoenix@action': { + type: 'keyword', + }, + 'phoenix@plug': { + type: 'keyword', + }, + query_time_microseconds: { + type: 'keyword', + }, + queue_time_microseconds: { + type: 'keyword', + }, + 'rpc@grpc@status_code': { + type: 'keyword', + }, + 'rpc@method': { + type: 'keyword', + }, + 'rpc@service': { + type: 'keyword', + }, + 'rpc@system': { + type: 'keyword', + }, + 'rpc@user_agent': { + type: 'keyword', + }, + 'sinatra@template_name': { + type: 'keyword', + }, + source: { + type: 'keyword', + }, + 'thread@id': { + type: 'keyword', + }, + 'thread@name': { + type: 'keyword', + }, + total_time_microseconds: { + type: 'keyword', + }, + }, + }, + }, + }, + spanId: { + type: 'keyword', + ignore_above: 256, + }, + startTime: { + type: 'date_nanos', + }, + status: { + properties: { + code: { + type: 'integer', + }, + message: { + type: 'keyword', + }, + }, + }, + traceGroup: { + type: 'keyword', + ignore_above: 1024, + }, + traceGroupFields: { + properties: { + durationInNanos: { + type: 'long', + }, + endTime: { + type: 'date_nanos', + }, + statusCode: { + type: 'integer', + }, + }, + }, + traceId: { + type: 'keyword', + ignore_above: 256, + }, + traceState: { + type: 'text', + fields: { + keyword: { + type: 'keyword', + ignore_above: 256, + }, + }, + }, +}; diff --git a/src/plugins/home/server/services/sample_data/data_sets/util.test.ts b/src/plugins/home/server/services/sample_data/data_sets/util.test.ts index c51e289231dc..4f3863167004 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/util.test.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/util.test.ts @@ -3,9 +3,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { getSavedObjectsWithDataSource, getFinalSavedObjects } from './util'; import { SavedObject, updateDataSourceNameInVegaSpec } from '../../../../../../core/server'; import visualizationObjects from './test_utils/visualization_objects.json'; +import { + getFinalSavedObjects, + getNestedField, + getSavedObjectsWithDataSource, + setNestedField, +} from './util'; describe('getSavedObjectsWithDataSource()', () => { const getVisualizationSavedObjects = (): Array> => { @@ -267,3 +272,67 @@ describe('getFinalSavedObjects()', () => { ).toBe(dataset.savedObjects); }); }); + +describe('getNestedField', () => { + it('should return the value of a top-level field', () => { + const doc = { field1: 'value1', field2: 'value2' }; + const result = getNestedField(doc, 'field1'); + expect(result).toBe('value1'); + }); + + it('should return the value of a nested field', () => { + const doc = { field1: { nestedField: 'nestedValue' } }; + const result = getNestedField(doc, 'field1.nestedField'); + expect(result).toBe('nestedValue'); + }); + + it('should return undefined for a non-existent field', () => { + const doc = { field1: 'value1' }; + const result = getNestedField(doc, 'nonExistentField'); + expect(result).toBeUndefined(); + }); + + it('should handle fields with dots in their names', () => { + const doc = { 'field.with.dot': 'valueWithDot' }; + const result = getNestedField(doc, 'field.with.dot'); + expect(result).toBe('valueWithDot'); + }); + + it('should return undefined for a non-existent nested field', () => { + const doc = { field1: { nestedField: 'nestedValue' } }; + const result = getNestedField(doc, 'field1.nonExistentField'); + expect(result).toBeUndefined(); + }); +}); + +describe('setNestedField', () => { + it('should set the value of a top-level field', () => { + const doc = { field1: 'value1', field2: 'value2' }; + setNestedField(doc, 'field1', 'newValue1'); + expect(doc.field1).toBe('newValue1'); + }); + + it('should set the value of a nested field', () => { + const doc = { field1: { nestedField: 'nestedValue' } }; + setNestedField(doc, 'field1.nestedField', 'newNestedValue'); + expect(doc.field1.nestedField).toBe('newNestedValue'); + }); + + it('should create nested fields if they do not exist', () => { + const doc: any = {}; // Allow dynamic properties + setNestedField(doc, 'field1.nestedField', 'newNestedValue'); + expect(doc.field1).toEqual({ nestedField: 'newNestedValue' }); + }); + + it('should handle fields with dots in their names', () => { + const doc = { 'field.with.dot': 'valueWithDot' }; + setNestedField(doc, 'field.with.dot', 'newValueWithDot'); + expect(doc['field.with.dot']).toBe('newValueWithDot'); + }); + + it('should set a value in deeply nested structures', () => { + const doc = { level1: { level2: { level3: { level4: 'oldValue' } } } }; + setNestedField(doc, 'level1.level2.level3.level4', 'newValue'); + expect(doc.level1.level2.level3.level4).toBe('newValue'); + }); +}); diff --git a/src/plugins/home/server/services/sample_data/data_sets/util.ts b/src/plugins/home/server/services/sample_data/data_sets/util.ts index 4b2cc8b56fa3..77abf6122033 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/util.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/util.ts @@ -5,10 +5,10 @@ import { SavedObject } from 'opensearch-dashboards/server'; import { - extractVegaSpecFromSavedObject, extractTimelineExpression, - updateDataSourceNameInVegaSpec, + extractVegaSpecFromSavedObject, updateDataSourceNameInTimeline, + updateDataSourceNameInVegaSpec, } from '../../../../../../core/server'; import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; @@ -193,3 +193,34 @@ export const getFinalSavedObjects = ({ return dataset.savedObjects; }; + +// Helper function to get a nested field by path +export const getNestedField = (doc: any, path: string) => { + // First check if the exact path exists as a field + if (path in doc) { + return doc[path]; + } + // If not, treat it as a nested path + return path + .split('.') + .reduce((obj, key) => (obj && obj[key] !== 'undefined' ? obj[key] : undefined), doc); +}; + +// Helper function to set a nested field by path +export const setNestedField = (doc: any, path: string, value: any) => { + // First check if the exact path exists as a field + if (path in doc) { + doc[path] = value; + return; + } + // If not, treat it as a nested path + const keys = path.split('.'); + keys.reduce((obj, key, indexName) => { + if (indexName === keys.length - 1) { + obj[key] = value; + } else { + if (!obj[key]) obj[key] = {}; // Create the object if it doesn't exist + return obj[key]; + } + }, doc); +}; diff --git a/src/plugins/home/server/services/sample_data/lib/create_index_name.test.ts b/src/plugins/home/server/services/sample_data/lib/create_index_name.test.ts new file mode 100644 index 000000000000..7c3ac799ee10 --- /dev/null +++ b/src/plugins/home/server/services/sample_data/lib/create_index_name.test.ts @@ -0,0 +1,18 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createIndexName } from './create_index_name'; + +describe('createIndexName', () => { + it('should return the sample dataset name when sampleDataSetId equals dataIndexId', () => { + const result = createIndexName('sample1', 'sample1'); + expect(result).toBe('opensearch_dashboards_sample_data_sample1'); + }); + + it('should return a name with both sampleDataSetId and dataIndexId when they are different', () => { + const result = createIndexName('sample1', 'data1'); + expect(result).toBe('opensearch_dashboards_sample_data_sample1_data1'); + }); +}); diff --git a/src/plugins/home/server/services/sample_data/lib/create_index_name.ts b/src/plugins/home/server/services/sample_data/lib/create_index_name.ts index 532c07d23dca..34314b74e324 100644 --- a/src/plugins/home/server/services/sample_data/lib/create_index_name.ts +++ b/src/plugins/home/server/services/sample_data/lib/create_index_name.ts @@ -29,10 +29,10 @@ */ export const createIndexName = function (sampleDataSetId: string, dataIndexId: string): string { - // Sample data schema was updated to support multiple indices in 6.5. - // This if statement ensures that sample data sets that used a single index prior to the schema change - // have the same index name to avoid orphaned indices when uninstalling. if (sampleDataSetId === dataIndexId) { + // Sample data schema was updated to support multiple indices in 6.5. + // This if statement ensures that sample data sets that used a single index prior to the schema change + // have the same index name to avoid orphaned indices when uninstalling. return `opensearch_dashboards_sample_data_${sampleDataSetId}`; } return `opensearch_dashboards_sample_data_${sampleDataSetId}_${dataIndexId}`; diff --git a/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts index 625427265858..8d68cb93de91 100644 --- a/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts +++ b/src/plugins/home/server/services/sample_data/lib/sample_dataset_registry_types.ts @@ -71,12 +71,19 @@ export interface DataIndexSchema { // Set to true to move timestamp to current week, preserving day of week and time of day // Relative distance from timestamp to currentTimeMarker will not remain the same preserveDayOfWeekTimeOfDay: boolean; + + // Optional indexName field, if added wouldn't all flow would use this name + // `createIndexName` wouldn't be used + indexName?: string; } export interface AppLinkSchema { path: string; icon: string; label: string; + // Alternative app path when new nav flag is enabled + newPath?: string; + appendDatasourceToPath?: boolean; } export interface SampleDatasetSchema { diff --git a/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts b/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts index c202290f911a..6f56a960e458 100644 --- a/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts +++ b/src/plugins/home/server/services/sample_data/lib/sample_dataset_schema.ts @@ -55,12 +55,19 @@ const dataIndexSchema = Joi.object({ // Set to true to move timestamp to current week, preserving day of week and time of day // Relative distance from timestamp to currentTimeMarker will not remain the same preserveDayOfWeekTimeOfDay: Joi.boolean().default(false), + + // Optional indexName field, if added wouldn't all flow would use this name + // `createIndexName` wouldn't be used + indexName: Joi.string(), }); const appLinkSchema = Joi.object({ path: Joi.string().required(), label: Joi.string().required(), icon: Joi.string().required(), + // Alternative app path when new nav flag is enabled + newPath: Joi.string(), + appendDatasourceToPath: Joi.string(), }); export const sampleDataSchema = { diff --git a/src/plugins/home/server/services/sample_data/routes/install.ts b/src/plugins/home/server/services/sample_data/routes/install.ts index 923a57479c3b..4f6d0ca563d6 100644 --- a/src/plugins/home/server/services/sample_data/routes/install.ts +++ b/src/plugins/home/server/services/sample_data/routes/install.ts @@ -30,18 +30,18 @@ import { schema } from '@osd/config-schema'; import { IRouter, LegacyCallAPIOptions, Logger } from 'src/core/server'; -import { getWorkspaceState } from '../../../../../../core/server/utils'; import { SavedObjectsErrorHelpers } from '../../../../../../core/server'; -import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; +import { getWorkspaceState } from '../../../../../../core/server/utils'; +import { getFinalSavedObjects, getNestedField, setNestedField } from '../data_sets/util'; import { createIndexName } from '../lib/create_index_name'; +import { loadData } from '../lib/load_data'; +import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; import { dateToIso8601IgnoringTime, translateTimeRelativeToDifference, translateTimeRelativeToWeek, } from '../lib/translate_timestamp'; -import { loadData } from '../lib/load_data'; import { SampleDataUsageTracker } from '../usage/usage'; -import { getFinalSavedObjects } from '../data_sets/util'; const insertDataIntoIndex = ( dataIndexConfig: any, @@ -54,21 +54,20 @@ const insertDataIntoIndex = ( ) => Promise, logger: Logger ) => { + // Function to update timestamps function updateTimestamps(doc: any) { dataIndexConfig.timeFields - .filter((timeFieldName: string) => doc[timeFieldName]) + .filter((timeFieldName: string) => getNestedField(doc, timeFieldName)) .forEach((timeFieldName: string) => { - doc[timeFieldName] = dataIndexConfig.preserveDayOfWeekTimeOfDay - ? translateTimeRelativeToWeek( - doc[timeFieldName], - dataIndexConfig.currentTimeMarker, - nowReference - ) + const timeValue = getNestedField(doc, timeFieldName); + const updatedTime = dataIndexConfig.preserveDayOfWeekTimeOfDay + ? translateTimeRelativeToWeek(timeValue, dataIndexConfig.currentTimeMarker, nowReference) : translateTimeRelativeToDifference( - doc[timeFieldName], + timeValue, dataIndexConfig.currentTimeMarker, nowReference ); + setNestedField(doc, timeFieldName, updatedTime); }); return doc; } @@ -158,7 +157,8 @@ export function createInstallRoute( for (let i = 0; i < sampleDataset.dataIndices.length; i++) { const dataIndexConfig = sampleDataset.dataIndices[i]; - const index = createIndexName(sampleDataset.id, dataIndexConfig.id); + const index = + dataIndexConfig.indexName ?? createIndexName(sampleDataset.id, dataIndexConfig.id); // clean up any old installation of dataset try { diff --git a/src/plugins/home/server/services/sample_data/routes/list.ts b/src/plugins/home/server/services/sample_data/routes/list.ts index 9a3f1acbead2..209e43d7f63b 100644 --- a/src/plugins/home/server/services/sample_data/routes/list.ts +++ b/src/plugins/home/server/services/sample_data/routes/list.ts @@ -28,11 +28,11 @@ * under the License. */ -import { IRouter } from 'src/core/server'; import { schema } from '@osd/config-schema'; +import { IRouter } from 'src/core/server'; import { getWorkspaceState } from '../../../../../../core/server/utils'; -import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; import { createIndexName } from '../lib/create_index_name'; +import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; const NOT_INSTALLED = 'not_installed'; const INSTALLED = 'installed'; @@ -61,16 +61,17 @@ export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSc previewImagePath: sampleDataset.previewImagePath, darkPreviewImagePath: sampleDataset.darkPreviewImagePath, hasNewThemeImages: sampleDataset.hasNewThemeImages, - overviewDashboard: sampleDataset.getDataSourceIntegratedDashboard( - dataSourceId, - workspaceId - ), + overviewDashboard: sampleDataset.overviewDashboard + ? sampleDataset.getDataSourceIntegratedDashboard(dataSourceId, workspaceId) + : '', appLinks: sampleDataset.appLinks, - defaultIndex: sampleDataset.getDataSourceIntegratedDefaultIndex( - dataSourceId, - workspaceId - ), - dataIndices: sampleDataset.dataIndices.map(({ id }) => ({ id })), + defaultIndex: sampleDataset.defaultIndex + ? sampleDataset.getDataSourceIntegratedDefaultIndex(dataSourceId, workspaceId) + : '', + dataIndices: sampleDataset.dataIndices.map(({ id, indexName }) => ({ + id, + indexName, + })), status: sampleDataset.status, statusMsg: sampleDataset.statusMsg, }; @@ -82,7 +83,8 @@ export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSc for (let i = 0; i < sampleDataset.dataIndices.length; i++) { const dataIndexConfig = sampleDataset.dataIndices[i]; - const index = createIndexName(sampleDataset.id, dataIndexConfig.id); + const index = + dataIndexConfig.indexName ?? createIndexName(sampleDataset.id, dataIndexConfig.id); try { const indexExists = await caller('indices.exists', { index }); @@ -105,19 +107,25 @@ export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSc 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; + + // check dashboards only if a default dashboard is set + if (sampleDataset.overviewDashboard) { + 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; + } + + sampleDataset.status = UNKNOWN; + sampleDataset.statusMsg = err.message; return; } - - sampleDataset.status = UNKNOWN; - sampleDataset.statusMsg = err.message; - return; } - sampleDataset.status = INSTALLED; }); diff --git a/src/plugins/home/server/services/sample_data/routes/uninstall.ts b/src/plugins/home/server/services/sample_data/routes/uninstall.ts index 472bc6bc4aad..3e4636c32486 100644 --- a/src/plugins/home/server/services/sample_data/routes/uninstall.ts +++ b/src/plugins/home/server/services/sample_data/routes/uninstall.ts @@ -32,10 +32,10 @@ import { schema } from '@osd/config-schema'; import _ from 'lodash'; import { IRouter } from 'src/core/server'; import { getWorkspaceState } from '../../../../../../core/server/utils'; -import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; +import { getFinalSavedObjects } from '../data_sets/util'; import { createIndexName } from '../lib/create_index_name'; +import { SampleDatasetSchema } from '../lib/sample_dataset_registry_types'; import { SampleDataUsageTracker } from '../usage/usage'; -import { getFinalSavedObjects } from '../data_sets/util'; export function createUninstallRoute( router: IRouter, @@ -68,7 +68,8 @@ export function createUninstallRoute( for (let i = 0; i < sampleDataset.dataIndices.length; i++) { const dataIndexConfig = sampleDataset.dataIndices[i]; - const index = createIndexName(sampleDataset.id, dataIndexConfig.id); + const index = + dataIndexConfig.indexName ?? createIndexName(sampleDataset.id, dataIndexConfig.id); try { await caller('indices.delete', { index }); diff --git a/src/plugins/home/server/services/sample_data/sample_data_registry.ts b/src/plugins/home/server/services/sample_data/sample_data_registry.ts index d90de532c339..4acbcb0c861d 100644 --- a/src/plugins/home/server/services/sample_data/sample_data_registry.ts +++ b/src/plugins/home/server/services/sample_data/sample_data_registry.ts @@ -29,25 +29,27 @@ */ import Joi from 'joi'; -import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { SavedObject } from 'src/core/public'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { - SampleDatasetProvider, - SampleDatasetSchema, AppLinkSchema, SampleDatasetDashboardPanel, + SampleDatasetProvider, + SampleDatasetSchema, } from './lib/sample_dataset_registry_types'; import { sampleDataSchema } from './lib/sample_dataset_schema'; -import { flightsSpecProvider, logsSpecProvider, ecommerceSpecProvider } from './data_sets'; -import { createListRoute, createInstallRoute } from './routes'; import { UsageCollectionSetup } from '../../../../usage_collection/server'; -import { makeSampleDataUsageCollector, usage } from './usage'; +import { ecommerceSpecProvider, flightsSpecProvider, logsSpecProvider } from './data_sets'; +import { otelSpecProvider } from './data_sets/otel'; +import { createInstallRoute, createListRoute } from './routes'; import { createUninstallRoute } from './routes/uninstall'; +import { makeSampleDataUsageCollector, usage } from './usage'; const flightsSampleDataset = flightsSpecProvider(); const logsSampleDataset = logsSpecProvider(); const ecommerceSampleDataset = ecommerceSpecProvider(); +const otelSampleDataset = otelSpecProvider(); export class SampleDataRegistry { constructor(private readonly initContext: PluginInitializerContext) {} @@ -55,6 +57,7 @@ export class SampleDataRegistry { flightsSampleDataset, logsSampleDataset, ecommerceSampleDataset, + otelSampleDataset, ]; public setup(core: CoreSetup, usageCollections: UsageCollectionSetup | undefined) {