mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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 Serverless7161364e
-333f-4ac4-87d5-7f1ffec699b3 - APM Stateful058c9587
-b766-4d4f-a73d-50fd381be4bb - Onboarding Serverlessee1cab42
-f91c-4558-aa5f-4fa7e8963427 - Onboarding Statefula376a12b
-499b-4488-a75a-d06e81f8e21d - Observability Landing Pagec1c084ca
-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:
parent
f6084a5d1b
commit
3d58a1da64
79 changed files with 1549 additions and 411 deletions
|
@ -9,3 +9,5 @@
|
|||
export { OBSERVABILITY_ONBOARDING_APP_ID } from './constants';
|
||||
|
||||
export type { AppId, DeepLinkId } from './deep_links';
|
||||
|
||||
export * from './locators';
|
||||
|
|
11
packages/deeplinks/observability/locators/index.ts
Normal file
11
packages/deeplinks/observability/locators/index.ts
Normal 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';
|
51
packages/deeplinks/observability/locators/log_explorer.ts
Normal file
51
packages/deeplinks/observability/locators/log_explorer.ts
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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';
|
||||
}
|
|
@ -16,5 +16,7 @@
|
|||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/utility-types",
|
||||
"@kbn/es-query",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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[] = [
|
||||
{
|
||||
|
|
|
@ -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();
|
|
@ -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',
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -141,6 +141,7 @@ export const mockApmPluginContextValue = {
|
|||
locators: infraLocatorsMock,
|
||||
},
|
||||
deps: {},
|
||||
share: sharePluginMock.createSetupContract(),
|
||||
unifiedSearch: mockUnifiedSearch,
|
||||
uiActions: {
|
||||
getTriggerCompatibleActions: () => Promise.resolve([]),
|
||||
|
|
|
@ -101,7 +101,8 @@
|
|||
"@kbn/profiling-utils",
|
||||
"@kbn/core-analytics-server",
|
||||
"@kbn/analytics-client",
|
||||
"@kbn/monaco"
|
||||
"@kbn/monaco",
|
||||
"@kbn/deeplinks-observability"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Dataset } from '../../../common/datasets';
|
||||
import { Dataset } from '../datasets';
|
||||
import { encodeDatasetSelection } from './encoding';
|
||||
import { DatasetSelectionStrategy } from './types';
|
||||
|
|
@ -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';
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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';
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Dataset } from '../../../common/datasets';
|
||||
import { Dataset } from '../datasets';
|
||||
import { encodeDatasetSelection } from './encoding';
|
||||
import { DatasetSelectionStrategy, SingleDatasetSelectionPayload } from './types';
|
||||
|
|
@ -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 {
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
|
|
8
x-pack/plugins/log_explorer/common/index.ts
Normal file
8
x-pack/plugins/log_explorer/common/index.ts
Normal 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';
|
14
x-pack/plugins/log_explorer/common/locators/index.ts
Normal file
14
x-pack/plugins/log_explorer/common/locators/index.ts
Normal 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;
|
||||
}
|
|
@ -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';
|
|
@ -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-*-*',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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,
|
||||
})!;
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -19,9 +19,13 @@
|
|||
"kibanaReact",
|
||||
"kibanaUtils",
|
||||
"controls",
|
||||
"embeddable"
|
||||
"embeddable",
|
||||
"share",
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": []
|
||||
"requiredBundles": [],
|
||||
"extraPublicDirs": [
|
||||
"common",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { HttpStart } from '@kbn/core/public';
|
||||
|
||||
import { Dataset, Integration } from '../../../common/datasets';
|
||||
import {
|
||||
DATASETS_URL,
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
};
|
|
@ -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 }),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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/**/*"]
|
||||
}
|
||||
|
|
|
@ -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 <></>;
|
||||
}
|
||||
|
|
|
@ -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/**/*"
|
||||
|
|
|
@ -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';
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
}
|
|
@ -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';
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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';
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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: {},
|
||||
};
|
||||
};
|
|
@ -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';
|
|
@ -16,11 +16,15 @@
|
|||
"discover",
|
||||
"logExplorer",
|
||||
"observabilityShared",
|
||||
"share"
|
||||
"share",
|
||||
"kibanaUtils",
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"serverless"
|
||||
],
|
||||
"requiredBundles": ["kibanaReact", "observabilityOnboarding"]
|
||||
"requiredBundles": ["kibanaReact"],
|
||||
"extraPublicDirs": [
|
||||
"common",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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/**/*"
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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),
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue