[Discover] Create Discover Shared plugin and features registry (#181952)

## 📓 Summary

Closes #181528 
Closes #181976 

**N.B.** This work was initially reviewed on a separate PR at
https://github.com/tonyghiani/kibana/pull/1.

This implementation aims to have a stateful layer that allows the
management of dependencies between Discover and other plugins, reducing
the need for a direct dependency.

Although the initial thought was to have a plugin to register features
for Discover, there might be other cases in future where we need to
prevent cyclic dependencies.
With this in mind, I created the plugin as a more generic solution to
hold stateful logic as a communication layer for Discover <-> Plugins.

## Discover Shared

Based on some recurring naming in the Kibana codebase, `discover_shared`
felt like the right place for owning these dependencies and exposing
Discover functionalities to the external world.
It is initially pretty simple and only exposes a registry for the
Discover features, but might be a good place to implement other upcoming
concepts related to Discover.

Also, this plugin should ideally never depend on other solution plugins
and keep its dependencies to a bare minimum of packages and core/data
services.

```mermaid
flowchart TD

A(Discover) -- Get --> E[DiscoverShared]
B(Logs Explorer) -- Set --> E[DiscoverShared]
C(Security) -- Set --> E[DiscoverShared]
D(Any app) -- Set --> E[DiscoverShared]
```

## DiscoverFeaturesService

This service initializes and exposes a strictly typed registry to allow
consumer apps to register additional features and Discover and retrieve
them.

The **README** file explains a real use case of when we'd need to use it
and how to do that step-by-step.

Although it introduces a more nested folder structure, I decided to
implement the service as a client-service and expose it through the
plugin lifecycle methods to provide the necessary flexibility we might
need:
- We don't know yet if any of the features we register will be done on
the setup/start steps, so having the registry available in both places
opens the door to any case we face.
- The service is client-only on purpose. My opinion is that if we ever
need to register features such as server services or anything else, it
should be scoped to a similar service dedicated for the server lifecycle
and its environment.
It should never be possible to register the ObsAIAssistant
presentational component from the server, as it should not be permitted
to register a server service in the client registry.
A server DiscoverFeaturesService is not required yet for any feature, so
I left it out to avoid overcomplicating the implementation.

## FeaturesRegistry

To have a strictly typed utility that suggests the available features on
a registry and adheres to a base contract, the registry exposed on the
DiscoverFeaturesService is an instance of the `FeaturesRegistry` class,
which implements the registration/retrieval logic such that:
- If a feature is already registered, is not possible to override it and
an error is thrown to notify the user of the collision.
- In case we need to react to registry changes, is possible to subscribe
to the registry or obtain it as an observable for more complex
scenarios.

The FeaturesRegistry already takes care of the required logic for the
registry, so that `DiscoverFeaturesService`is left with the
responsibility of instantiating/exposing an instance and provide the set
of allowed features.

---------

Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Davis McPhee <davismcphee@hotmail.com>
This commit is contained in:
Marco Antonio Ghiani 2024-05-03 11:27:32 +02:00 committed by GitHub
parent a6491ab360
commit 747ecea18a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 550 additions and 369 deletions

1
.github/CODEOWNERS vendored
View file

@ -370,6 +370,7 @@ examples/developer_examples @elastic/appex-sharedux
examples/discover_customization_examples @elastic/kibana-data-discovery
x-pack/plugins/discover_enhanced @elastic/kibana-data-discovery
src/plugins/discover @elastic/kibana-data-discovery
src/plugins/discover_shared @elastic/kibana-data-discovery @elastic/obs-ux-logs-team
packages/kbn-discover-utils @elastic/kibana-data-discovery
packages/kbn-doc-links @elastic/docs
packages/kbn-docs-utils @elastic/kibana-operations

View file

@ -94,6 +94,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a
|Contains the Discover application and the saved search embeddable.
|{kib-repo}blob/{branch}/src/plugins/discover_shared/README.md[discoverShared]
|A stateful layer to register shared features and provide an access point to discover without a direct dependency.
|{kib-repo}blob/{branch}/src/plugins/embeddable/README.md[embeddable]
|The Embeddables Plugin provides an opportunity to expose reusable interactive widgets that can be embedded outside the original plugin.

View file

@ -420,6 +420,7 @@
"@kbn/discover-customization-examples-plugin": "link:examples/discover_customization_examples",
"@kbn/discover-enhanced-plugin": "link:x-pack/plugins/discover_enhanced",
"@kbn/discover-plugin": "link:src/plugins/discover",
"@kbn/discover-shared-plugin": "link:src/plugins/discover_shared",
"@kbn/discover-utils": "link:packages/kbn-discover-utils",
"@kbn/doc-links": "link:packages/kbn-doc-links",
"@kbn/dom-drag-drop": "link:packages/kbn-dom-drag-drop",

View file

@ -37,6 +37,7 @@ pageLoadAssetSize:
devTools: 38637
discover: 99999
discoverEnhanced: 42730
discoverShared: 17111
embeddable: 87309
embeddableEnhanced: 22107
enterpriseSearch: 50858
@ -143,7 +144,7 @@ pageLoadAssetSize:
snapshotRestore: 79032
spaces: 57868
stackAlerts: 58316
stackConnectors: 52131
stackConnectors: 67227
synthetics: 40958
telemetry: 51957
telemetryManagementSection: 38586

View file

@ -0,0 +1,90 @@
# Discover Shared
A stateful layer to register shared features and provide an access point to discover without a direct dependency.
## Register new features
The plugin exposes a service to register features that can be opinionatedly used in Discover on both the setup and start lifecycle hooks.
Although this allows for greater flexibility, its purpose is not to customize Discover as a default choice but to be used as a solution to prevent cyclic dependency between plugins that interact with Discover.
To register a new feature, let's take a more practical case.
> _We want to introduce the LogsAIAssistant in the Discover flyout. Porting all the logic of the Observability AI Assistant into Discover is not an option, and we don't want Discover to directly depend on the AI Assistant codebase._
We can solve this case with some steps:
### Define a feature registration contract
First of all, we need to define an interface to which the plugin registering the AI Assistant and Discover can adhere.
The `DiscoverFeaturesService` already defines a union of available features and uses them to strictly type the exposed registry from the discover_shared plugin, so we can update it with the new feature:
```tsx
// src/plugins/discover_shared/public/services/discover_features/types.ts
export interface SecurityAIAssistantFeature {
id: 'security-ai-assistant';
render: (/* Update with deps required for this integration */) => React.ReactNode;
// Add any prop required for the feature
}
export interface ObservabilityLogsAIAssistantFeature {
id: 'observability-logs-ai-assistant';
render: (deps: {doc: DataTableRecord}) => React.ReactNode;
// Add any prop required for the feature
}
// This should be a union of all the available client features.
export type DiscoverFeature = SecurityAIAssistantFeature | ObservabilityLogsAIAssistantFeature;
```
### Discover consumes the registered feature
Once we have an interface for the feature, Discover can now retrieve it and use its content if is registered by any app in Kibana.
```tsx
// Somewhere in the unified doc viewer
function LogsOverviewAIAssistant ({ doc }) {
const { discoverShared } = getUnifiedDocViewerServices();
const logsAIAssistantFeature = discoverShared.features.registry.getById('observability-logs-ai-assistant')
if (logsAIAssistantFeature) {
return logsAIAssistantFeature.render({ doc })
}
}
```
### Register the feature
Having an interface for the feature and Discover consuming its definition, we are left with the registration part.
For our example, we'll go to the logs app that owns the LogsAIAssistant codebase and register the feature:
```tsx
// x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
export class LogsSharedPlugin implements LogsSharedClientPluginClass {
// The rest of the plugin implementation is hidden for a cleaner example
public start(core: CoreStart, plugins: LogsSharedClientStartDeps) {
const { observabilityAIAssistant } = plugins;
const LogAIAssistant = createLogAIAssistant({ observabilityAIAssistant });
// Strict typing on the registry will let you know which features you can register
plugins.discoverShared.features.registry.register({
id: 'observability-logs-ai-assistant',
render: ({doc}) => <LogAIAssistant doc={doc}/>
})
return {
LogAIAssistant,
};
}
}
```
At this point, the feature should work correctly when registered and we have not created any direct dependency between the Discover and LogsShared apps.

View file

@ -0,0 +1,58 @@
/*
* 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 { FeaturesRegistry } from './features_registry';
type TestFeature =
| { id: 'feature-id-1'; adHocProperty1?: string }
| { id: 'feature-id-2'; adHocProperty2?: string }
| { id: 'feature-id-3'; adHocProperty3?: string };
describe('FeaturesRegistry', () => {
describe('#register', () => {
test('should add a feature to the registry', () => {
const registry = new FeaturesRegistry<TestFeature>();
registry.register({ id: 'feature-id-1' });
expect(registry.getById('feature-id-1')).toBeDefined();
});
test('should throw an error when a feature is already registered by the given id', () => {
const registry = new FeaturesRegistry<TestFeature>();
registry.register({ id: 'feature-id-1' });
expect(() => registry.register({ id: 'feature-id-1' })).toThrow(
'FeaturesRegistry#register: feature with id "feature-id-1" already exists in the registry.'
);
});
});
describe('#getById', () => {
test('should retrieve a feature by its id', () => {
const registry = new FeaturesRegistry<TestFeature>();
registry.register({ id: 'feature-id-1', adHocProperty1: 'test' });
registry.register({ id: 'feature-id-2', adHocProperty2: 'test' });
expect(registry.getById('feature-id-1')).toEqual({
id: 'feature-id-1',
adHocProperty1: 'test',
});
});
test('should return undefined if there is no feature registered by the given id', () => {
const registry = new FeaturesRegistry<TestFeature>();
registry.register({ id: 'feature-id-1' });
expect(registry.getById('feature-id-2')).toBeUndefined();
});
});
});

View file

@ -0,0 +1,26 @@
/*
* 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 { BaseFeature } from './types';
export class FeaturesRegistry<Feature extends BaseFeature = BaseFeature> {
private readonly features = new Map<Feature['id'], Feature>();
register(feature: Feature): void {
if (this.features.has(feature.id)) {
throw new Error(
`FeaturesRegistry#register: feature with id "${feature.id}" already exists in the registry.`
);
}
this.features.set(feature.id, feature);
}
getById<Id extends Feature['id']>(id: Id) {
return this.features.get(id) as Extract<Feature, { id: Id }> | undefined;
}
}

View file

@ -0,0 +1,9 @@
/*
* 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 { FeaturesRegistry } from './features_registry';

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export interface BaseFeature {
id: string;
}

View file

@ -0,0 +1,9 @@
/*
* 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 { FeaturesRegistry } from './features_registry';

View file

@ -0,0 +1,18 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/src/plugins/discover_shared'],
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/src/plugins/discover_shared',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/src/plugins/discover_shared/{common,public,server}/**/*.{js,ts,tsx}',
],
};

