[Log Explorer] Add Explorer app locator (#165962)

closes https://github.com/elastic/kibana/issues/164995
closes https://github.com/elastic/kibana/issues/165618
closes https://github.com/elastic/kibana/issues/166596

## 📝  Summary

### Observability Log Explorer Locators:

This PR adds 2 new customized locators to the Observability log explorer
profile. At the moment we implemented:

       1- Single dataset selector locator
       2- All dataset selector locator

With more locators to come in the future depending on the use cases.

### Log Explorer Locators:

We also added a log explorer locator that navigates to discover, this
can be used in case the **Observability Log Explorer** plugin is
disabled.

### Logs Onboarding:

The PR also replaces the temp navigation to the default discover we
implemented for[ 8.10
here](https://github.com/elastic/kibana/pull/163218) with the above new
Observability Log Explorer locators.

### APM:

After [disabling infra plugin in serverless
projects](https://github.com/elastic/kibana/pull/165289), APM links to
infra locators in serverless have been replaced to use the above
locators.

### Observability Landing Page:

The landing page now redirects to the Log Explorer if `logs-*-*` has
data in it, otherwise the flow continues as before.

### Necessary Refactoring:

To avoid the circular dependency between `ObservabilityLogExplorer` &
`ObservabilityOnboarding` after each one using the other's locator and
importing the necessary types, I moved the type definition for all
locators in the `deeplinks` package.

##   Testing

- Onboarding Wizard in Serverless and Stateful

    1. Navigate to the onboarding flow `/app/observabilityOnboarding/`
    2. Choose either System logs or Stream log files
    3. Go through the onboarding wizard
    4. Click the Explore logs button at the end
5. You should be redirected to observability log explorer with the
integration and dataset preselected.

- APM links in Serverless

1. Navigate to APM and click on the logs links as shown in the Demos
below
2. All links should navigate to Observability Log Explorer with the
queries set in the search bar.

## 🎥 Demos

- APM Serverless


7161364e-333f-4ac4-87d5-7f1ffec699b3


- APM Stateful


058c9587-b766-4d4f-a73d-50fd381be4bb


- Onboarding Serverless



ee1cab42-f91c-4558-aa5f-4fa7e8963427

- Onboarding Stateful



a376a12b-499b-4488-a75a-d06e81f8e21d

- Observability Landing Page 



c1c084ca-b1b1-4c4b-a4e6-ae8e157dcf57

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co>
Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani01@gmail.com>
This commit is contained in:
mohamedhamed-ahmed 2023-09-20 19:49:57 +02:00 committed by GitHub
parent f6084a5d1b
commit 3d58a1da64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
79 changed files with 1549 additions and 411 deletions

View file

@ -9,3 +9,5 @@
export { OBSERVABILITY_ONBOARDING_APP_ID } from './constants';
export type { AppId, DeepLinkId } from './deep_links';
export * from './locators';

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 * from './log_explorer';
export * from './observability_log_explorer';
export * from './observability_onboarding';

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { SerializableRecord } from '@kbn/utility-types';
import type { Filter, TimeRange, Query, AggregateQuery } from '@kbn/es-query';
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type RefreshInterval = {
pause: boolean;
value: number;
};
export const LOG_EXPLORER_LOCATOR_ID = 'LOG_EXPLORER_LOCATOR';
export interface LogExplorerNavigationParams extends SerializableRecord {
/**
* Optionally set the time range in the time picker.
*/
timeRange?: TimeRange;
/**
* Optionally set the refresh interval.
*/
refreshInterval?: RefreshInterval;
/**
* Optionally set a query.
*/
query?: Query | AggregateQuery;
/**
* Columns displayed in the table
*/
columns?: string[];
/**
* Array of the used sorting [[field,direction],...]
*/
sort?: string[][];
/**
* Optionally apply filters.
*/
filters?: Filter[];
}
export interface LogExplorerLocatorParams extends LogExplorerNavigationParams {
/**
* Dataset name to be selected.
*/
dataset?: string;
}

View file

@ -0,0 +1,31 @@
/*
* 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 { LogExplorerNavigationParams } from './log_explorer';
export type DatasetLocatorParams = LogExplorerNavigationParams;
// All datasets locator
export const ALL_DATASETS_LOCATOR_ID = 'ALL_DATASETS_LOCATOR';
export type AllDatasetsLocatorParams = DatasetLocatorParams;
// Single dataset locator
export const SINGLE_DATASET_LOCATOR_ID = 'SINGLE_DATASET_LOCATOR';
export interface SingleDatasetLocatorParams extends DatasetLocatorParams {
/**
* Integration name to be selected.
*/
integration?: string;
/**
* Dataset name to be selected.
* ex: system.syslog
*/
dataset: string;
}

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 { SerializableRecord } from '@kbn/utility-types';
export const OBSERVABILITY_ONBOARDING_LOCATOR = 'OBSERVABILITY_ONBOARDING_LOCATOR' as const;
export interface ObservabilityOnboardingLocatorParams extends SerializableRecord {
/** If given, it will load the given map else will load the create a new map page. */
source?: 'customLogs' | 'systemLogs';
}

View file

@ -16,5 +16,7 @@
"target/**/*"
],
"kbn_references": [
"@kbn/utility-types",
"@kbn/es-query",
]
}

View file

@ -58,6 +58,7 @@ export const renderApp = ({
lens: pluginsStart.lens,
uiActions: pluginsStart.uiActions,
observabilityAIAssistant: pluginsStart.observabilityAIAssistant,
share: pluginsSetup.share,
};
// render APM feedback link in global help menu

View file

@ -15,6 +15,10 @@ import {
SectionSubtitle,
SectionTitle,
} from '@kbn/observability-shared-plugin/public';
import {
AllDatasetsLocatorParams,
ALL_DATASETS_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { isJavaAgentName } from '../../../../../../common/agent_name';
import { SERVICE_NODE_NAME } from '../../../../../../common/es_fields/apm';
import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context';
@ -40,7 +44,7 @@ export function InstanceActionsMenu({
kuery,
onClose,
}: Props) {
const { core, infra } = useApmPluginContext();
const { core, infra, share } = useApmPluginContext();
const { data, status } = useInstanceDetailsFetcher({
serviceName,
serviceNodeName,
@ -52,6 +56,10 @@ export function InstanceActionsMenu({
const metricOverviewHref = useMetricOverviewHref(serviceName);
const history = useHistory();
const allDatasetsLocator = share.url.locators.get<AllDatasetsLocatorParams>(
ALL_DATASETS_LOCATOR_ID
)!;
if (isPending(status)) {
return (
<div
@ -90,6 +98,7 @@ export function InstanceActionsMenu({
onFilterByInstanceClick: handleFilterByInstanceClick,
metricsHref,
infraLocators: infra?.locators,
allDatasetsLocator,
});
return (

View file

@ -9,6 +9,9 @@ import { i18n } from '@kbn/i18n';
import { IBasePath } from '@kbn/core/public';
import moment from 'moment';
import type { InfraLocators } from '@kbn/infra-plugin/common/locators';
import type { LocatorPublic } from '@kbn/share-plugin/public';
import { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability/locators';
import { getNodeLogsHref } from '../../../../shared/links/observability_logs_link';
import { APIReturnType } from '../../../../../services/rest/create_call_apm_api';
import { getInfraHref } from '../../../../shared/links/infra_link';
import {
@ -39,12 +42,14 @@ export function getMenuSections({
onFilterByInstanceClick,
metricsHref,
infraLocators,
allDatasetsLocator,
}: {
instanceDetails: InstaceDetails;
basePath: IBasePath;
onFilterByInstanceClick: () => void;
metricsHref: string;
infraLocators: InfraLocators;
infraLocators?: InfraLocators;
allDatasetsLocator: LocatorPublic<AllDatasetsLocatorParams>;
}) {
const podId = instanceDetails.kubernetes?.pod?.uid;
const containerId = instanceDetails.container?.id;
@ -54,69 +59,72 @@ export function getMenuSections({
const infraMetricsQuery = getInfraMetricsQuery(instanceDetails['@timestamp']);
const infraNodeLocator = infraLocators?.nodeLogsLocator;
const podActions: Action[] = infraNodeLocator
? [
{
key: 'podLogs',
label: i18n.translate(
'xpack.apm.serviceOverview.instancesTable.actionMenus.podLogs',
{ defaultMessage: 'Pod logs' }
),
href: infraNodeLocator?.getRedirectUrl({
nodeId: podId!,
nodeType: 'pod',
time,
}),
condition: !!podId,
},
{
key: 'podMetrics',
label: i18n.translate(
'xpack.apm.serviceOverview.instancesTable.actionMenus.podMetrics',
{ defaultMessage: 'Pod metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/pod-detail/${podId}`,
query: infraMetricsQuery,
}),
condition: !!podId,
},
]
: [];
const podLogsHref = getNodeLogsHref(
'pod',
podId!,
time,
allDatasetsLocator,
infraNodeLocator
);
const containerLogsHref = getNodeLogsHref(
'container',
containerId!,
time,
allDatasetsLocator,
infraNodeLocator
);
const containerActions: Action[] = infraNodeLocator
? [
{
key: 'containerLogs',
label: i18n.translate(
'xpack.apm.serviceOverview.instancesTable.actionMenus.containerLogs',
{ defaultMessage: 'Container logs' }
),
href: infraNodeLocator?.getRedirectUrl({
nodeId: containerId!,
nodeType: 'container',
time,
}),
condition: !!containerId,
},
{
key: 'containerMetrics',
label: i18n.translate(
'xpack.apm.serviceOverview.instancesTable.actionMenus.containerMetrics',
{ defaultMessage: 'Container metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/container-detail/${containerId}`,
query: infraMetricsQuery,
}),
condition: !!containerId,
},
]
: [];
const podActions: Action[] = [
{
key: 'podLogs',
label: i18n.translate(
'xpack.apm.serviceOverview.instancesTable.actionMenus.podLogs',
{ defaultMessage: 'Pod logs' }
),
href: podLogsHref,
condition: !!podId,
},
{
key: 'podMetrics',
label: i18n.translate(
'xpack.apm.serviceOverview.instancesTable.actionMenus.podMetrics',
{ defaultMessage: 'Pod metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/pod-detail/${podId}`,
query: infraMetricsQuery,
}),
condition: !!podId && !!infraLocators,
},
];
const containerActions: Action[] = [
{
key: 'containerLogs',
label: i18n.translate(
'xpack.apm.serviceOverview.instancesTable.actionMenus.containerLogs',
{ defaultMessage: 'Container logs' }
),
href: containerLogsHref,
condition: !!containerId,
},
{
key: 'containerMetrics',
label: i18n.translate(
'xpack.apm.serviceOverview.instancesTable.actionMenus.containerMetrics',
{ defaultMessage: 'Container metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/container-detail/${containerId}`,
query: infraMetricsQuery,
}),
condition: !!containerId && !!infraLocators,
},
];
const apmActions: Action[] = [
{

View file

@ -0,0 +1,91 @@
/*
* 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 type {
NodeLogsLocator,
DiscoverNodeLogsLocator,
LogsLocator,
DiscoverLogsLocator,
} from '@kbn/infra-plugin/common/locators';
import { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability/locators';
import { LocatorPublic } from '@kbn/share-plugin/common';
import moment from 'moment';
import { DurationInputObject } from 'moment';
type NodeType = 'host' | 'pod' | 'container';
const NodeTypeMapping: Record<NodeType, string> = {
host: 'host.name',
container: 'container.id',
pod: 'kubernetes.pod.uid',
};
export const getNodeLogsHref = (
nodeType: NodeType,
id: string,
time: number | undefined,
allDatasetsLocator: LocatorPublic<AllDatasetsLocatorParams>,
infraNodeLocator?: NodeLogsLocator | DiscoverNodeLogsLocator
): string => {
if (infraNodeLocator)
return infraNodeLocator?.getRedirectUrl({
nodeId: id!,
nodeType,
time,
});
return allDatasetsLocator.getRedirectUrl({
query: getNodeQuery(nodeType, id),
...(time
? {
timeRange: {
from: getTimeRangeStartFromTime(time),
to: getTimeRangeEndFromTime(time),
},
}
: {}),
});
};
export const getTraceLogsHref = (
traceId: string,
time: number | undefined,
allDatasetsLocator: LocatorPublic<AllDatasetsLocatorParams>,
infraLogsLocator?: LogsLocator | DiscoverLogsLocator
): string => {
const query = `trace.id:"${traceId}" OR (not trace.id:* AND "${traceId}")`;
if (infraLogsLocator)
return infraLogsLocator.getRedirectUrl({
filter: query,
time,
});
return allDatasetsLocator.getRedirectUrl({
query: { language: 'kuery', query },
...(time
? {
timeRange: {
from: getTimeRangeStartFromTime(time),
to: getTimeRangeEndFromTime(time),
},
}
: {}),
});
};
const getNodeQuery = (type: NodeType, id: string) => {
return { language: 'kuery', query: `${NodeTypeMapping[type]}: ${id}` };
};
const defaultTimeRangeFromPositionOffset: DurationInputObject = { hours: 1 };
const getTimeRangeStartFromTime = (time: number): string =>
moment(time).subtract(defaultTimeRangeFromPositionOffset).toISOString();
const getTimeRangeEndFromTime = (time: number): string =>
moment(time).add(defaultTimeRangeFromPositionOffset).toISOString();

View file

@ -13,7 +13,10 @@ import {
apmRouter as apmRouterBase,
ApmRouter,
} from '../../routing/apm_route_config';
import { infraLocatorsMock } from '../../../context/apm_plugin/mock_apm_plugin_context';
import {
infraLocatorsMock,
observabilityLogExplorerLocatorsMock,
} from '../../../context/apm_plugin/mock_apm_plugin_context';
const apmRouter = {
...apmRouterBase,
@ -22,6 +25,7 @@ const apmRouter = {
} as ApmRouter;
const infraLocators = infraLocatorsMock;
const observabilityLogExplorerLocators = observabilityLogExplorerLocatorsMock;
const expectInfraLocatorsToBeCalled = () => {
expect(infraLocators.nodeLogsLocator.getRedirectUrl).toBeCalledTimes(3);
@ -61,6 +65,7 @@ describe('Transaction action menu', () => {
location,
apmRouter,
infraLocators,
observabilityLogExplorerLocators,
infraLinksAvailable: false,
rangeFrom: 'now-24h',
rangeTo: 'now',
@ -126,6 +131,7 @@ describe('Transaction action menu', () => {
location,
apmRouter,
infraLocators,
observabilityLogExplorerLocators,
infraLinksAvailable: true,
rangeFrom: 'now-24h',
rangeTo: 'now',
@ -210,6 +216,7 @@ describe('Transaction action menu', () => {
location,
apmRouter,
infraLocators,
observabilityLogExplorerLocators,
infraLinksAvailable: true,
rangeFrom: 'now-24h',
rangeTo: 'now',

View file

@ -13,6 +13,8 @@ import moment from 'moment';
import url from 'url';
import type { InfraLocators } from '@kbn/infra-plugin/common/locators';
import type { ProfilingLocators } from '@kbn/profiling-plugin/public';
import { LocatorPublic } from '@kbn/share-plugin/common';
import { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability/locators';
import { Environment } from '../../../../common/environment_rt';
import type { Transaction } from '../../../../typings/es_schemas/ui/transaction';
import { getDiscoverHref } from '../links/discover_links/discover_link';
@ -22,6 +24,10 @@ import { fromQuery } from '../links/url_helpers';
import { SectionRecord, getNonEmptySections, Action } from './sections_helper';
import { HOST_NAME, TRACE_ID } from '../../../../common/es_fields/apm';
import { ApmRouter } from '../../routing/apm_route_config';
import {
getNodeLogsHref,
getTraceLogsHref,
} from '../links/observability_logs_link';
function getInfraMetricsQuery(transaction: Transaction) {
const timestamp = new Date(transaction['@timestamp']).getTime();
@ -44,6 +50,7 @@ export const getSections = ({
rangeFrom,
rangeTo,
environment,
allDatasetsLocator,
}: {
transaction?: Transaction;
basePath: IBasePath;
@ -55,8 +62,10 @@ export const getSections = ({
rangeFrom: string;
rangeTo: string;
environment: Environment;
allDatasetsLocator: LocatorPublic<AllDatasetsLocatorParams>;
}) => {
if (!transaction) return [];
const hostName = transaction.host?.hostname;
const podId = transaction.kubernetes?.pod?.uid;
const containerId = transaction.container?.id;
@ -79,102 +88,111 @@ export const getSections = ({
)}`,
});
const podActions: Action[] = nodeLogsLocator
? [
{
key: 'podLogs',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showPodLogsLinkLabel',
{ defaultMessage: 'Pod logs' }
),
href: nodeLogsLocator.getRedirectUrl({
nodeId: podId!,
nodeType: 'pod',
time,
}),
condition: !!podId,
},
{
key: 'podMetrics',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showPodMetricsLinkLabel',
{ defaultMessage: 'Pod metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/pod-detail/${podId}`,
query: infraMetricsQuery,
}),
condition: !!podId,
},
]
: [];
// Logs hrefs
const podLogsHref = getNodeLogsHref(
'pod',
podId!,
time,
allDatasetsLocator,
nodeLogsLocator
);
const containerLogsHref = getNodeLogsHref(
'container',
containerId!,
time,
allDatasetsLocator,
nodeLogsLocator
);
const hostLogsHref = getNodeLogsHref(
'host',
hostName!,
time,
allDatasetsLocator,
nodeLogsLocator
);
const traceLogsHref = getTraceLogsHref(
transaction.trace.id!,
time,
allDatasetsLocator,
logsLocator
);
const containerActions: Action[] = nodeLogsLocator
? [
{
key: 'containerLogs',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showContainerLogsLinkLabel',
{ defaultMessage: 'Container logs' }
),
href: nodeLogsLocator.getRedirectUrl({
nodeId: containerId!,
nodeType: 'container',
time,
}),
condition: !!containerId,
},
{
key: 'containerMetrics',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel',
{ defaultMessage: 'Container metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/container-detail/${containerId}`,
query: infraMetricsQuery,
}),
condition: !!containerId,
},
]
: [];
const podActions: Action[] = [
{
key: 'podLogs',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showPodLogsLinkLabel',
{ defaultMessage: 'Pod logs' }
),
href: podLogsHref,
condition: !!podId,
},
{
key: 'podMetrics',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showPodMetricsLinkLabel',
{ defaultMessage: 'Pod metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/pod-detail/${podId}`,
query: infraMetricsQuery,
}),
condition: !!podId && infraLinksAvailable,
},
];
const containerActions: Action[] = [
{
key: 'containerLogs',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showContainerLogsLinkLabel',
{ defaultMessage: 'Container logs' }
),
href: containerLogsHref,
condition: !!containerId,
},
{
key: 'containerMetrics',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel',
{ defaultMessage: 'Container metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/container-detail/${containerId}`,
query: infraMetricsQuery,
}),
condition: !!containerId && infraLinksAvailable,
},
];
const hostActions: Action[] = [
...(nodeLogsLocator
? [
{
key: 'hostLogs',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showHostLogsLinkLabel',
{ defaultMessage: 'Host logs' }
),
href: nodeLogsLocator.getRedirectUrl({
nodeId: hostName!,
nodeType: 'host',
time,
}),
condition: !!hostName,
},
{
key: 'hostMetrics',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showHostMetricsLinkLabel',
{ defaultMessage: 'Host metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/host-detail/${hostName}`,
query: infraMetricsQuery,
}),
condition: !!hostName,
},
]
: []),
{
key: 'hostLogs',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showHostLogsLinkLabel',
{ defaultMessage: 'Host logs' }
),
href: hostLogsHref,
condition: !!hostName,
},
{
key: 'hostMetrics',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showHostMetricsLinkLabel',
{ defaultMessage: 'Host metrics' }
),
href: getInfraHref({
app: 'metrics',
basePath,
path: `/link-to/host-detail/${hostName}`,
query: infraMetricsQuery,
}),
condition: !!hostName && infraLinksAvailable,
},
{
key: 'hostProfilingFlamegraph',
label: i18n.translate(
@ -213,22 +231,17 @@ export const getSections = ({
},
];
const logActions: Action[] = logsLocator
? [
{
key: 'traceLogs',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showTraceLogsLinkLabel',
{ defaultMessage: 'Trace logs' }
),
href: logsLocator.getRedirectUrl({
filter: `trace.id:"${transaction.trace.id}" OR (not trace.id:* AND "${transaction.trace.id}")`,
time,
}),
condition: true,
},
]
: [];
const logActions: Action[] = [
{
key: 'traceLogs',
label: i18n.translate(
'xpack.apm.transactionActionMenu.showTraceLogsLinkLabel',
{ defaultMessage: 'Trace logs' }
),
href: traceLogsHref,
condition: true,
},
];
const uptimeActions: Action[] = [
{
@ -283,82 +296,64 @@ export const getSections = ({
const sectionRecord: SectionRecord = {
observability: [
...(infraLinksAvailable && infraLocators
? [
{
key: 'podDetails',
title: i18n.translate(
'xpack.apm.transactionActionMenu.pod.title',
{
defaultMessage: 'Pod details',
}
),
subtitle: i18n.translate(
'xpack.apm.transactionActionMenu.pod.subtitle',
{
defaultMessage:
'View logs and metrics for this pod to get further details.',
}
),
actions: podActions,
},
{
key: 'containerDetails',
title: i18n.translate(
'xpack.apm.transactionActionMenu.container.title',
{
defaultMessage: 'Container details',
}
),
subtitle: i18n.translate(
'xpack.apm.transactionActionMenu.container.subtitle',
{
defaultMessage:
'View logs and metrics for this container to get further details.',
}
),
actions: containerActions,
},
{
key: 'hostDetails',
title: i18n.translate(
'xpack.apm.transactionActionMenu.host.title',
{
defaultMessage: 'Host details',
}
),
subtitle: i18n.translate(
'xpack.apm.transactionActionMenu.host.subtitle',
{
defaultMessage:
'View host logs and metrics to get further details.',
}
),
actions: hostActions,
},
]
: []),
...(infraLocators
? [
{
key: 'traceDetails',
title: i18n.translate(
'xpack.apm.transactionActionMenu.trace.title',
{
defaultMessage: 'Trace details',
}
),
subtitle: i18n.translate(
'xpack.apm.transactionActionMenu.trace.subtitle',
{
defaultMessage: 'View trace logs to get further details.',
}
),
actions: logActions,
},
]
: []),
{
key: 'podDetails',
title: i18n.translate('xpack.apm.transactionActionMenu.pod.title', {
defaultMessage: 'Pod details',
}),
subtitle: i18n.translate(
'xpack.apm.transactionActionMenu.pod.subtitle',
{
defaultMessage:
'View logs and metrics for this pod to get further details.',
}
),
actions: podActions,
},
{
key: 'containerDetails',
title: i18n.translate(
'xpack.apm.transactionActionMenu.container.title',
{
defaultMessage: 'Container details',
}
),
subtitle: i18n.translate(
'xpack.apm.transactionActionMenu.container.subtitle',
{
defaultMessage:
'View logs and metrics for this container to get further details.',
}
),
actions: containerActions,
},
{
key: 'hostDetails',
title: i18n.translate('xpack.apm.transactionActionMenu.host.title', {
defaultMessage: 'Host details',
}),
subtitle: i18n.translate(
'xpack.apm.transactionActionMenu.host.subtitle',
{
defaultMessage:
'View host logs and metrics to get further details.',
}
),
actions: hostActions,
},
{
key: 'traceDetails',
title: i18n.translate('xpack.apm.transactionActionMenu.trace.title', {
defaultMessage: 'Trace details',
}),
subtitle: i18n.translate(
'xpack.apm.transactionActionMenu.trace.subtitle',
{
defaultMessage: 'View trace logs to get further details.',
}
),
actions: logActions,
},
{
key: 'statusDetails',
title: i18n.translate('xpack.apm.transactionActionMenu.status.title', {

View file

@ -69,9 +69,11 @@ const renderTransaction = async (transaction: Record<string, any>) => {
const expectInfraLocatorsToBeCalled = () => {
expect(
apmContextMock.infra.locators.nodeLogsLocator.getRedirectUrl
apmContextMock.infra?.locators.nodeLogsLocator.getRedirectUrl
).toBeCalled();
expect(
apmContextMock.infra?.locators.logsLocator.getRedirectUrl
).toBeCalled();
expect(apmContextMock.infra.locators.logsLocator.getRedirectUrl).toBeCalled();
};
describe('TransactionActionMenu component', () => {

View file

@ -22,6 +22,10 @@ import { ProfilingLocators } from '@kbn/profiling-plugin/public';
import React, { useState } from 'react';
import { useLocation } from 'react-router-dom';
import useAsync from 'react-use/lib/useAsync';
import {
AllDatasetsLocatorParams,
ALL_DATASETS_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
import { ApmFeatureFlagName } from '../../../../common/apm_feature_flags';
import { Transaction } from '../../../../typings/es_schemas/ui/transaction';
@ -125,10 +129,14 @@ function ActionMenuSections({
transaction?: Transaction;
profilingLocators?: ProfilingLocators;
}) {
const { core, uiActions, infra } = useApmPluginContext();
const { core, uiActions, infra, share } = useApmPluginContext();
const location = useLocation();
const apmRouter = useApmRouter();
const allDatasetsLocator = share.url.locators.get<AllDatasetsLocatorParams>(
ALL_DATASETS_LOCATOR_ID
)!;
const infraLinksAvailable = useApmFeatureFlag(
ApmFeatureFlagName.InfraUiAvailable
);
@ -153,6 +161,7 @@ function ActionMenuSections({
rangeFrom,
rangeTo,
environment,
allDatasetsLocator,
});
const externalMenuItems = useAsync(() => {

View file

@ -17,6 +17,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import type { InfraClientStartExports } from '@kbn/infra-plugin/public';
import type { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import type { ApmPluginSetupDeps } from '../../plugin';
import type { ConfigSchema } from '../..';
@ -28,12 +29,13 @@ export interface ApmPluginContextValue {
plugins: ApmPluginSetupDeps & { maps?: MapsStartApi };
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry;
observability: ObservabilityPublicStart;
infra: InfraClientStartExports;
infra?: InfraClientStartExports;
dataViews: DataViewsPublicPluginStart;
data: DataPublicPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
uiActions: UiActionsStart;
observabilityAIAssistant: ObservabilityAIAssistantPluginStart;
share: SharePluginSetup;
}
export const ApmPluginContext = createContext({} as ApmPluginContextValue);

View file

@ -141,6 +141,7 @@ export const mockApmPluginContextValue = {
locators: infraLocatorsMock,
},
deps: {},
share: sharePluginMock.createSetupContract(),
unifiedSearch: mockUnifiedSearch,
uiActions: {
getTriggerCompatibleActions: () => Promise.resolve([]),

View file

@ -101,7 +101,8 @@
"@kbn/profiling-utils",
"@kbn/core-analytics-server",
"@kbn/analytics-client",
"@kbn/monaco"
"@kbn/monaco",
"@kbn/deeplinks-observability"
],
"exclude": ["target/**/*"]
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Dataset } from '../../../common/datasets';
import { Dataset } from '../datasets';
import { encodeDatasetSelection } from './encoding';
import { DatasetSelectionStrategy } from './types';

View file

@ -7,7 +7,7 @@
import { decode, encode, RisonValue } from '@kbn/rison';
import * as lz from 'lz-string';
import { decodeOrThrow } from '../../../common/runtime_types';
import { decodeOrThrow } from '../runtime_types';
import { DatasetEncodingError } from './errors';
import { DatasetSelectionPlain, datasetSelectionPlainRT } from './types';

View file

@ -8,6 +8,7 @@
import { AllDatasetSelection } from './all_dataset_selection';
import { SingleDatasetSelection } from './single_dataset_selection';
import { DatasetSelectionPlain } from './types';
import { UnresolvedDatasetSelection } from './unresolved_dataset_selection';
export const hydrateDatasetSelection = (datasetSelection: DatasetSelectionPlain) => {
if (datasetSelection.selectionType === 'all') {
@ -16,4 +17,7 @@ export const hydrateDatasetSelection = (datasetSelection: DatasetSelectionPlain)
if (datasetSelection.selectionType === 'single') {
return SingleDatasetSelection.fromSelection(datasetSelection.selection);
}
if (datasetSelection.selectionType === 'unresolved') {
return UnresolvedDatasetSelection.fromSelection(datasetSelection.selection);
}
};

View file

@ -7,16 +7,25 @@
import { AllDatasetSelection } from './all_dataset_selection';
import { SingleDatasetSelection } from './single_dataset_selection';
import { UnresolvedDatasetSelection } from './unresolved_dataset_selection';
export type DatasetSelection = AllDatasetSelection | SingleDatasetSelection;
export type DatasetSelection =
| AllDatasetSelection
| SingleDatasetSelection
| UnresolvedDatasetSelection;
export type DatasetSelectionChange = (datasetSelection: DatasetSelection) => void;
export const isDatasetSelection = (input: any): input is DatasetSelection => {
return input instanceof AllDatasetSelection || input instanceof SingleDatasetSelection;
return (
input instanceof AllDatasetSelection ||
input instanceof SingleDatasetSelection ||
input instanceof UnresolvedDatasetSelection
);
};
export * from './all_dataset_selection';
export * from './single_dataset_selection';
export * from './unresolved_dataset_selection';
export * from './encoding';
export * from './errors';
export * from './hydrate_dataset_selection.ts';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Dataset } from '../../../common/datasets';
import { Dataset } from '../datasets';
import { encodeDatasetSelection } from './encoding';
import { DatasetSelectionStrategy, SingleDatasetSelectionPayload } from './types';

View file

@ -6,7 +6,7 @@
*/
import { DataViewSpec } from '@kbn/data-views-plugin/common';
import * as rt from 'io-ts';
import { datasetRT } from '../../../common/datasets';
import { datasetRT } from '../datasets';
export const allDatasetSelectionPlainRT = rt.type({
selectionType: rt.literal('all'),
@ -33,17 +33,33 @@ const singleDatasetSelectionPayloadRT = rt.intersection([
}),
]);
const unresolvedDatasetSelectionPayloadRT = rt.intersection([
integrationNameRT,
rt.type({
dataset: datasetRT,
}),
]);
export const singleDatasetSelectionPlainRT = rt.type({
selectionType: rt.literal('single'),
selection: singleDatasetSelectionPayloadRT,
});
export const unresolvedDatasetSelectionPlainRT = rt.type({
selectionType: rt.literal('unresolved'),
selection: unresolvedDatasetSelectionPayloadRT,
});
export const datasetSelectionPlainRT = rt.union([
allDatasetSelectionPlainRT,
singleDatasetSelectionPlainRT,
unresolvedDatasetSelectionPlainRT,
]);
export type SingleDatasetSelectionPayload = rt.TypeOf<typeof singleDatasetSelectionPayloadRT>;
export type UnresolvedDatasetSelectionPayload = rt.TypeOf<
typeof unresolvedDatasetSelectionPayloadRT
>;
export type DatasetSelectionPlain = rt.TypeOf<typeof datasetSelectionPlainRT>;
export interface DatasetSelectionStrategy {

View file

@ -0,0 +1,59 @@
/*
* 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 { Dataset } from '../datasets';
import { encodeDatasetSelection } from './encoding';
import { DatasetSelectionStrategy, UnresolvedDatasetSelectionPayload } from './types';
export class UnresolvedDatasetSelection implements DatasetSelectionStrategy {
selectionType: 'unresolved';
selection: {
name?: string;
dataset: Dataset;
};
private constructor(dataset: Dataset) {
this.selectionType = 'unresolved';
this.selection = {
name: dataset.parentIntegration?.name,
dataset,
};
}
toDataviewSpec() {
const { name, title } = this.selection.dataset.toDataviewSpec();
return {
id: this.toURLSelectionId(),
name,
title,
};
}
toURLSelectionId() {
return encodeDatasetSelection({
selectionType: this.selectionType,
selection: {
name: this.selection.name,
dataset: this.selection.dataset.toPlain(),
},
});
}
public static fromSelection(selection: UnresolvedDatasetSelectionPayload) {
const { name, dataset } = selection;
// Attempt reconstructing the integration object
const integration = name ? { name } : undefined;
const datasetInstance = Dataset.create(dataset, integration);
return new UnresolvedDatasetSelection(datasetInstance);
}
public static create(dataset: Dataset) {
return new UnresolvedDatasetSelection(dataset);
}
}

View file

@ -11,7 +11,7 @@ import { IndexPattern } from '@kbn/io-ts-utils';
import { TIMESTAMP_FIELD } from '../../constants';
import { DatasetId, DatasetType, IntegrationType } from '../types';
type IntegrationBase = Pick<IntegrationType, 'name' | 'title' | 'icons' | 'version'>;
type IntegrationBase = Partial<Pick<IntegrationType, 'name' | 'title' | 'icons' | 'version'>>;
interface DatasetDeps extends DatasetType {
iconType?: IconType;
@ -31,7 +31,7 @@ export class Dataset {
this.title = dataset.title ?? dataset.name;
this.parentIntegration = parentIntegration && {
name: parentIntegration.name,
title: parentIntegration.title,
title: parentIntegration.title ?? parentIntegration.name,
icons: parentIntegration.icons,
version: parentIntegration.version,
};

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export { AllDatasetSelection, UnresolvedDatasetSelection } from './dataset_selection';

View file

@ -0,0 +1,14 @@
/*
* 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 { LogExplorerLocator } from './log_explorer/log_explorer_locator';
export * from './log_explorer';
export interface LogExplorerLocators {
logExplorerLocator: LogExplorerLocator;
}

View file

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

View file

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
import { LogExplorerLocatorDefinition } from './log_explorer_locator';
import { LogExplorerLocatorDependencies } from './types';
const setup = async () => {
const discoverSetupContract: LogExplorerLocatorDependencies = {
discover: {
locator: sharePluginMock.createLocator(),
},
};
const logExplorerLocator = new LogExplorerLocatorDefinition(discoverSetupContract);
return {
logExplorerLocator,
discoverGetLocation: discoverSetupContract.discover.locator?.getLocation,
};
};
describe('Logs Explorer Locators', () => {
const dataset = 'logs-*-*';
it('should call discover locator with empty params', async () => {
const { logExplorerLocator, discoverGetLocation } = await setup();
await logExplorerLocator.getLocation({});
expect(discoverGetLocation).toBeCalledWith({});
});
it('should call discover locator with correct dataViewId if dataset is sent', async () => {
const { logExplorerLocator, discoverGetLocation } = await setup();
await logExplorerLocator.getLocation({ dataset });
expect(discoverGetLocation).toBeCalledWith(
expect.objectContaining({
dataViewId: 'logs-*-*',
})
);
});
it('should call discover locator with correct dataViewSpec if dataset is sent', async () => {
const { logExplorerLocator, discoverGetLocation } = await setup();
await logExplorerLocator.getLocation({ dataset });
expect(discoverGetLocation).toBeCalledWith(
expect.objectContaining({
dataViewId: 'logs-*-*',
dataViewSpec: {
id: 'logs-*-*',
title: 'logs-*-*',
},
})
);
});
});

View file

@ -0,0 +1,38 @@
/*
* 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 type { DataViewSpec } from '@kbn/data-views-plugin/common';
import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public';
import {
LogExplorerLocatorParams,
LOG_EXPLORER_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { LogExplorerLocatorDependencies } from './types';
export type LogExplorerLocator = LocatorPublic<LogExplorerLocatorParams>;
export class LogExplorerLocatorDefinition implements LocatorDefinition<LogExplorerLocatorParams> {
public readonly id = LOG_EXPLORER_LOCATOR_ID;
constructor(protected readonly deps: LogExplorerLocatorDependencies) {}
public readonly getLocation = (params: LogExplorerLocatorParams) => {
const { dataset } = params;
const dataViewSpec: DataViewSpec | undefined = dataset
? {
id: dataset,
title: dataset,
}
: undefined;
return this.deps.discover.locator?.getLocation({
...params,
dataViewId: dataset,
dataViewSpec,
})!;
};
}

View file

@ -0,0 +1,12 @@
/*
* 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 type { DiscoverSetup } from '@kbn/discover-plugin/public';
export interface LogExplorerLocatorDependencies {
discover: DiscoverSetup;
}

View file

@ -19,9 +19,13 @@
"kibanaReact",
"kibanaUtils",
"controls",
"embeddable"
"embeddable",
"share",
],
"optionalPlugins": [],
"requiredBundles": []
"requiredBundles": [],
"extraPublicDirs": [
"common",
]
}
}

View file

@ -11,14 +11,14 @@ import React, { useState } from 'react';
import { I18nProvider } from '@kbn/i18n-react';
import type { Meta, Story } from '@storybook/react';
import { IndexPattern } from '@kbn/io-ts-utils';
import { Dataset, Integration } from '../../../common/datasets';
import { DatasetSelector } from './dataset_selector';
import { DatasetSelectorProps, DatasetsSelectorSearchParams } from './types';
import {
AllDatasetSelection,
DatasetSelection,
DatasetSelectionChange,
} from '../../utils/dataset_selection';
} from '../../../common/dataset_selection';
import { Dataset, Integration } from '../../../common/datasets';
import { DatasetSelector } from './dataset_selector';
import { DatasetSelectorProps, DatasetsSelectorSearchParams } from './types';
const meta: Meta<typeof DatasetSelector> = {
component: DatasetSelector,

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { AllDatasetSelection } from '../../../utils/dataset_selection';
import { AllDatasetSelection } from '../../../../common/dataset_selection';
import { HashedCache } from '../../../../common/hashed_cache';
import { INTEGRATION_PANEL_ID } from '../constants';
import { DatasetsSelectorSearchParams } from '../types';

View file

@ -6,7 +6,7 @@
*/
import { actions, assign, createMachine, raise } from 'xstate';
import { AllDatasetSelection, SingleDatasetSelection } from '../../../utils/dataset_selection';
import { AllDatasetSelection, SingleDatasetSelection } from '../../../../common/dataset_selection';
import { UNMANAGED_STREAMS_PANEL_ID } from '../constants';
import { defaultSearch, DEFAULT_CONTEXT } from './defaults';
import {

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { DatasetSelection, DatasetSelectionChange } from '../../../utils/dataset_selection';
import { DatasetSelection, DatasetSelectionChange } from '../../../../common/dataset_selection';
import { Dataset } from '../../../../common/datasets/models/dataset';
import { ReloadDatasets, SearchDatasets } from '../../../hooks/use_datasets';
import {

View file

@ -18,7 +18,7 @@ import {
} from '@elastic/eui';
import styled from '@emotion/styled';
import { PackageIcon } from '@kbn/fleet-plugin/public';
import { DatasetSelection } from '../../../utils/dataset_selection';
import { DatasetSelection } from '../../../../common/dataset_selection';
import { DATA_VIEW_POPOVER_CONTENT_WIDTH, POPOVER_ID, selectDatasetLabel } from '../constants';
import { getPopoverButtonStyles } from '../utils';

View file

@ -6,6 +6,7 @@
*/
import { EuiContextMenuPanelId } from '@elastic/eui/src/components/context_menu/context_menu';
import type { DatasetSelection, DatasetSelectionChange } from '../../../common/dataset_selection';
import { SortOrder } from '../../../common/latest';
import { Dataset, Integration, IntegrationId } from '../../../common/datasets';
import { LoadDatasets, ReloadDatasets, SearchDatasets } from '../../hooks/use_datasets';
@ -15,7 +16,6 @@ import {
SearchIntegrations,
} from '../../hooks/use_integrations';
import { INTEGRATION_PANEL_ID, UNMANAGED_STREAMS_PANEL_ID } from './constants';
import type { DatasetSelection, DatasetSelectionChange } from '../../utils/dataset_selection';
export interface DatasetSelectorProps {
/* The generic data stream list */

View file

@ -8,8 +8,9 @@
import React, { useMemo } from 'react';
import { ScopedHistory } from '@kbn/core-application-browser';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DiscoverAppState, DiscoverStart } from '@kbn/discover-plugin/public';
import { DiscoverStart } from '@kbn/discover-plugin/public';
import type { BehaviorSubject } from 'rxjs';
import { DiscoverAppState } from '@kbn/discover-plugin/public/application/main/services/discover_app_state_container';
import {
createLogExplorerProfileCustomizations,
CreateLogExplorerProfileCustomizationsDeps,

View file

@ -33,11 +33,12 @@ export const createLogExplorerProfileCustomizations =
const [{ DatasetsService }, { initializeLogExplorerProfileStateService, waitForState }] =
await Promise.all([datasetServiceModuleLoadable, logExplorerMachineModuleLoadable]);
const datasetsService = new DatasetsService().start({
const datasetsClient = new DatasetsService().start({
http: core.http,
});
}).client;
const logExplorerProfileStateService = initializeLogExplorerProfileStateService({
datasetsClient,
stateContainer,
toasts: core.notifications.toasts,
});
@ -70,7 +71,7 @@ export const createLogExplorerProfileCustomizations =
id: 'search_bar',
CustomDataViewPicker: () => (
<LazyCustomDatasetSelector
datasetsClient={datasetsService.client}
datasetsClient={datasetsClient}
logExplorerProfileStateService={logExplorerProfileStateService}
/>
),

View file

@ -7,8 +7,8 @@
import { useSelector } from '@xstate/react';
import { useCallback } from 'react';
import { DatasetSelectionChange } from '../../common/dataset_selection';
import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile';
import { DatasetSelectionChange } from '../utils/dataset_selection';
export const useDatasetSelection = (
logExplorerProfileStateService: LogExplorerProfileStateService

View file

@ -6,6 +6,7 @@
*/
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public';
import { LogExplorerLocatorDefinition, LogExplorerLocators } from '../common/locators';
import { createLogExplorer } from './components/log_explorer';
import {
LogExplorerPluginSetup,
@ -15,9 +16,28 @@ import {
} from './types';
export class LogExplorerPlugin implements Plugin<LogExplorerPluginSetup, LogExplorerPluginStart> {
private locators?: LogExplorerLocators;
constructor(context: PluginInitializerContext) {}
public setup(core: CoreSetup, plugins: LogExplorerSetupDeps) {}
public setup(core: CoreSetup, plugins: LogExplorerSetupDeps) {
const { share, discover } = plugins;
// Register Locators
const logExplorerLocator = share.url.locators.create(
new LogExplorerLocatorDefinition({
discover,
})
);
this.locators = {
logExplorerLocator,
};
return {
locators: this.locators,
};
}
public start(core: CoreStart, plugins: LogExplorerStartDeps) {
const { data, discover } = plugins;

View file

@ -6,6 +6,7 @@
*/
import { HttpStart } from '@kbn/core/public';
import { Dataset, Integration } from '../../../common/datasets';
import {
DATASETS_URL,

View file

@ -6,7 +6,7 @@
*/
import { DatasetsClient } from './datasets_client';
import { DatasetsServiceStartDeps, DatasetsServiceSetup, DatasetsServiceStart } from './types';
import { DatasetsServiceSetup, DatasetsServiceStart, DatasetsServiceStartDeps } from './types';
export class DatasetsService {
constructor() {}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { AllDatasetSelection } from '../../../utils/dataset_selection';
import { AllDatasetSelection } from '../../../../common/dataset_selection';
import { ControlPanels, DefaultLogExplorerProfileState } from './types';
export const DEFAULT_CONTEXT: DefaultLogExplorerProfileState = {

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { InvokeCreator } from 'xstate';
import { Dataset } from '../../../../common/datasets';
import { SingleDatasetSelection } from '../../../../common/dataset_selection';
import { IDatasetsClient } from '../../../services/datasets';
import { LogExplorerProfileContext, LogExplorerProfileEvent } from './types';
interface LogExplorerProfileUrlStateDependencies {
datasetsClient: IDatasetsClient;
}
export const validateSelection =
({
datasetsClient,
}: LogExplorerProfileUrlStateDependencies): InvokeCreator<
LogExplorerProfileContext,
LogExplorerProfileEvent
> =>
(context) =>
async (send) => {
const unresolvedIntegrationName =
context.datasetSelection.selection.dataset.parentIntegration?.name;
const unresolvedDatasetName = context.datasetSelection.selection.dataset.name;
if (context.datasetSelection.selectionType !== 'unresolved' || !unresolvedIntegrationName) {
return send('LISTEN_TO_CHANGES');
}
try {
const { items } = await datasetsClient.findIntegrations({
nameQuery: unresolvedIntegrationName,
});
// There should only be one matching integration with the given name
// If no integration matches, skip the update and listen for user changes
const installedIntegration = items[0];
if (!installedIntegration) {
return send('LISTEN_TO_CHANGES');
}
// If no dataset matches the passed name for the retrieved integration,
// skip the update and listen for user changes
const targetDataset = installedIntegration.datasets.find(
(d) => d.name === unresolvedDatasetName
);
if (!targetDataset) {
return send('LISTEN_TO_CHANGES');
}
const dataset = Dataset.create(targetDataset, installedIntegration);
const datasetSelection = SingleDatasetSelection.create(dataset);
send({ type: 'UPDATE_DATASET_SELECTION', data: datasetSelection });
} catch (error) {
return send('DATASET_SELECTION_RESTORE_FAILURE');
}
};

View file

@ -8,8 +8,10 @@
import { IToasts } from '@kbn/core/public';
import { DiscoverStateContainer } from '@kbn/discover-plugin/public';
import { actions, createMachine, interpret, InterpreterFrom, raise } from 'xstate';
import { isDatasetSelection } from '../../../utils/dataset_selection';
import { IDatasetsClient } from '../../../services/datasets';
import { isDatasetSelection } from '../../../../common/dataset_selection';
import { createAndSetDataView } from './data_view_service';
import { validateSelection } from './selection_service';
import { DEFAULT_CONTEXT } from './defaults';
import {
createCreateDataViewFailedNotifier,
@ -33,7 +35,7 @@ import {
export const createPureLogExplorerProfileStateMachine = (
initialContext: LogExplorerProfileContext
) =>
/** @xstate-layout N4IgpgJg5mDOIC5QBkD2UCiAPADgG1QCcxCAFQ1AMwEs8wA6AVwDtrWAXagQz2oC9IAYgDaABgC6iUDlSxqnVMykgsiAIwAOAMz0NorVoDsAVi0a1x0QE4ATADYANCACe6zfVMbjeu-rWi7DQAWAF8QpzRMXAJiMgoaOno2eW5ePjYoADEKAFsAVUI8QQhFBjYAN1QAawZI7HwiEnIqWjKOVP4M7NR8woQK1ABjLgVmMXFx5Rk5UeVVBEsNekMrQK0bYKC7fztHF3VDII8vDTXDNUNtu2MwiPR6mKb41qT2nk7mLNyCopIKQno+BGlCIOXodWijTiLUSyU473Sn26vTw-WYlWGo3GkyQIGmKUUc0Qi2Wq20GyCWx2e1cCHM9CCXmuekMokO2y0txAEIasWaCTaKQRGQAIiMuAA1ahgADuxVKr0qNXB90hfOesLeaVF4qlsrRGJG1EU2IkU1kBKUuPmdhsTlpxjUR00Nis+g0NjUVis3i5PMe0IFryF2s+YvYkulcr+REBeGBoJVUV5Txhgvhoag4cj+oGmONYwkOOkFtm1uJdv2CDUFzsHkOjpshhsWguahsftVKcDLzhHURUAAwop2BQ8KQuMwwHhYPKp4rqrUuwH+b2tR8hyOxxOpzODUMjSai2bcfiy6B5qZjAyjIzDOYbNYm-bEFYDPRrJZNJpvI+NJ3kxXDV037DJh2YUdUHHSdp1nGMASBdgQUIMF-ShVdNRDDdwMg6Dd1gfd8yPCYTxLGYCyJBYtGvIJbw0e92yfQwXwQIJvXoN9jCsIIAiMV1XQAh50OA4MM34SB6AgcVYDAdgAGVpzAQZRiSCA6EEPJSBFABBAAVDAAH0dN07S5IwXSDLM5AMEHXSAEkAHkADlizxUsKPLatrCOZt720LRRFdIIbErWlmzUZZjEMaLNHMbRzkEtVUyDPsEQkqSIxk+TFOUgtVPU4zTPMyyMGs2zHKcgyACUMDk3SHJqgzMm0uzkDyGrXLPDyL3UbzlibDR-MC7iQpY8xDF0LjqNsIx6LURLuwwkC0ogSTpNkhS6FyxQmBwDKdQjPU5RKecBmVND1TTUT+3S9bsq2lTGD2o0w11KNCMPQsSMkU93MJTz21Megm18QJLnbRkaVfNQdFWURggMKKtnvBagKu1K0luzKNpyx7ns4V7DqjQR4LjBMUKTITLpS9cBFWjKuCyzalLx-bCZzGUPqxY8frIy1KPbdxLC0WxaLY8WghY20lmuBtRB41tHw7cJuWXYT0dpiTBi3KCAHEKCe2AmFYTWIEEOynLs+ztOQOyAC1DMHZzdKqhzkAM3XXc0gztNIOzOr+q0eurQGPEZCwBqbIJLhY-xWQ4rRGUpa5PXWVH1ZprC6fobWILHfXUEN-KwA0rS9IwEUjL07SDIlOyMAAdQD8j-uDmHLg4wLG3sN0rCdWPqKWGtKW9Aw+9tdPqbXLOtZ1vAC6L6g1JLzTjMd53Xfd0htKc0q5Ob-mAYMHRtiCYxAlOMwgn7qsazsCb9DsWjH3htRdkn5Lp7E7Pc9whecCNk9Nmm485QR3LBOcbQlRLkAhnL+N1Vq-3zgbABu1gE4W3DBPceZPqml5m5FuQcVBuBsNeYw4djCR0ODHW+noJqUN8GfeGoh9AGA-j2TC39Z6gPnigwB+MwJz3ATOEmhB-hkyQomC6n9OEIJznPf+-D0FCKwQRHB3NvrmkIQLUO5DzCUPvFHGhtIYb+HoLaOwWhdjGG8CYfQYQVbMFQBAOAyhpEcLAFow+wcAC0UMEA+OvN6YJITQmchVu4paxsMbiQgF488xDWKhTcPQow3Fz4rBhi2dhUSYkDmRD8eJ3VEkDV0IcKw4VAotkCCxbiH5r7aECI6YI2hQgRLVlPWRwp2ZHSKa3RJKwrAeD7jWfwidgqrFqUceijpWQ-m-PfHJIk8mCJ4cI+Av1tGeXsDLXwFhyGGGoosSWVZGRDMCByYKVwmxWCWRrGecTNneIGZSegFg7H8SbCYfxZ8T6nBhlct+Ny7B3Mzlw+md1mbbSIV1fp8wVg2DeVFSwnzornxYuQiKbYEUGBbIcUF8CVprWxvdFmeUl50D6UQ+YwRRBIo+bYL56KqwpzeTWUhpg-zUXCXcWBnTlqYwhSSqFrMXpZjerKKlAsthDLfNsUWdgKnXysCxQ5EVtjeFMOPDJbTeVUxkQK2J8ieGKKlZ5BF9KUWMrRf4qw2gGRsU0MyGGXhbntL5Qa66RKkF6z4dE02Zrg4+iWIcRk6xvQsI0G+WOfUbA8W0BcEaFhdWqw9R4r1grjV-z9RSzxTyEnzDfmY9JrZmyWPPtHWOnprzaFMEClOrSCVdMzT63hhdUFALFRgsBqjA2JNpZa4aIUbWxzfkMzQjIPTNjjefZWYQgA */
/** @xstate-layout N4IgpgJg5mDOIC5QBkD2UCiAPADgG1QCcxCAFQ1AMwEs8wA6AVwDtrWAXagQz2oC9IAYgDaABgC6iUDlSxqnVMykgsiAIwAOAMz0NagEwBWUdoBsWgOxaAnEYA0IAJ7rt9UxesWD+2xf2WAXwCHNExcAmIyCho6ejZ5bl4+NigAMQoAWwBVQjxBCEUGNgA3VABrBlDsfCIScipaIo5E-hT01GzchBLUAGMuBWYxcWHlGTlB5VUEYw1dfQ1RQ30LC0NrbX0HZwQ1CwAWemtDLTV991FTPWWtIJD0aoi66Ma45p5W5jTMnLySCkI9HwA0oRAy9Cq4VqUQasXinA+yS+7U6eG6zFK-UGw1GSBA4wSiimiFm80Wy1W60220QenoxlEjP2J32alEmmsdxAkJqkXqMSaCURKQAIgMuAA1ahgADu+UKb1KFQhDyhfJecPeSVF4qlsvRmIG1EUOIkY1khKUeOmPkM9D8pkMFm0bMpFhpM3Obi0tqdxk0Fi5PKeMIFbyF2q+YvYkulcv+RCBeBBYJVYV5z1hgoRkag0dj+p6WONQwkuOkFsm1sQtvt+kdztOojdHquonoah91h9WkWS1MQdVGdDr3hLSRUAAwop2BQ8KQuMwwHhYPKl4rypUhyH+aOtZ8pzO5wulyuDX0jSay2a8QSq6BphZTNZ6Po1O+jMy++YPScLEdLi0UwDG7Z9jkHdMdw1bNxxSadmFnVB50XZdVwTQFgXYUFCHBYNoV3TUIwPeDEOQ09YHPYsrxGG8KwmEtiQQJ8XzfD9DC-RkfycRB9msdt9kuBYNA0Dxlg0Adgm5bd8Og8McwPABlGN2DAEiuDYEg1yaJUt0gmSszk2CviUgZVJndSl0ISjL1LGjJFvSsGOrBAtC0Q4tFEc5DHE-YPI0XjTA9NRDCdI4rhEvR9HrEKIMefSwzHYVjOUsyEIszT0KTFMcLTOL1QMxLcxMlS1I0qyixs017Loy1GNc9zPMdHy-ICoLDDZehzg0Wx3z4vtbkkvD8oS-cBAgegIHFWAwHYBTlzAXpBnoYoPkmzhjPmxaS0EZAAEkFIAFQwAA5AB9A6AHlTsnAAJABBY6AHEMAU8t8UcolnOE-9fLWZ9NEsAwgtc9ttG6p0fQsQCNFitVMxGoixomqaZrmugtsUZbVqNDb0cGQQslIEU7qO07iYOu6FIwA7Tqp5AMEnA7dou463rvJyH1pETOssQx-u0Lwtm43Yzjtbz1jZGwDg2Ab7j04a90RyBkZjabZs2paVt4NaUjRhb8fJynqdpjB6cZ5mzoAJRey7rdO1I7t25AsmttmPqtTmEG+nm-usAHBba9rOp67trGffz-Nh4cCJgxFlbWrg1b1jHmDiCA6AJomSYwMmSaNmm6YZpmWbd+jPs9s5PFff19jWZsDH2IWdk7MP7UdUx-GbdxTGbKOoIK0b45R9W8ZLNOM8NqmC9NouLdO63Douu2Hadl2MFL2rnMr-8jHZWvjEFxvgcZehTn0fZIr48WYcG6SFcI+SkYTpONbHxgcB1qNdTjLSN2VIb4aK0fkPVWqNX6Y3fp-PM39CwYgvNia81V3plw9ioGsbI1CvnfJ5SwtdTB4OBmoF83k+KmHcCcNyN85Z5UAQ-ccIDE5gNHhAj+ONoExj1PGQgAIspYVTAAkcdC47jWfkw-Wb9WHrXYQWGU1kEF2XNCgxib52RYLZL9PBBDhadncPQEwfF-BXGWKIBYfd4pAPoSI4eyclqQLYcVVKMYyq-x6P-O+tDY5JAYS-Zhqc7FSIcaVSyciSxVUUZvT2KjMGsRwQcJ8Wjm7sUwdYEOlgnxOk5LfeWHjDLCJVowke4iWFQMCeZZxmVMLYVwu4wRnj+DeLESnJgkjdYpSCSQEJ1EN73jQQgFR+h6TnGsL5TQQlz5BWCnMEwEsTGeSipk6hcNam5K8eNXoR4kKPQoO-WATBWCDwgIIXax1dpMzuntAAWjnScLMDqWwusgU6j17mE1OndUgu1ukc16bYQ4ngyHuDDs2ICgVtFrB+syICfse5QxsGY++dSkbrIQnOLZqAdnjzAIIQ2p0JS7QwAAdVOoTcmGARRfPLr098T5T6+T4s6RYWhvIen0J5O0TLgpkPWN3FY8KcmFXqWsjZeA0UYuoOnLFJLs7XVufcx5pAHqm1erRZBESqWdh0EsWu-kiG1zWEFDQJxdAbHaqIYZVxnQSUWdHWSAqkXCtFTgXZ-i4LCpPKhFxcC3HZOWXa5WyLSKOudS0r4JFjwoTPBVeRFLUHTF+UcJ8ZCPAwpBUFPi7kNjnGlm+c1fLfUHPoAG1F2ynXNKgWGpC7qVyCAqcmPhOUBExxWYKwtDqS3BvLW6iNFEo2hMQeEnpcbz4JoBcm4FZC01V0NWoHuYd8F9SCJJZgqAIBwGUI26CA7vnTAALSgp2Duu0KTj0npPVFPNTaWB+ogFuyl0wj7aI0AMwFBxOx8ydG+C9trRptB+LkW9saazOmNf5aZ+CfTMjai+PYngjCdgOJ5bQX6B6Ix1BwuMAHGLuFbPg0+6xTV6EuMyK1UkfVNrta6lFlbu2YecsYAZjciGms8qyVlhhfxRQ7F2KW58Fh+2QwjR+rTTLtMILRz22HtHtRYhyN87gpZMoExY4R4nensR0E6PQz5WTsm+h6Tw7ZvIwuZBSZ8stSM0PzUrKxoDCkp1U9MCW9JnQzuGRgvT2ilhTMNSk8+axOz7CU0I1Z+SfFFNTlrcV9jwGoPZnexA5hiEue0+5kSx9-xgxScJXiWqFkWaWeRgtoi7NLXFXQBz6gjDPq8H7KkAkbDpd0DYfs6wTGdiC4ihpJWJFQPzJwiruwRIMf8rxXyzZmSdmPi+VyfFJawfEoYDrzan7WJi2W+xbSymWQG4Y0wnURvNYg0yiZ-ksHn20LxXsIUSMbpQ8AoVVGRXtoG05zTrmdOaDS9oshL5hkUL9D3KKVD8s2ru5Y1tj2g17OvQNtYcwZ2smy+Qp8WggrOnbCHYCLmNX6CW9eiHgb22YoG2cDqrJWR+Dwf6DQaaTG6GluJNy13nR44LUWzZROXWhq7eRAbiXnNabc7pr7zdDV7cQ+1CbthnyBkXUAA */
createMachine<LogExplorerProfileContext, LogExplorerProfileEvent, LogExplorerProfileTypeState>(
{
context: initialContext,
@ -96,8 +98,26 @@ export const createPureLogExplorerProfileStateMachine = (
type: 'parallel',
states: {
datasetSelection: {
initial: 'idle',
initial: 'validatingSelection',
states: {
validatingSelection: {
invoke: {
src: 'validateSelection',
},
on: {
LISTEN_TO_CHANGES: {
target: 'idle',
},
UPDATE_DATASET_SELECTION: {
target: 'updatingDataView',
actions: ['storeDatasetSelection'],
},
DATASET_SELECTION_RESTORE_FAILURE: {
target: 'updatingDataView',
actions: ['notifyDatasetSelectionRestoreFailed'],
},
},
},
idle: {
invoke: {
src: 'listenUrlChange',
@ -218,12 +238,14 @@ export const createPureLogExplorerProfileStateMachine = (
export interface LogExplorerProfileStateMachineDependencies {
initialContext?: LogExplorerProfileContext;
datasetsClient: IDatasetsClient;
stateContainer: DiscoverStateContainer;
toasts: IToasts;
}
export const createLogExplorerProfileStateMachine = ({
initialContext = DEFAULT_CONTEXT,
datasetsClient,
stateContainer,
toasts,
}: LogExplorerProfileStateMachineDependencies) =>
@ -240,6 +262,7 @@ export const createLogExplorerProfileStateMachine = ({
subscribeControlGroup: subscribeControlGroup({ stateContainer }),
updateControlPanels: updateControlPanels({ stateContainer }),
updateStateContainer: updateStateContainer({ stateContainer }),
validateSelection: validateSelection({ datasetsClient }),
},
});

View file

@ -8,7 +8,7 @@
import * as rt from 'io-ts';
import { ControlGroupAPI } from '@kbn/controls-plugin/public';
import { DoneInvokeEvent } from 'xstate';
import type { DatasetEncodingError, DatasetSelection } from '../../../utils/dataset_selection';
import type { DatasetEncodingError, DatasetSelection } from '../../../../common/dataset_selection';
export interface WithDatasetSelection {
datasetSelection: DatasetSelection;
@ -49,6 +49,10 @@ export type LogExplorerProfileTypeState =
value: 'initialized';
context: WithDatasetSelection & WithControlPanels;
}
| {
value: 'initialized.datasetSelection.validatingSelection';
context: WithDatasetSelection & WithControlPanels;
}
| {
value: 'initialized.datasetSelection.idle';
context: WithDatasetSelection & WithControlPanels;
@ -79,6 +83,9 @@ export type LogExplorerProfileContext = LogExplorerProfileTypeState['context'];
export type LogExplorerProfileStateValue = LogExplorerProfileTypeState['value'];
export type LogExplorerProfileEvent =
| {
type: 'LISTEN_TO_CHANGES';
}
| {
type: 'UPDATE_DATASET_SELECTION';
data: DatasetSelection;

View file

@ -10,16 +10,16 @@ import deepEqual from 'fast-deep-equal';
import { DiscoverAppState, DiscoverStateContainer } from '@kbn/discover-plugin/public';
import type { DataView } from '@kbn/data-views-plugin/public';
import { ROWS_HEIGHT_OPTIONS } from '@kbn/unified-data-table';
import {
DATA_GRID_COLUMNS_PREFERENCES,
DATA_GRID_DEFAULT_COLUMNS,
} from '../../../../common/constants';
import {
AllDatasetSelection,
decodeDatasetSelectionId,
hydrateDatasetSelection,
isDatasetSelection,
} from '../../../utils/dataset_selection';
} from '../../../../common/dataset_selection';
import {
DATA_GRID_COLUMNS_PREFERENCES,
DATA_GRID_DEFAULT_COLUMNS,
} from '../../../../common/constants';
import {
ControlPanelRT,
ControlPanels,

View file

@ -5,17 +5,23 @@
* 2.0.
*/
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DiscoverStart } from '@kbn/discover-plugin/public';
import type { DiscoverSetup, DiscoverStart } from '@kbn/discover-plugin/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import type { ComponentType } from 'react';
import { LogExplorerLocators } from '../common/locators';
import type { LogExplorerProps } from './components/log_explorer';
export type LogExplorerPluginSetup = void;
export interface LogExplorerPluginSetup {
locators: LogExplorerLocators;
}
export interface LogExplorerPluginStart {
LogExplorer: ComponentType<LogExplorerProps>;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface LogExplorerSetupDeps {}
export interface LogExplorerSetupDeps {
share: SharePluginSetup;
discover: DiscoverSetup;
}
export interface LogExplorerStartDeps {
data: DataPublicPluginStart;

View file

@ -20,7 +20,9 @@
"@kbn/data-plugin",
"@kbn/unified-field-list",
"@kbn/core-application-browser",
"@kbn/share-plugin",
"@kbn/unified-data-table",
"@kbn/deeplinks-observability"
],
"exclude": ["target/**/*"]
}

View file

@ -4,8 +4,11 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { DISCOVER_APP_ID } from '@kbn/deeplinks-analytics';
import React, { useEffect } from 'react';
import {
AllDatasetsLocatorParams,
ALL_DATASETS_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { useHasData } from '../../hooks/use_has_data';
import { useKibana } from '../../utils/kibana_react';
@ -14,6 +17,7 @@ export function LandingPage() {
const {
application: { navigateToUrl, navigateToApp },
http: { basePath },
share: { url },
} = useKibana().services;
useEffect(() => {
@ -23,16 +27,17 @@ export function LandingPage() {
const hasLogsData = logs?.hasData;
if (hasLogsData) {
navigateToApp(DISCOVER_APP_ID, {
deepLinkId: 'log-explorer',
});
const allDataSetsLocator =
url.locators.get<AllDatasetsLocatorParams>(ALL_DATASETS_LOCATOR_ID);
allDataSetsLocator?.navigate({});
} else if (hasApmData) {
navigateToUrl(basePath.prepend('/app/apm/services'));
} else {
navigateToUrl(basePath.prepend('/app/observabilityOnboarding'));
}
}
}, [basePath, hasDataMap, isAllRequestsComplete, navigateToApp, navigateToUrl]);
}, [basePath, hasDataMap, isAllRequestsComplete, navigateToApp, navigateToUrl, url.locators]);
return <></>;
}

View file

@ -82,11 +82,11 @@
"@kbn/data-view-editor-plugin",
"@kbn/actions-plugin",
"@kbn/core-capabilities-common",
"@kbn/deeplinks-analytics",
"@kbn/observability-ai-assistant-plugin",
"@kbn/osquery-plugin",
"@kbn/aiops-plugin",
"@kbn/content-management-plugin"
"@kbn/content-management-plugin",
"@kbn/deeplinks-observability"
],
"exclude": [
"target/**/*"

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export { SingleDatasetLocatorDefinition, AllDatasetsLocatorDefinition } from './locators';

View file

@ -0,0 +1,34 @@
/*
* 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 type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public';
import { AllDatasetSelection } from '@kbn/log-explorer-plugin/common';
import {
AllDatasetsLocatorParams,
ALL_DATASETS_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { DatasetLocatorDependencies } from '../types';
import { constructLocatorPath } from '../utils';
export type AllDatasetsLocator = LocatorPublic<AllDatasetsLocatorParams>;
export class AllDatasetsLocatorDefinition implements LocatorDefinition<AllDatasetsLocatorParams> {
public readonly id = ALL_DATASETS_LOCATOR_ID;
constructor(protected readonly deps: DatasetLocatorDependencies) {}
public readonly getLocation = (params: AllDatasetsLocatorParams) => {
const { useHash } = this.deps;
const index = AllDatasetSelection.create().toDataviewSpec().id;
return constructLocatorPath({
locatorParams: params,
index,
useHash,
});
};
}

View file

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

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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { AllDatasetsLocator } from './all_datasets';
import { SingleDatasetLocator } from './single_dataset';
export * from './single_dataset';
export * from './all_datasets';
export * from './utils';
export interface ObservabilityLogExplorerLocators {
allDatasetsLocator: AllDatasetsLocator;
singleDatasetLocator: SingleDatasetLocator;
}

View file

@ -0,0 +1,333 @@
/*
* 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 { FilterStateStore } from '@kbn/es-query';
import { getStatesFromKbnUrl } from '@kbn/kibana-utils-plugin/public';
import {
AllDatasetsLocatorParams,
SingleDatasetLocatorParams,
} from '@kbn/deeplinks-observability/locators';
import { OBSERVABILITY_LOG_EXPLORER_APP_ID } from '../constants';
import { AllDatasetsLocatorDefinition } from './all_datasets/all_datasets_locator';
import { SingleDatasetLocatorDefinition } from './single_dataset';
import { DatasetLocatorDependencies } from './types';
const setup = async () => {
const dep: DatasetLocatorDependencies = {
useHash: false,
};
const allDatasetsLocator = new AllDatasetsLocatorDefinition(dep);
const singleDatasetLocator = new SingleDatasetLocatorDefinition(dep);
return {
allDatasetsLocator,
singleDatasetLocator,
};
};
describe('Observability Logs Explorer Locators', () => {
const timeRange = { to: 'now', from: 'now-30m' };
describe('All Dataset Locator', () => {
it('should create a link with no state', async () => {
const { allDatasetsLocator } = await setup();
const location = await allDatasetsLocator.getLocation({});
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: '/?_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA)',
state: {},
});
});
it('should allow specifiying time range', async () => {
const params: AllDatasetsLocatorParams = {
timeRange,
};
const { allDatasetsLocator } = await setup();
const location = await allDatasetsLocator.getLocation(params);
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: '/?_g=(time:(from:now-30m,to:now))&_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA)',
state: {},
});
});
it('should allow specifiying query', async () => {
const params: AllDatasetsLocatorParams = {
query: {
language: 'kuery',
query: 'foo',
},
};
const { allDatasetsLocator } = await setup();
const location = await allDatasetsLocator.getLocation(params);
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: '/?_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA,query:(language:kuery,query:foo))',
state: {},
});
});
it('should allow specifiying refresh interval', async () => {
const params: AllDatasetsLocatorParams = {
refreshInterval: {
pause: false,
value: 666,
},
};
const { allDatasetsLocator } = await setup();
const location = await allDatasetsLocator.getLocation(params);
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: '/?_g=(refreshInterval:(pause:!f,value:666))&_a=(index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA)',
state: {},
});
});
it('should allow specifiying columns and sort', async () => {
const params: AllDatasetsLocatorParams = {
columns: ['_source'],
sort: [['timestamp, asc']] as string[][],
};
const { allDatasetsLocator } = await setup();
const location = await allDatasetsLocator.getLocation(params);
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: `/?_a=(columns:!(_source),index:BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA,sort:!(!('timestamp,%20asc')))`,
state: {},
});
});
it('should allow specifiying filters', async () => {
const params: AllDatasetsLocatorParams = {
filters: [
{
meta: {
alias: 'foo',
disabled: false,
negate: false,
},
$state: {
store: FilterStateStore.APP_STATE,
},
},
{
meta: {
alias: 'bar',
disabled: false,
negate: false,
},
$state: {
store: FilterStateStore.GLOBAL_STATE,
},
},
],
};
const { allDatasetsLocator } = await setup();
const { path } = await allDatasetsLocator.getLocation(params);
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g'], { getFromHashQuery: false });
expect(_a).toEqual({
filters: [
{
$state: {
store: 'appState',
},
meta: {
alias: 'foo',
disabled: false,
negate: false,
},
},
],
index: 'BQZwpgNmDGAuCWB7AdgFQJ4AcwC4CGEEAlEA',
});
expect(_g).toEqual({
filters: [
{
$state: {
store: 'globalState',
},
meta: {
alias: 'bar',
disabled: false,
negate: false,
},
},
],
});
});
});
describe('Single Dataset Locator', () => {
const integration = 'Test';
const dataset = 'test-*';
it('should create a link with correct index', async () => {
const { singleDatasetLocator } = await setup();
const location = await singleDatasetLocator.getLocation({
integration,
dataset,
});
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: `/?_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA)`,
state: {},
});
});
it('should allow specifiying time range', async () => {
const params: SingleDatasetLocatorParams = {
integration,
dataset,
timeRange,
};
const { singleDatasetLocator } = await setup();
const location = await singleDatasetLocator.getLocation(params);
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: `/?_g=(time:(from:now-30m,to:now))&_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA)`,
state: {},
});
});
it('should allow specifiying query', async () => {
const params: SingleDatasetLocatorParams = {
integration,
dataset,
query: {
language: 'kuery',
query: 'foo',
},
};
const { singleDatasetLocator } = await setup();
const location = await singleDatasetLocator.getLocation(params);
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: `/?_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA,query:(language:kuery,query:foo))`,
state: {},
});
});
it('should allow specifiying refresh interval', async () => {
const params: SingleDatasetLocatorParams = {
integration,
dataset,
refreshInterval: {
pause: false,
value: 666,
},
};
const { singleDatasetLocator } = await setup();
const location = await singleDatasetLocator.getLocation(params);
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: `/?_g=(refreshInterval:(pause:!f,value:666))&_a=(index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA)`,
state: {},
});
});
it('should allow specifiying columns and sort', async () => {
const params: SingleDatasetLocatorParams = {
integration,
dataset,
columns: ['_source'],
sort: [['timestamp, asc']] as string[][],
};
const { singleDatasetLocator } = await setup();
const location = await singleDatasetLocator.getLocation(params);
expect(location).toMatchObject({
app: OBSERVABILITY_LOG_EXPLORER_APP_ID,
path: `/?_a=(columns:!(_source),index:BQZwpgNmDGAuCWB7AdgLmAEwIay%2BW6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA,sort:!(!('timestamp,%20asc')))`,
state: {},
});
});
it('should allow specifiying filters', async () => {
const params: SingleDatasetLocatorParams = {
integration,
dataset,
filters: [
{
meta: {
alias: 'foo',
disabled: false,
negate: false,
},
$state: {
store: FilterStateStore.APP_STATE,
},
},
{
meta: {
alias: 'bar',
disabled: false,
negate: false,
},
$state: {
store: FilterStateStore.GLOBAL_STATE,
},
},
],
};
const { singleDatasetLocator } = await setup();
const { path } = await singleDatasetLocator.getLocation(params);
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g'], { getFromHashQuery: false });
expect(_a).toEqual({
filters: [
{
$state: {
store: 'appState',
},
meta: {
alias: 'foo',
disabled: false,
negate: false,
},
},
],
index:
'BQZwpgNmDGAuCWB7AdgLmAEwIay+W6yWAtmKgOQSIDmIAtLGCLHQFRvkA0CsUqjzAJScipVABUmsYeChwkycQE8ADmQCuyAE5NEEAG5gMgoA',
});
expect(_g).toEqual({
filters: [
{
$state: {
store: 'globalState',
},
meta: {
alias: 'bar',
disabled: false,
negate: false,
},
},
],
});
});
});
});

View file

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

View file

@ -0,0 +1,50 @@
/*
* 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 type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public';
import { UnresolvedDatasetSelection } from '@kbn/log-explorer-plugin/common';
import type { IndexPattern } from '@kbn/io-ts-utils';
import {
SingleDatasetLocatorParams,
SINGLE_DATASET_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { DatasetLocatorDependencies } from '../types';
import { constructLocatorPath } from '../utils';
export type SingleDatasetLocator = LocatorPublic<SingleDatasetLocatorParams>;
export class SingleDatasetLocatorDefinition
implements LocatorDefinition<SingleDatasetLocatorParams>
{
public readonly id = SINGLE_DATASET_LOCATOR_ID;
constructor(protected readonly deps: DatasetLocatorDependencies) {}
public readonly getLocation = (params: SingleDatasetLocatorParams) => {
const { useHash } = this.deps;
const { integration, dataset } = params;
const unresolvedDatasetSelection = UnresolvedDatasetSelection.fromSelection({
name: integration,
dataset: {
name: this.composeIndexPattern(dataset),
},
});
const index = unresolvedDatasetSelection.toDataviewSpec().id;
return constructLocatorPath({
locatorParams: params,
index,
useHash,
});
};
private composeIndexPattern(datasetName: SingleDatasetLocatorParams['dataset']) {
return `logs-${datasetName}-*` as IndexPattern;
}
}

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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { AggregateQuery, Filter, Query } from '@kbn/es-query';
export interface AppState {
index?: string;
query?: Query | AggregateQuery;
filters?: Filter[];
columns?: string[];
sort?: string[][];
}
export interface DatasetLocatorDependencies {
useHash: boolean;
}

View file

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public';
import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/common';
import { DatasetLocatorParams } from '@kbn/deeplinks-observability/locators';
import { AppState } from '../types';
interface LocatorPathCosntructionParams {
locatorParams: DatasetLocatorParams;
index: string;
useHash: boolean;
}
export const constructLocatorPath = async (params: LocatorPathCosntructionParams) => {
const { isFilterPinned } = await import('@kbn/es-query');
const {
locatorParams: { filters, query, refreshInterval, timeRange, columns, sort },
index,
useHash,
} = params;
const appState: AppState = {};
const queryState: GlobalQueryStateFromUrl = {};
// App state
if (index) appState.index = index;
if (query) appState.query = query;
if (filters && filters.length) appState.filters = filters?.filter((f) => !isFilterPinned(f));
if (columns) appState.columns = columns;
if (sort) appState.sort = sort;
// Global State
if (timeRange) queryState.time = timeRange;
if (filters && filters.length) queryState.filters = filters?.filter((f) => isFilterPinned(f));
if (refreshInterval) queryState.refreshInterval = refreshInterval;
let path = '/';
if (Object.keys(queryState).length) {
path = setStateToKbnUrl<GlobalQueryStateFromUrl>(
'_g',
queryState,
{ useHash, storeInHashQuery: false },
path
);
}
path = setStateToKbnUrl('_a', appState, { useHash, storeInHashQuery: false }, path);
return {
app: 'observability-log-explorer',
path,
state: {},
};
};

View file

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

View file

@ -16,11 +16,15 @@
"discover",
"logExplorer",
"observabilityShared",
"share"
"share",
"kibanaUtils",
],
"optionalPlugins": [
"serverless"
],
"requiredBundles": ["kibanaReact", "observabilityOnboarding"]
"requiredBundles": ["kibanaReact"],
"extraPublicDirs": [
"common",
]
}
}

View file

@ -25,7 +25,7 @@ import { LogExplorerStateContainer } from '@kbn/log-explorer-plugin/public';
import {
OBSERVABILITY_ONBOARDING_LOCATOR,
ObservabilityOnboardingLocatorParams,
} from '@kbn/observability-onboarding-plugin/public';
} from '@kbn/deeplinks-observability/locators';
import { KibanaReactContextValue } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { css } from '@emotion/react';

View file

@ -13,6 +13,11 @@ import {
Plugin,
PluginInitializerContext,
} from '@kbn/core/public';
import {
ObservabilityLogExplorerLocators,
SingleDatasetLocatorDefinition,
AllDatasetsLocatorDefinition,
} from '../common/locators';
import { type ObservabilityLogExplorerConfig } from '../common/plugin_config';
import { OBSERVABILITY_LOG_EXPLORER_APP_ID } from '../common/constants';
import { logExplorerAppTitle } from '../common/translations';
@ -28,6 +33,7 @@ export class ObservabilityLogExplorerPlugin
implements Plugin<ObservabilityLogExplorerPluginSetup, ObservabilityLogExplorerPluginStart>
{
private config: ObservabilityLogExplorerConfig;
private locators?: ObservabilityLogExplorerLocators;
constructor(context: PluginInitializerContext<ObservabilityLogExplorerConfig>) {
this.config = context.config.get();
@ -37,6 +43,9 @@ export class ObservabilityLogExplorerPlugin
core: CoreSetup<ObservabilityLogExplorerStartDeps, ObservabilityLogExplorerPluginStart>,
_pluginsSetup: ObservabilityLogExplorerSetupDeps
) {
const { share } = _pluginsSetup;
const useHash = core.uiSettings.get('state:storeInSessionStorage');
core.application.register({
id: OBSERVABILITY_LOG_EXPLORER_APP_ID,
title: logExplorerAppTitle,
@ -58,7 +67,26 @@ export class ObservabilityLogExplorerPlugin
},
});
return {};
// Register Locators
const singleDatasetLocator = share.url.locators.create(
new SingleDatasetLocatorDefinition({
useHash,
})
);
const allDatasetsLocator = share.url.locators.create(
new AllDatasetsLocatorDefinition({
useHash,
})
);
this.locators = {
singleDatasetLocator,
allDatasetsLocator,
};
return {
locators: this.locators,
};
}
public start(_core: CoreStart, _pluginsStart: ObservabilityLogExplorerStartDeps) {

View file

@ -6,20 +6,23 @@
*/
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { DiscoverStart } from '@kbn/discover-plugin/public';
import { LogExplorerPluginStart } from '@kbn/log-explorer-plugin/public';
import { DiscoverStart } from '@kbn/discover-plugin/public';
import { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public';
import { ServerlessPluginStart } from '@kbn/serverless/public';
import { SharePluginStart } from '@kbn/share-plugin/public';
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import { ObservabilityLogExplorerLocators } from '../common/locators';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ObservabilityLogExplorerPluginSetup {}
export interface ObservabilityLogExplorerPluginSetup {
locators: ObservabilityLogExplorerLocators;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ObservabilityLogExplorerPluginStart {}
export interface ObservabilityLogExplorerSetupDeps {
serverless?: ServerlessPluginStart;
share: SharePluginSetup;
}
export interface ObservabilityLogExplorerStartDeps {

View file

@ -22,11 +22,14 @@
"@kbn/serverless",
"@kbn/core-chrome-browser",
"@kbn/config-schema",
"@kbn/kibana-utils-plugin",
"@kbn/core-application-browser",
"@kbn/discover-plugin",
"@kbn/observability-onboarding-plugin",
"@kbn/es-query",
"@kbn/react-kibana-mount",
"@kbn/share-plugin",
"@kbn/io-ts-utils",
"@kbn/deeplinks-observability"
],
"exclude": [
"target/**/*"

View file

@ -618,12 +618,12 @@ describe('[Logs onboarding] Custom logs - install elastic agent', () => {
.should('exist');
});
it('when user clicks on Explore Logs it navigates to discover', () => {
it('when user clicks on Explore Logs it navigates to observability log explorer', () => {
cy.wait('@checkOnboardingProgress');
cy.getByTestSubj('obltOnboardingExploreLogs').should('exist').click();
cy.url().should('include', '/app/discover');
cy.get('button[title="logs-*"]').should('exist');
cy.url().should('include', '/app/observability-log-explorer');
cy.get('button').contains('[mylogs] mylogs').should('exist');
});
});
});

View file

@ -645,13 +645,13 @@ describe('[Logs onboarding] System logs', () => {
.should('exist');
});
it('when user clicks on Explore Logs it navigates to discover', () => {
it('when user clicks on Explore Logs it navigates to observability log explorer', () => {
cy.wait('@systemIntegrationInstall');
cy.wait('@checkOnboardingProgress');
cy.getByTestSubj('obltOnboardingExploreLogs').should('exist').click();
cy.url().should('include', '/app/discover');
cy.get('button[title="logs-*"]').should('exist');
cy.url().should('include', '/app/observability-log-explorer');
cy.get('button').contains('[System] syslog').should('exist');
});
});
});

View file

@ -15,6 +15,10 @@ import {
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { default as React, useCallback, useEffect, useState } from 'react';
import {
SingleDatasetLocatorParams,
SINGLE_DATASET_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { ObservabilityOnboardingPluginSetupDeps } from '../../../plugin';
import { useWizard } from '.';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
@ -34,25 +38,38 @@ import {
} from '../../shared/step_panel';
import { ApiKeyBanner } from './api_key_banner';
import { BackButton } from './back_button';
import { getDiscoverNavigationParams } from '../utils';
import { WindowsInstallStep } from '../../shared/windows_install_step';
import { TroubleshootingLink } from '../../shared/troubleshooting_link';
export function InstallElasticAgent() {
const {
services: {
discover: { locator },
},
services: { share },
} = useKibana<ObservabilityOnboardingPluginSetupDeps>();
const singleDatasetLocator =
share.url.locators.get<SingleDatasetLocatorParams>(
SINGLE_DATASET_LOCATOR_ID
);
const { goBack, getState, setState } = useWizard();
const wizardState = getState();
const {
integrationName: integration,
datasetName: dataset,
autoDownloadConfig,
} = wizardState;
const [elasticAgentPlatform, setElasticAgentPlatform] =
useState<ElasticAgentPlatform>('linux-tar');
const enforcedDatasetName =
integration === dataset ? dataset : `${integration}.${dataset}`;
async function onContinue() {
await locator?.navigate(
getDiscoverNavigationParams([wizardState.datasetName])
);
await singleDatasetLocator!.navigate({
integration,
dataset: enforcedDatasetName,
});
}
function onAutoDownloadConfig() {
@ -258,7 +275,7 @@ export function InstallElasticAgent() {
</p>
</EuiText>
<EuiSpacer size="m" />
{wizardState.integrationName && (
{integration && (
<>
<EuiCallOut
title={i18n.translate(
@ -266,7 +283,7 @@ export function InstallElasticAgent() {
{
defaultMessage: '{integrationName} integration installed.',
values: {
integrationName: wizardState.integrationName,
integrationName: integration,
},
}
)}
@ -331,10 +348,10 @@ export function InstallElasticAgent() {
apiEndpoint: setup?.apiEndpoint,
scriptDownloadUrl: setup?.scriptDownloadUrl,
elasticAgentVersion: setup?.elasticAgentVersion,
autoDownloadConfig: wizardState.autoDownloadConfig,
autoDownloadConfig,
onboardingId,
})}
autoDownloadConfig={wizardState.autoDownloadConfig}
autoDownloadConfig={autoDownloadConfig}
onToggleAutoDownloadConfig={onAutoDownloadConfig}
installAgentStatus={
installShipperSetupStatus === FETCH_STATUS.LOADING

View file

@ -16,7 +16,10 @@ import {
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { default as React, useCallback, useEffect, useState } from 'react';
import { getSystemLogsDataStreams } from '../../../../common/elastic_agent_logs';
import {
SingleDatasetLocatorParams,
SINGLE_DATASET_LOCATOR_ID,
} from '@kbn/deeplinks-observability/locators';
import { ObservabilityOnboardingPluginSetupDeps } from '../../../plugin';
import { useWizard } from '.';
import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
@ -36,18 +39,20 @@ import {
StepPanelFooter,
} from '../../shared/step_panel';
import { ApiKeyBanner } from '../custom_logs/api_key_banner';
import { getDiscoverNavigationParams } from '../utils';
import { WindowsInstallStep } from '../../shared/windows_install_step';
import { SystemIntegrationBanner } from './system_integration_banner';
import { TroubleshootingLink } from '../../shared/troubleshooting_link';
export function InstallElasticAgent() {
const {
services: {
discover: { locator },
},
services: { share },
} = useKibana<ObservabilityOnboardingPluginSetupDeps>();
const singleDatasetLocator =
share.url.locators.get<SingleDatasetLocatorParams>(
SINGLE_DATASET_LOCATOR_ID
);
const { navigateToKibanaUrl } = useKibanaNavigation();
const { getState, setState } = useWizard();
const wizardState = getState();
@ -60,11 +65,10 @@ export function InstallElasticAgent() {
navigateToKibanaUrl('/app/observabilityOnboarding');
}
async function onContinue() {
const dataStreams = getSystemLogsDataStreams();
const dataSets = dataStreams.map(
(dataSream) => dataSream.data_stream.dataset
);
await locator?.navigate(getDiscoverNavigationParams(dataSets));
await singleDatasetLocator!.navigate({
integration: 'system',
dataset: 'system.syslog',
});
}
function onAutoDownloadConfig() {

View file

@ -1,56 +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 type { DataViewSpec } from '@kbn/data-views-plugin/common';
import { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common';
import { Filter, FilterStateStore } from '@kbn/es-query';
type DiscoverPropertiesToPick = 'dataViewId' | 'dataViewSpec' | 'filters';
type DiscoverNavigationParams = Pick<
DiscoverAppLocatorParams,
DiscoverPropertiesToPick
>;
const defaultFilterKey = 'data_stream.dataset';
const defaultLogsDataViewId = 'logs-*';
const defaultLogsDataView: DataViewSpec = {
id: defaultLogsDataViewId,
title: defaultLogsDataViewId,
};
const getDefaultDatasetFilter = (datasets: string[]): Filter[] => [
{
meta: {
index: defaultLogsDataViewId,
key: defaultFilterKey,
params: datasets,
type: 'phrases',
},
query: {
bool: {
minimum_should_match: 1,
should: datasets.map((dataset) => ({
match_phrase: {
[defaultFilterKey]: dataset,
},
})),
},
},
$state: {
store: FilterStateStore.APP_STATE,
},
},
];
export const getDiscoverNavigationParams = (
datasets: string[]
): DiscoverNavigationParams => ({
dataViewId: defaultLogsDataViewId,
dataViewSpec: defaultLogsDataView,
filters: getDefaultDatasetFilter(datasets),
});

View file

@ -6,10 +6,10 @@
*/
import type { LocatorDefinition } from '@kbn/share-plugin/public';
import type { ObservabilityOnboardingLocatorParams } from './types';
export const OBSERVABILITY_ONBOARDING_LOCATOR =
'OBSERVABILITY_ONBOARDING_LOCATOR' as const;
import {
ObservabilityOnboardingLocatorParams,
OBSERVABILITY_ONBOARDING_LOCATOR,
} from '@kbn/deeplinks-observability/locators';
export class ObservabilityOnboardingLocatorDefinition
implements LocatorDefinition<ObservabilityOnboardingLocatorParams>

View file

@ -6,13 +6,7 @@
*/
import type { LocatorPublic } from '@kbn/share-plugin/public';
import { SerializableRecord } from '@kbn/utility-types';
export interface ObservabilityOnboardingLocatorParams
extends SerializableRecord {
/** If given, it will load the given map else will load the create a new map page. */
source?: 'customLogs' | 'systemLogs';
}
import type { ObservabilityOnboardingLocatorParams } from '@kbn/deeplinks-observability/locators';
export type ObservabilityOnboardingLocator =
LocatorPublic<ObservabilityOnboardingLocatorParams>;

View file

@ -23,7 +23,6 @@ import {
DataPublicPluginSetup,
DataPublicPluginStart,
} from '@kbn/data-plugin/public';
import type { DiscoverSetup } from '@kbn/discover-plugin/public';
import { SharePluginSetup } from '@kbn/share-plugin/public';
import type { ObservabilityOnboardingConfig } from '../server';
import { PLUGIN_ID } from '../common';
@ -35,7 +34,6 @@ export type ObservabilityOnboardingPluginStart = void;
export interface ObservabilityOnboardingPluginSetupDeps {
data: DataPublicPluginSetup;
discover: DiscoverSetup;
observability: ObservabilityPublicSetup;
share: SharePluginSetup;
}

View file

@ -14,7 +14,6 @@
"kbn_references": [
"@kbn/core",
"@kbn/data-plugin",
"@kbn/discover-plugin",
"@kbn/kibana-react-plugin",
"@kbn/observability-plugin",
"@kbn/i18n",
@ -30,12 +29,10 @@
"@kbn/core-http-server",
"@kbn/security-plugin",
"@kbn/std",
"@kbn/data-views-plugin",
"@kbn/es-query",
"@kbn/use-tracked-promise",
"@kbn/custom-integrations",
"@kbn/share-plugin",
"@kbn/utility-types",
"@kbn/deeplinks-observability"
],
"exclude": [
"target/**/*",