View file

@ -0,0 +1,13 @@
{
"type": "plugin",
"id": "@kbn/discover-shared-plugin",
"owner": ["@elastic/kibana-data-discovery", "@elastic/obs-ux-logs-team"],
"description": "A stateful layer to register shared features and provide an access point to discover without a direct dependency",
"plugin": {
"id": "discoverShared",
"server": false,
"browser": true,
"requiredPlugins": [],
"optionalPlugins": [],
},
}

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { DiscoverSharedPlugin } from './plugin';
export function plugin() {
return new DiscoverSharedPlugin();
}
export type { DiscoverSharedPublicSetup, DiscoverSharedPublicStart } from './types';
export type {
ObservabilityLogsAIAssistantFeatureRenderDeps,
ObservabilityLogsAIAssistantFeature,
DiscoverFeature,
} from './services/discover_features';

View file

@ -0,0 +1,33 @@
/*
* 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 {
createDiscoverFeaturesServiceSetupMock,
createDiscoverFeaturesServiceStartMock,
} from './services/discover_features/discover_features_service.mock';
import { DiscoverSharedPublicSetup, DiscoverSharedPublicStart } from './types';
export type Setup = jest.Mocked<DiscoverSharedPublicSetup>;
export type Start = jest.Mocked<DiscoverSharedPublicStart>;
const createSetupContract = (): Setup => {
return {
features: createDiscoverFeaturesServiceSetupMock(),
};
};
const createStartContract = (): Start => {
return {
features: createDiscoverFeaturesServiceStartMock(),
};
};
export const discoverSharedPluginMock = {
createSetupContract,
createStartContract,
};

View file

@ -0,0 +1,26 @@
/*
* 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 { DiscoverFeaturesService } from './services/discover_features';
import { DiscoverSharedPublicPlugin } from './types';
export class DiscoverSharedPlugin implements DiscoverSharedPublicPlugin {
private discoverFeaturesService: DiscoverFeaturesService = new DiscoverFeaturesService();
public setup() {
return {
features: this.discoverFeaturesService.setup(),
};
}
public start() {
return {
features: this.discoverFeaturesService.start(),
};
}
}

View file

@ -0,0 +1,15 @@
/*
* 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 { FeaturesRegistry } from '../../../common';
import { DiscoverFeature } from './types';
const registry = new FeaturesRegistry<DiscoverFeature>();
export const createDiscoverFeaturesServiceSetupMock = () => ({ registry });
export const createDiscoverFeaturesServiceStartMock = () => ({ registry });

View file

@ -0,0 +1,26 @@
/*
* 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 { FeaturesRegistry } from '../../../common';
import { DiscoverFeature } from './types';
export class DiscoverFeaturesService {
private registry: FeaturesRegistry<DiscoverFeature> = new FeaturesRegistry();
public setup() {
return {
registry: this.registry,
};
}
public start() {
return {
registry: this.registry,
};
}
}

View file

@ -0,0 +1,10 @@
/*
* 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 * from './discover_features_service';
export * from './types';

View file

@ -0,0 +1,44 @@
/*
* 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 { DataTableRecord } from '@kbn/discover-utils';
import { FeaturesRegistry } from '../../../common';
/**
* Features types
* Here goes the contract definition for the client features that can be registered
* and that will be consumed by Discover.
*/
/**
* Allow to register an AIAssistant scoped to investigate log entries.
* It will be opinionatedly used as an additional tool to investigate a log document and
* will be shown on the logs-overview preset tab of the UnifiedDocViewer.
*/
export interface ObservabilityLogsAIAssistantFeatureRenderDeps {
doc: DataTableRecord;
}
export interface ObservabilityLogsAIAssistantFeature {
id: 'observability-logs-ai-assistant';
render: (deps: ObservabilityLogsAIAssistantFeatureRenderDeps) => JSX.Element;
}
// This should be a union of all the available client features.
export type DiscoverFeature = ObservabilityLogsAIAssistantFeature;
/**
* Service types
*/
export interface DiscoverFeaturesServiceSetup {
registry: FeaturesRegistry<DiscoverFeature>;
}
export interface DiscoverFeaturesServiceStart {
registry: FeaturesRegistry<DiscoverFeature>;
}

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { Plugin } from '@kbn/core/public';
import {
DiscoverFeaturesServiceSetup,
DiscoverFeaturesServiceStart,
} from './services/discover_features';
export interface DiscoverSharedPublicSetup {
features: DiscoverFeaturesServiceSetup;
}
export interface DiscoverSharedPublicStart {
features: DiscoverFeaturesServiceStart;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface DiscoverSharedPublicSetupDeps {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface DiscoverSharedPublicStartDeps {}
export type DiscoverSharedPublicPlugin = Plugin<
DiscoverSharedPublicSetup,
DiscoverSharedPublicStart,
DiscoverSharedPublicSetupDeps,
DiscoverSharedPublicStartDeps
>;

View file

@ -0,0 +1,17 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types"
},
"include": [
"common/**/*",
"public/**/*",
"server/**/*",
"../../../typings/**/*",
],
"exclude": ["target/**/*"],
"kbn_references": [
"@kbn/discover-utils",
"@kbn/core",
]
}

View file

@ -8,6 +8,6 @@
"server": false,
"browser": true,
"requiredBundles": ["kibanaUtils"],
"requiredPlugins": ["data", "fieldFormats"],
"requiredPlugins": ["data", "discoverShared", "fieldFormats"],
}
}

View file

@ -8,6 +8,7 @@
import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { discoverSharedPluginMock } from '@kbn/discover-shared-plugin/public/mocks';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
import type { UnifiedDocViewerServices, UnifiedDocViewerStart } from '../types';
@ -21,6 +22,7 @@ export const mockUnifiedDocViewer: jest.Mocked<UnifiedDocViewerStart> = {
export const mockUnifiedDocViewerServices: jest.Mocked<UnifiedDocViewerServices> = {
analytics: analyticsServiceMock.createAnalyticsServiceStart(),
data: dataPluginMock.createStartContract(),
discoverShared: discoverSharedPluginMock.createStartContract(),
fieldFormats: fieldFormatsMock,
storage: new Storage(localStorage),
uiSettings: uiSettingsServiceMock.createStartContract(),

View file

@ -14,6 +14,7 @@ import { LogsOverviewHeader } from './logs_overview_header';
import { LogsOverviewHighlights } from './logs_overview_highlights';
import { FieldActionsProvider } from '../../hooks/use_field_actions';
import { getUnifiedDocViewerServices } from '../../plugin';
import { LogsOverviewAIAssistant } from './logs_overview_ai_assistant';
export function LogsOverview({
columns,
@ -37,6 +38,7 @@ export function LogsOverview({
<LogsOverviewHeader doc={parsedDoc} />
<EuiHorizontalRule margin="xs" />
<LogsOverviewHighlights formattedDoc={parsedDoc} flattenedDoc={hit.flattened} />
<LogsOverviewAIAssistant doc={hit} />
</FieldActionsProvider>
);
}

View file

@ -0,0 +1,24 @@
/*
* 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 { DataTableRecord } from '@kbn/discover-utils';
import { getUnifiedDocViewerServices } from '../../plugin';
export function LogsOverviewAIAssistant({ doc }: { doc: DataTableRecord }) {
const { discoverShared } = getUnifiedDocViewerServices();
const logsAIAssistantFeature = discoverShared.features.registry.getById(
'observability-logs-ai-assistant'
);
if (!logsAIAssistantFeature) {
return null;
}
return logsAIAssistantFeature.render({ doc });
}

View file

@ -17,6 +17,7 @@ import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { CoreStart } from '@kbn/core/public';
import { dynamic } from '@kbn/shared-ux-utility';
import { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public';
import type { UnifiedDocViewerServices } from './types';
export const [getUnifiedDocViewerServices, setUnifiedDocViewerServices] =
@ -47,6 +48,7 @@ export interface UnifiedDocViewerStart {
export interface UnifiedDocViewerStartDeps {
data: DataPublicPluginStart;
discoverShared: DiscoverSharedPublicStart;
fieldFormats: FieldFormatsStart;
}
@ -116,12 +118,20 @@ export class UnifiedDocViewerPublicPlugin
public start(core: CoreStart, deps: UnifiedDocViewerStartDeps) {
const { analytics, uiSettings } = core;
const { data, fieldFormats } = deps;
const { data, discoverShared, fieldFormats } = deps;
const storage = new Storage(localStorage);
const unifiedDocViewer = {
registry: this.docViewsRegistry,
};
const services = { analytics, data, fieldFormats, storage, uiSettings, unifiedDocViewer };
const services = {
analytics,
data,
discoverShared,
fieldFormats,
storage,
uiSettings,
unifiedDocViewer,
};
setUnifiedDocViewerServices(services);
return unifiedDocViewer;
}

View file

@ -12,6 +12,7 @@ export type { UnifiedDocViewerSetup, UnifiedDocViewerStart } from './plugin';
import type { AnalyticsServiceStart } from '@kbn/core-analytics-browser';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { Storage } from '@kbn/kibana-utils-plugin/public';
import type { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
@ -20,6 +21,7 @@ import type { UnifiedDocViewerStart } from './plugin';
export interface UnifiedDocViewerServices {
analytics: AnalyticsServiceStart;
data: DataPublicPluginStart;
discoverShared: DiscoverSharedPublicStart;
fieldFormats: FieldFormatsStart;
storage: Storage;
uiSettings: IUiSettingsClient;

View file

@ -28,7 +28,8 @@
"@kbn/code-editor-mock",
"@kbn/custom-icons",
"@kbn/react-field",
"@kbn/ui-theme"
"@kbn/ui-theme",
"@kbn/discover-shared-plugin"
],
"exclude": [
"target/**/*",

View file

@ -734,6 +734,8 @@
"@kbn/discover-enhanced-plugin/*": ["x-pack/plugins/discover_enhanced/*"],
"@kbn/discover-plugin": ["src/plugins/discover"],
"@kbn/discover-plugin/*": ["src/plugins/discover/*"],
"@kbn/discover-shared-plugin": ["src/plugins/discover_shared"],
"@kbn/discover-shared-plugin/*": ["src/plugins/discover_shared/*"],
"@kbn/discover-utils": ["packages/kbn-discover-utils"],
"@kbn/discover-utils/*": ["packages/kbn-discover-utils/*"],
"@kbn/doc-links": ["packages/kbn-doc-links"],

View file

@ -10,10 +10,6 @@ import { i18n } from '@kbn/i18n';
import { EuiCode } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
export const flyoutContentLabel = i18n.translate('xpack.logsExplorer.flyoutDetail.label.message', {
defaultMessage: 'Content breakdown',
});
export const contentLabel = i18n.translate('xpack.logsExplorer.dataTable.header.popover.content', {
defaultMessage: 'Content',
});
@ -36,113 +32,6 @@ export const actionsLabelLowerCase = i18n.translate(
}
);
export const flyoutServiceLabel = i18n.translate('xpack.logsExplorer.flyoutDetail.label.service', {
defaultMessage: 'Service',
});
export const flyoutTraceLabel = i18n.translate('xpack.logsExplorer.flyoutDetail.label.trace', {
defaultMessage: 'Trace',
});
export const flyoutHostNameLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.hostName',
{
defaultMessage: 'Host name',
}
);
export const serviceInfraAccordionTitle = i18n.translate(
'xpack.logsExplorer.flyoutDetail.accordion.title.serviceInfra',
{
defaultMessage: 'Service & Infrastructure',
}
);
export const cloudAccordionTitle = i18n.translate(
'xpack.logsExplorer.flyoutDetail.accordion.title.cloud',
{
defaultMessage: 'Cloud',
}
);
export const otherAccordionTitle = i18n.translate(
'xpack.logsExplorer.flyoutDetail.accordion.title.other',
{
defaultMessage: 'Other',
}
);
export const flyoutOrchestratorClusterNameLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.orchestratorClusterName',
{
defaultMessage: 'Orchestrator cluster Name',
}
);
export const flyoutOrchestratorResourceIdLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.orchestratorResourceId',
{
defaultMessage: 'Orchestrator resource ID',
}
);
export const flyoutCloudProviderLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.cloudProvider',
{
defaultMessage: 'Cloud provider',
}
);
export const flyoutCloudRegionLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.cloudRegion',
{
defaultMessage: 'Cloud region',
}
);
export const flyoutCloudAvailabilityZoneLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.cloudAvailabilityZone',
{
defaultMessage: 'Cloud availability zone',
}
);
export const flyoutCloudProjectIdLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.cloudProjectId',
{
defaultMessage: 'Cloud project ID',
}
);
export const flyoutCloudInstanceIdLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.cloudInstanceId',
{
defaultMessage: 'Cloud instance ID',
}
);
export const flyoutLogPathFileLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.logPathFile',
{
defaultMessage: 'Log path file',
}
);
export const flyoutNamespaceLabel = i18n.translate(
'xpack.logsExplorer.flyoutDetail.label.namespace',
{
defaultMessage: 'Namespace',
}
);
export const flyoutDatasetLabel = i18n.translate('xpack.logsExplorer.flyoutDetail.label.dataset', {
defaultMessage: 'Dataset',
});
export const flyoutShipperLabel = i18n.translate('xpack.logsExplorer.flyoutDetail.label.shipper', {
defaultMessage: 'Shipper',
});
export const actionFilterForText = (text: string) =>
i18n.translate('xpack.logsExplorer.flyoutDetail.value.hover.filterFor', {
defaultMessage: 'Filter for this {value}',
@ -167,27 +56,6 @@ export const filterForText = i18n.translate('xpack.logsExplorer.popoverAction.fi
defaultMessage: 'Filter for',
});
export const flyoutHoverActionFilterForFieldPresentText = i18n.translate(
'xpack.logsExplorer.flyoutDetail.value.hover.filterForFieldPresent',
{
defaultMessage: 'Filter for field present',
}
);
export const flyoutHoverActionToggleColumnText = i18n.translate(
'xpack.logsExplorer.flyoutDetail.value.hover.toggleColumn',
{
defaultMessage: 'Toggle column in table',
}
);
export const flyoutHoverActionCopyToClipboardText = i18n.translate(
'xpack.logsExplorer.flyoutDetail.value.hover.copyToClipboard',
{
defaultMessage: 'Copy to clipboard',
}
);
export const copyValueText = i18n.translate('xpack.logsExplorer.popoverAction.copyValue', {
defaultMessage: 'Copy value',
});
@ -200,14 +68,6 @@ export const copyValueAriaText = (fieldName: string) =>
},
});
export const flyoutAccordionShowMoreText = (count: number) =>
i18n.translate('xpack.logsExplorer.flyoutDetail.section.showMore', {
defaultMessage: '+ {hiddenCount} more',
values: {
hiddenCount: count,
},
});
export const openCellActionPopoverAriaText = i18n.translate(
'xpack.logsExplorer.popoverAction.openPopover',
{
@ -227,7 +87,9 @@ export const contentHeaderTooltipParagraph1 = (
id="xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1"
defaultMessage="Displays the document's {logLevel} and {message} fields."
values={{
// eslint-disable-next-line @kbn/i18n/strings_should_be_translated_with_i18n
logLevel: <strong>log.level</strong>,
// eslint-disable-next-line @kbn/i18n/strings_should_be_translated_with_i18n
message: <strong>message</strong>,
}}
/>

View file

@ -1,63 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { DocViewRenderProps } from '@kbn/unified-doc-viewer/types';
import { LogsExplorerFlyoutContentProps } from './types';
import { useLogsExplorerControllerContext } from '../controller';
import { LogDocument } from '../../common/document';
const CustomFlyoutContent = ({
filter,
onAddColumn,
onRemoveColumn,
dataView,
hit,
}: DocViewRenderProps) => {
const {
customizations: { flyout },
} = useLogsExplorerControllerContext();
const flyoutContentProps: LogsExplorerFlyoutContentProps = useMemo(
() => ({
actions: {
addFilter: filter,
addColumn: onAddColumn,
removeColumn: onRemoveColumn,
},
dataView,
doc: hit as LogDocument,
}),
[filter, onAddColumn, onRemoveColumn, dataView, hit]
);
const renderCustomizedContent = useMemo(
() => flyout?.renderContent?.(renderContent) ?? renderContent,
[flyout]
);
return (
<>
<EuiSpacer size="m" />
<EuiFlexGroup direction="column">
{/* Apply custom Logs Explorer detail */}
{renderCustomizedContent(flyoutContentProps)}
</EuiFlexGroup>
</>
);
};
const renderContent = ({ actions, dataView, doc }: LogsExplorerFlyoutContentProps) => (
<EuiFlexItem>
{/* TOREMOVE */}
{/* <FlyoutDetail actions={actions} dataView={dataView} doc={doc} /> */}
</EuiFlexItem>
);
// eslint-disable-next-line import/no-default-export
export default CustomFlyoutContent;

View file

@ -5,27 +5,8 @@
* 2.0.
*/
import React from 'react';
import { DocViewRenderProps } from '@kbn/unified-doc-viewer/src/services/types';
import { LogDocument } from '../../common/document';
import { LogsExplorerControllerContext } from '../state_machines/logs_explorer_controller';
export type RenderPreviousContent<Props> = (props: Props) => React.ReactNode;
export type RenderContentCustomization<Props> = (
renderPreviousContent: RenderPreviousContent<Props>
) => (props: Props) => React.ReactNode;
export interface LogsExplorerFlyoutContentProps {
actions: {
addFilter: DocViewRenderProps['filter'];
addColumn: DocViewRenderProps['onAddColumn'];
removeColumn: DocViewRenderProps['onRemoveColumn'];
};
dataView: DocViewRenderProps['dataView'];
doc: LogDocument;
}
export type OnUknownDataViewSelectionHandler = (context: LogsExplorerControllerContext) => void;
export interface LogsExplorerCustomizationEvents {
@ -33,8 +14,5 @@ export interface LogsExplorerCustomizationEvents {
}
export interface LogsExplorerCustomizations {
flyout?: {
renderContent?: RenderContentCustomization<LogsExplorerFlyoutContentProps>;
};
events?: LogsExplorerCustomizationEvents;
}

View file

@ -18,7 +18,6 @@ export type {
export type {
LogsExplorerCustomizations,
LogsExplorerCustomizationEvents,
LogsExplorerFlyoutContentProps,
} from './customizations/types';
export type { LogsExplorerControllerContext } from './state_machines/logs_explorer_controller';
export type { LogsExplorerPluginSetup, LogsExplorerPluginStart } from './types';

View file

@ -42,7 +42,6 @@
"@kbn/shared-ux-utility",
"@kbn/ui-theme",
"@kbn/unified-data-table",
"@kbn/unified-doc-viewer",
"@kbn/unified-field-list",
"@kbn/unified-search-plugin",
"@kbn/xstate-utils",

View file

@ -11,6 +11,7 @@
"requiredPlugins": [
"data",
"dataViews",
"discoverShared",
"usageCollection",
"observabilityShared",
"share"

View file

@ -4,9 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import React, { useMemo } from 'react';
import { dynamic } from '@kbn/shared-ux-utility';
import { LogAIAssistantProps } from './log_ai_assistant';
import { ObservabilityLogsAIAssistantFeatureRenderDeps } from '@kbn/discover-shared-plugin/public';
import { LogAIAssistantDocument, LogAIAssistantProps } from './log_ai_assistant';
export const LogAIAssistant = dynamic(() => import('./log_ai_assistant'));
@ -17,3 +18,19 @@ export function createLogAIAssistant({
<LogAIAssistant observabilityAIAssistant={observabilityAIAssistant} {...props} />
);
}
export const createLogsAIAssistantRenderer =
(LogAIAssistantRender: ReturnType<typeof createLogAIAssistant>) =>
({ doc }: ObservabilityLogsAIAssistantFeatureRenderDeps) => {
const mappedDoc = useMemo(
() => ({
fields: Object.entries(doc.flattened).map(([field, value]) => ({
field,
value,
})) as LogAIAssistantDocument['fields'],
}),
[doc]
);
return <LogAIAssistantRender key={doc.id} doc={mappedDoc} />;
};

View file

@ -11,7 +11,7 @@ import {
NodeLogsLocatorDefinition,
TraceLogsLocatorDefinition,
} from '../common/locators';
import { createLogAIAssistant } from './components/log_ai_assistant';
import { createLogAIAssistant, createLogsAIAssistantRenderer } from './components/log_ai_assistant';
import { LogViewsService } from './services/log_views';
import {
LogsSharedClientCoreSetup,
@ -52,7 +52,7 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
public start(core: CoreStart, plugins: LogsSharedClientStartDeps) {
const { http } = core;
const { data, dataViews, observabilityAIAssistant } = plugins;
const { data, dataViews, discoverShared, observabilityAIAssistant } = plugins;
const logViews = this.logViews.start({
http,
@ -68,6 +68,11 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
const LogAIAssistant = createLogAIAssistant({ observabilityAIAssistant });
discoverShared.features.registry.register({
id: 'observability-logs-ai-assistant',
render: createLogsAIAssistantRenderer(LogAIAssistant),
});
return {
logViews,
LogAIAssistant,

View file

@ -8,6 +8,7 @@
import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public';
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
@ -35,6 +36,7 @@ export interface LogsSharedClientSetupDeps {
export interface LogsSharedClientStartDeps {
data: DataPublicPluginStart;
dataViews: DataViewsPublicPluginStart;
discoverShared: DiscoverSharedPublicStart;
observabilityAIAssistant?: ObservabilityAIAssistantPublicStart;
uiActions: UiActionsStart;
}

View file

@ -35,6 +35,7 @@
"@kbn/observability-ai-assistant-plugin",
"@kbn/deeplinks-observability",
"@kbn/share-plugin",
"@kbn/shared-ux-utility"
"@kbn/shared-ux-utility",
"@kbn/discover-shared-plugin"
]
}

View file

@ -1,62 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiFlexItem } from '@elastic/eui';
import {
LogsExplorerCustomizations,
LogsExplorerFlyoutContentProps,
} from '@kbn/logs-explorer-plugin/public';
import type {
LogAIAssistantDocument,
LogsSharedClientStartExports,
} from '@kbn/logs-shared-plugin/public';
import React, { useMemo } from 'react';
import { useKibanaContextForPlugin } from '../utils/use_kibana';
type RenderFlyoutContentCustomization =
Required<LogsExplorerCustomizations>['flyout']['renderContent'];
const ObservabilityLogAIAssistant = ({
doc,
LogAIAssistant,
}: LogsExplorerFlyoutContentProps & {
LogAIAssistant: Required<LogsSharedClientStartExports>['LogAIAssistant'];
}) => {
const mappedDoc = useMemo(() => mapDocToAIAssistantFormat(doc), [doc]);
return <LogAIAssistant key={doc.id} doc={mappedDoc} />;
};
export const renderFlyoutContent: RenderFlyoutContentCustomization =
(renderPreviousContent) => (props) => {
const { services } = useKibanaContextForPlugin();
const { LogAIAssistant } = services.logsShared;
return (
<>
{renderPreviousContent(props)}
{LogAIAssistant ? (
<EuiFlexItem>
<ObservabilityLogAIAssistant {...props} LogAIAssistant={LogAIAssistant} />
</EuiFlexItem>
) : null}
</>
);
};
/**
* Utils
*/
const mapDocToAIAssistantFormat = (doc: LogsExplorerFlyoutContentProps['doc']) => {
if (!doc) return;
return {
fields: Object.entries(doc.flattened).map(([field, value]) => ({
field,
value,
})) as LogAIAssistantDocument['fields'],
};
};

View file

@ -8,7 +8,6 @@
import { CreateLogsExplorerController } from '@kbn/logs-explorer-plugin/public';
import { PluginKibanaContextValue } from '../utils/use_kibana';
import { createOnUknownDataViewSelectionHandler } from './discover_navigation_handler';
import { renderFlyoutContent } from './flyout_content';
export const createLogsExplorerControllerWithCustomizations =
(
@ -23,8 +22,5 @@ export const createLogsExplorerControllerWithCustomizations =
events: {
onUknownDataViewSelection: createOnUknownDataViewSelectionHandler(services.discover),
},
flyout: {
renderContent: renderFlyoutContent,
},
},
});

View file

@ -24116,7 +24116,6 @@
"xpack.lists.exceptions.field.mappingConflict.description": "Ce champ est défini avec différents types dans les index suivants ou il n'est pas mappé, ce qui peut entraîner des résultats inattendus lors des requêtes.",
"xpack.lists.exceptions.orDescription": "OR",
"xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "Affiche le {logLevel} du document et les champs {message}.",
"xpack.logsExplorer.flyoutDetail.section.showMore": "+ {hiddenCount} autres",
"xpack.logsExplorer.flyoutDetail.value.hover.filterFor": "Filtrer sur cette {value}",
"xpack.logsExplorer.flyoutDetail.value.hover.filterOut": "Exclure cette {value}",
"xpack.logsExplorer.popoverAction.copyValueAriaText": "Copier la valeur de {fieldName}",
@ -24131,28 +24130,7 @@
"xpack.logsExplorer.dataTable.header.popover.content": "Contenu",
"xpack.logsExplorer.dataTable.header.popover.resource": "Ressource",
"xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "Les champs fournissant des informations sur la source du document, comme :",
"xpack.logsExplorer.flyoutDetail.accordion.title.cloud": "Cloud",
"xpack.logsExplorer.flyoutDetail.accordion.title.other": "Autre",
"xpack.logsExplorer.flyoutDetail.accordion.title.serviceInfra": "Service et Infrastructure",
"xpack.logsExplorer.flyoutDetail.label.cloudAvailabilityZone": "Zone de disponibilité du cloud",
"xpack.logsExplorer.flyoutDetail.label.cloudInstanceId": "ID d'instance du cloud",
"xpack.logsExplorer.flyoutDetail.label.cloudProjectId": "ID de projet du cloud",
"xpack.logsExplorer.flyoutDetail.label.cloudProvider": "Fournisseur cloud",
"xpack.logsExplorer.flyoutDetail.label.cloudRegion": "Région du cloud",
"xpack.logsExplorer.flyoutDetail.label.dataset": "Ensemble de données",
"xpack.logsExplorer.flyoutDetail.label.hostName": "Nom d'hôte",
"xpack.logsExplorer.flyoutDetail.label.logPathFile": "Fichier de chemin d'accès au log",
"xpack.logsExplorer.flyoutDetail.label.message": "Répartition du contenu",
"xpack.logsExplorer.flyoutDetail.label.namespace": "Espace de nom",
"xpack.logsExplorer.flyoutDetail.label.orchestratorClusterName": "Nom de cluster de l'orchestrateur",
"xpack.logsExplorer.flyoutDetail.label.orchestratorResourceId": "ID de ressource de l'orchestrateur",
"xpack.logsExplorer.flyoutDetail.label.service": "Service",
"xpack.logsExplorer.flyoutDetail.label.shipper": "Agent de transfert",
"xpack.logsExplorer.flyoutDetail.label.trace": "Trace",
"xpack.logsExplorer.flyoutDetail.title": "Détails du log",
"xpack.logsExplorer.flyoutDetail.value.hover.copyToClipboard": "Copier dans le presse-papiers",
"xpack.logsExplorer.flyoutDetail.value.hover.filterForFieldPresent": "Filtrer sur le champ",
"xpack.logsExplorer.flyoutDetail.value.hover.toggleColumn": "Afficher/Masquer la colonne dans le tableau",
"xpack.logsExplorer.grid.closePopover": "Fermer la fenêtre contextuelle",
"xpack.logsExplorer.popoverAction.closePopover": "Fermer la fenêtre contextuelle",
"xpack.logsExplorer.popoverAction.copyValue": "Copier la valeur",

View file

@ -24091,7 +24091,6 @@
"xpack.lists.exceptions.field.mappingConflict.description": "このフィールドは、次のインデックスで別の型として定義されているか、マッピングされていません。これにより、予期しないクエリ結果になる場合があります。",
"xpack.lists.exceptions.orDescription": "OR",
"xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "ドキュメントの{logLevel}と{message}フィールドを表示します。",
"xpack.logsExplorer.flyoutDetail.section.showMore": "+ その他{hiddenCount}件",
"xpack.logsExplorer.flyoutDetail.value.hover.filterFor": "この{value}でフィルターを適用",
"xpack.logsExplorer.flyoutDetail.value.hover.filterOut": "この{value}を除外",
"xpack.logsExplorer.popoverAction.copyValueAriaText": "{fieldName}の値をコピー",
@ -24106,28 +24105,7 @@
"xpack.logsExplorer.dataTable.header.popover.content": "コンテンツ",
"xpack.logsExplorer.dataTable.header.popover.resource": "リソース",
"xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "次のようなドキュメントのソースに関する情報を提供するフィールド:",
"xpack.logsExplorer.flyoutDetail.accordion.title.cloud": "クラウド",
"xpack.logsExplorer.flyoutDetail.accordion.title.other": "その他",
"xpack.logsExplorer.flyoutDetail.accordion.title.serviceInfra": "サービスとインフラストラクチャー",
"xpack.logsExplorer.flyoutDetail.label.cloudAvailabilityZone": "クラウドアベイラビリティゾーン",
"xpack.logsExplorer.flyoutDetail.label.cloudInstanceId": "クラウドインスタンスID",
"xpack.logsExplorer.flyoutDetail.label.cloudProjectId": "クラウドプロジェクトID",
"xpack.logsExplorer.flyoutDetail.label.cloudProvider": "クラウドプロバイダー",
"xpack.logsExplorer.flyoutDetail.label.cloudRegion": "クラウドリージョン",
"xpack.logsExplorer.flyoutDetail.label.dataset": "データセット",
"xpack.logsExplorer.flyoutDetail.label.hostName": "ホスト名",
"xpack.logsExplorer.flyoutDetail.label.logPathFile": "ログパスファイル",
"xpack.logsExplorer.flyoutDetail.label.message": "コンテンツの内訳",
"xpack.logsExplorer.flyoutDetail.label.namespace": "名前空間",
"xpack.logsExplorer.flyoutDetail.label.orchestratorClusterName": "オーケストレータークラスター名",
"xpack.logsExplorer.flyoutDetail.label.orchestratorResourceId": "オーケストレーターリソースID",
"xpack.logsExplorer.flyoutDetail.label.service": "サービス",
"xpack.logsExplorer.flyoutDetail.label.shipper": "シッパー",
"xpack.logsExplorer.flyoutDetail.label.trace": "トレース",
"xpack.logsExplorer.flyoutDetail.title": "ログの詳細",
"xpack.logsExplorer.flyoutDetail.value.hover.copyToClipboard": "クリップボードにコピー",
"xpack.logsExplorer.flyoutDetail.value.hover.filterForFieldPresent": "フィールド表示のフィルター",
"xpack.logsExplorer.flyoutDetail.value.hover.toggleColumn": "表の列を切り替える",
"xpack.logsExplorer.grid.closePopover": "ポップオーバーを閉じる",
"xpack.logsExplorer.popoverAction.closePopover": "ポップオーバーを閉じる",
"xpack.logsExplorer.popoverAction.copyValue": "値をコピー",

View file

@ -24124,7 +24124,6 @@
"xpack.lists.exceptions.field.mappingConflict.description": "此字段在以下索引中定义为不同类型或未映射。这可能导致意外的查询结果。",
"xpack.lists.exceptions.orDescription": "OR",
"xpack.logsExplorer.dataTable.header.content.tooltip.paragraph1": "显示该文档的 {logLevel} 和 {message} 字段。",
"xpack.logsExplorer.flyoutDetail.section.showMore": "+ 另外 {hiddenCount} 个",
"xpack.logsExplorer.flyoutDetail.value.hover.filterFor": "筛留此 {value}",
"xpack.logsExplorer.flyoutDetail.value.hover.filterOut": "筛除此 {value}",
"xpack.logsExplorer.popoverAction.copyValueAriaText": "复制 {fieldName} 的值",
@ -24139,28 +24138,7 @@
"xpack.logsExplorer.dataTable.header.popover.content": "内容",
"xpack.logsExplorer.dataTable.header.popover.resource": "资源",
"xpack.logsExplorer.dataTable.header.resource.tooltip.paragraph": "提供有关文档来源信息的字段,例如:",
"xpack.logsExplorer.flyoutDetail.accordion.title.cloud": "云",
"xpack.logsExplorer.flyoutDetail.accordion.title.other": "其他",
"xpack.logsExplorer.flyoutDetail.accordion.title.serviceInfra": "服务和基础设施",
"xpack.logsExplorer.flyoutDetail.label.cloudAvailabilityZone": "云可用区",
"xpack.logsExplorer.flyoutDetail.label.cloudInstanceId": "云实例 ID",
"xpack.logsExplorer.flyoutDetail.label.cloudProjectId": "云项目 ID",
"xpack.logsExplorer.flyoutDetail.label.cloudProvider": "云服务提供商",
"xpack.logsExplorer.flyoutDetail.label.cloudRegion": "云区域",
"xpack.logsExplorer.flyoutDetail.label.dataset": "数据集",
"xpack.logsExplorer.flyoutDetail.label.hostName": "主机名",
"xpack.logsExplorer.flyoutDetail.label.logPathFile": "日志路径文件",
"xpack.logsExplorer.flyoutDetail.label.message": "内容细目",
"xpack.logsExplorer.flyoutDetail.label.namespace": "命名空间",
"xpack.logsExplorer.flyoutDetail.label.orchestratorClusterName": "Orchestrator 集群名称",
"xpack.logsExplorer.flyoutDetail.label.orchestratorResourceId": "Orchestrator 资源 ID",
"xpack.logsExplorer.flyoutDetail.label.service": "服务",
"xpack.logsExplorer.flyoutDetail.label.shipper": "采集器",
"xpack.logsExplorer.flyoutDetail.label.trace": "跟踪",
"xpack.logsExplorer.flyoutDetail.title": "日志详情",
"xpack.logsExplorer.flyoutDetail.value.hover.copyToClipboard": "复制到剪贴板",
"xpack.logsExplorer.flyoutDetail.value.hover.filterForFieldPresent": "筛留存在的字段",
"xpack.logsExplorer.flyoutDetail.value.hover.toggleColumn": "在表中切换列",
"xpack.logsExplorer.grid.closePopover": "关闭弹出框",
"xpack.logsExplorer.popoverAction.closePopover": "关闭弹出框",
"xpack.logsExplorer.popoverAction.copyValue": "复制值",

View file

@ -4518,6 +4518,10 @@
version "0.0.0"
uid ""
"@kbn/discover-shared-plugin@link:src/plugins/discover_shared":
version "0.0.0"
uid ""
"@kbn/discover-utils@link:packages/kbn-discover-utils":
version "0.0.0"
uid ""