From b3a1af8c6b4211b4231c9e07318c545459b9825a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Tue, 20 Aug 2024 16:50:54 +0000
Subject: [PATCH 01/74] Add rare logs scenario
---
.../src/lib/entity.ts | 12 ++
.../src/lib/infra/host.ts | 10 +-
.../src/lib/infra/index.ts | 3 +-
.../src/lib/interval.ts | 18 +-
.../src/lib/logs/index.ts | 21 +++
.../src/lib/random_events.test.ts | 52 ++++++
.../src/lib/random_events.ts | 76 ++++++++
.../src/lib/timerange.ts | 22 ++-
.../helpers/unstructured_logs.test.ts | 17 ++
.../scenarios/helpers/unstructured_logs.ts | 85 +++++++++
.../src/scenarios/rare_logs.ts | 162 ++++++++++++++++++
11 files changed, 472 insertions(+), 6 deletions(-)
create mode 100644 packages/kbn-apm-synthtrace-client/src/lib/random_events.test.ts
create mode 100644 packages/kbn-apm-synthtrace-client/src/lib/random_events.ts
create mode 100644 packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.test.ts
create mode 100644 packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts
create mode 100644 packages/kbn-apm-synthtrace/src/scenarios/rare_logs.ts
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entity.ts
index 9d872a6822462..1120c222c60af 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/entity.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/entity.ts
@@ -6,6 +6,8 @@
* Side Public License, v 1.
*/
+export type ObjectEntry = [keyof T, T[keyof T]];
+
export type Fields | undefined = undefined> = {
'@timestamp'?: number;
} & (TMeta extends undefined ? {} : Partial<{ meta: TMeta }>);
@@ -26,4 +28,14 @@ export class Entity {
return this;
}
+
+ overrides(overrides: Partial) {
+ const overrideEntries = Object.entries(overrides) as Array>;
+
+ overrideEntries.forEach(([fieldName, value]) => {
+ this.fields[fieldName] = value;
+ });
+
+ return this;
+ }
}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts
index a5ca11ad20203..d479b8889de9e 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/host.ts
@@ -23,7 +23,7 @@ interface HostDocument extends Fields {
'cloud.provider'?: string;
}
-class Host extends Entity {
+export class Host extends Entity {
cpu() {
return new HostMetrics({
...this.fields,
@@ -136,3 +136,11 @@ export function host(name: string): Host {
'cloud.provider': 'gcp',
});
}
+
+export function minimalHost(name: string): Host {
+ return new Host({
+ 'agent.id': 'synthtrace',
+ 'host.hostname': name,
+ 'host.name': name,
+ });
+}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts
index c325635e27098..3b415d6324a43 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/infra/index.ts
@@ -7,7 +7,7 @@
*/
import { dockerContainer, DockerContainerMetricsDocument } from './docker_container';
-import { host, HostMetricsDocument } from './host';
+import { host, HostMetricsDocument, minimalHost } from './host';
import { k8sContainer, K8sContainerMetricsDocument } from './k8s_container';
import { pod, PodMetricsDocument } from './pod';
import { awsRds, AWSRdsMetricsDocument } from './aws/rds';
@@ -21,6 +21,7 @@ export type InfraDocument =
export const infra = {
host,
+ minimalHost,
pod,
dockerContainer,
k8sContainer,
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/interval.ts b/packages/kbn-apm-synthtrace-client/src/lib/interval.ts
index bb77e6f6ee350..6f08a9038c746 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/interval.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/interval.ts
@@ -32,6 +32,10 @@ interface IntervalOptions {
rate?: number;
}
+interface StepDetails {
+ stepMilliseconds: number;
+}
+
export class Interval {
private readonly intervalAmount: number;
private readonly intervalUnit: unitOfTime.DurationConstructor;
@@ -44,12 +48,16 @@ export class Interval {
this._rate = options.rate || 1;
}
+ private getIntervalMilliseconds(): number {
+ return moment.duration(this.intervalAmount, this.intervalUnit).asMilliseconds();
+ }
+
private getTimestamps() {
const from = this.options.from.getTime();
const to = this.options.to.getTime();
let time: number = from;
- const diff = moment.duration(this.intervalAmount, this.intervalUnit).asMilliseconds();
+ const diff = this.getIntervalMilliseconds();
const timestamps: number[] = [];
@@ -66,15 +74,19 @@ export class Interval {
*generator(
map: (
timestamp: number,
- index: number
+ index: number,
+ stepDetails: StepDetails
) => Serializable | Array>
): SynthtraceGenerator {
const timestamps = this.getTimestamps();
+ const stepDetails: StepDetails = {
+ stepMilliseconds: this.getIntervalMilliseconds(),
+ };
let index = 0;
for (const timestamp of timestamps) {
- const events = castArray(map(timestamp, index));
+ const events = castArray(map(timestamp, index, stepDetails));
index++;
for (const event of events) {
yield event;
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts
index a649189de47a5..3fc09e4203dba 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/logs/index.ts
@@ -63,6 +63,7 @@ export type LogDocument = Fields &
'event.duration': number;
'event.start': Date;
'event.end': Date;
+ labels?: Record;
}>;
class Log extends Serializable {
@@ -139,6 +140,26 @@ function create(logsOptions: LogsOptions = defaultLogsOptions): Log {
).dataset('synth');
}
+function createMinimal({
+ dataset = 'synth',
+ namespace = 'default',
+}: {
+ dataset?: string;
+ namespace?: string;
+} = {}): Log {
+ return new Log(
+ {
+ 'input.type': 'logs',
+ 'data_stream.namespace': namespace,
+ 'data_stream.type': 'logs',
+ 'data_stream.dataset': dataset,
+ 'event.dataset': dataset,
+ },
+ { isLogsDb: false }
+ );
+}
+
export const log = {
create,
+ createMinimal,
};
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/random_events.test.ts b/packages/kbn-apm-synthtrace-client/src/lib/random_events.test.ts
new file mode 100644
index 0000000000000..27e9e673300fc
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/random_events.test.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { PoissonEvents } from './random_events';
+import { Serializable } from './serializable';
+
+describe('poisson events', () => {
+ it('generates events within the given time range', () => {
+ const poissonEvents = new PoissonEvents(new Date(1000), new Date(2000), 10);
+
+ const events = Array.from(
+ poissonEvents.generator((timestamp) => new Serializable({ '@timestamp': timestamp }))
+ );
+
+ expect(events.length).toBeGreaterThanOrEqual(1);
+
+ for (const event of events) {
+ expect(event.fields['@timestamp']).toBeGreaterThanOrEqual(1000);
+ expect(event.fields['@timestamp']).toBeLessThanOrEqual(2000);
+ }
+ });
+
+ it('generates at least one event if the rate is greater than 0', () => {
+ const poissonEvents = new PoissonEvents(new Date(1000), new Date(2000), 1);
+
+ const events = Array.from(
+ poissonEvents.generator((timestamp) => new Serializable({ '@timestamp': timestamp }))
+ );
+
+ expect(events.length).toBeGreaterThanOrEqual(1);
+
+ for (const event of events) {
+ expect(event.fields['@timestamp']).toBeGreaterThanOrEqual(1000);
+ expect(event.fields['@timestamp']).toBeLessThanOrEqual(2000);
+ }
+ });
+
+ it('generates no event if the rate is 0', () => {
+ const poissonEvents = new PoissonEvents(new Date(1000), new Date(2000), 0);
+
+ const events = Array.from(
+ poissonEvents.generator((timestamp) => new Serializable({ '@timestamp': timestamp }))
+ );
+
+ expect(events.length).toBe(0);
+ });
+});
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/random_events.ts b/packages/kbn-apm-synthtrace-client/src/lib/random_events.ts
new file mode 100644
index 0000000000000..05d8d8793d1b8
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/random_events.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 { castArray } from 'lodash';
+import { SynthtraceGenerator } from '../types';
+import { Fields } from './entity';
+import { Serializable } from './serializable';
+
+export class PoissonEvents {
+ constructor(
+ private readonly from: Date,
+ private readonly to: Date,
+ private readonly rate: number
+ ) {}
+
+ private getTotalTimePeriod(): number {
+ return this.to.getTime() - this.from.getTime();
+ }
+
+ private getInterarrivalTime(): number {
+ const distribution = -Math.log(1 - Math.random()) / this.rate;
+ const totalTimePeriod = this.getTotalTimePeriod();
+ return Math.floor(distribution * totalTimePeriod);
+ }
+
+ *generator(
+ map: (
+ timestamp: number,
+ index: number
+ ) => Serializable | Array>
+ ): SynthtraceGenerator {
+ if (this.rate <= 0) {
+ return;
+ }
+
+ let currentTime = this.from.getTime();
+ const endTime = this.to.getTime();
+ let eventIndex = 0;
+
+ while (currentTime < endTime) {
+ const interarrivalTime = this.getInterarrivalTime();
+ currentTime += interarrivalTime;
+
+ if (currentTime < endTime) {
+ yield* this.generateEvents(currentTime, eventIndex, map);
+ eventIndex++;
+ }
+ }
+
+ // ensure at least one event has been emitted
+ if (this.rate > 0 && eventIndex === 0) {
+ const forcedEventTime =
+ this.from.getTime() + Math.floor(Math.random() * this.getTotalTimePeriod());
+ yield* this.generateEvents(forcedEventTime, eventIndex, map);
+ }
+ }
+
+ private *generateEvents(
+ timestamp: number,
+ eventIndex: number,
+ map: (
+ timestamp: number,
+ index: number
+ ) => Serializable | Array>
+ ): Generator> {
+ const events = castArray(map(timestamp, eventIndex));
+ for (const event of events) {
+ yield event;
+ }
+ }
+}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts b/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts
index 0817ea3d0e34f..9e9e165ca5194 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts
@@ -8,9 +8,10 @@
import datemath from '@kbn/datemath';
import type { Moment } from 'moment';
import { Interval } from './interval';
+import { PoissonEvents } from './random_events';
export class Timerange {
- constructor(private from: Date, private to: Date) {}
+ constructor(public readonly from: Date, public readonly to: Date) {}
interval(interval: string) {
return new Interval({ from: this.from, to: this.to, interval });
@@ -19,6 +20,25 @@ export class Timerange {
ratePerMinute(rate: number) {
return this.interval(`1m`).rate(rate);
}
+
+ poissonEvents(rate: number) {
+ return new PoissonEvents(this.from, this.to, rate);
+ }
+
+ splitInto(segmentCount: number): Timerange[] {
+ const duration = this.to.getTime() - this.from.getTime();
+ const segmentDuration = duration / segmentCount;
+
+ return Array.from({ length: segmentCount }, (_, i) => {
+ const from = new Date(this.from.getTime() + i * segmentDuration);
+ const to = new Date(from.getTime() + segmentDuration);
+ return new Timerange(from, to);
+ });
+ }
+
+ toString() {
+ return `Timerange(from=${this.from.toISOString()}, to=${this.to.toISOString()})`;
+ }
}
type DateLike = Date | number | Moment | string;
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.test.ts b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.test.ts
new file mode 100644
index 0000000000000..2fdf52254540d
--- /dev/null
+++ b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.test.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { generateUnstructuredLogMessage } from './unstructured_logs';
+
+test('generates unstructured logs', () => {
+ const messages = Array(10)
+ .fill('')
+ .map(() => generateUnstructuredLogMessage()());
+
+ console.log(messages);
+});
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts
new file mode 100644
index 0000000000000..2f128d1352472
--- /dev/null
+++ b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts
@@ -0,0 +1,85 @@
+/*
+ * 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 { Faker, faker } from '@faker-js/faker';
+
+export type LogMessageGenerator = (f: Faker) => string[];
+
+/**
+ * Ensures the type safety of the log message generators.
+ */
+const ensureGeneratorType = >(generators: T): T => {
+ return generators;
+};
+
+export const unstructuredLogMessageGenerators = ensureGeneratorType({
+ httpAccess: (f: Faker) => [
+ `${f.internet.ip()} - - [${f.date
+ .past()
+ .toISOString()
+ .replace('T', ' ')
+ .replace(
+ /\..+/,
+ ''
+ )}] "${f.internet.httpMethod()} ${f.internet.url()} HTTP/1.1" ${f.helpers.arrayElement([
+ 200, 301, 404, 500,
+ ])} ${f.number.int({ min: 100, max: 5000 })}`,
+ ],
+ dbOperation: (f: Faker) => [
+ `${f.database.engine()}: ${f.database.column()} ${f.helpers.arrayElement([
+ 'created',
+ 'updated',
+ 'deleted',
+ 'inserted',
+ ])} successfully ${f.number.int({ max: 100000 })} times`,
+ ],
+ taskStatusSuccess: (f: Faker) => [
+ `${f.hacker.noun()}: ${f.word.words()} ${f.helpers.arrayElement([
+ 'triggered',
+ 'executed',
+ 'processed',
+ 'handled',
+ ])} successfully at ${f.date.recent().toISOString()}`,
+ ],
+ taskStatusFailure: (f: Faker) => [
+ `${f.hacker.noun()}: ${f.helpers.arrayElement([
+ 'triggering',
+ 'execution',
+ 'processing',
+ 'handling',
+ ])} of ${f.word.words()} failed at ${f.date.recent().toISOString()}`,
+ ],
+ error: (f: Faker) => [
+ `${f.helpers.arrayElement([
+ 'Error',
+ 'Exception',
+ 'Failure',
+ 'Crash',
+ 'Bug',
+ 'Issue',
+ ])}: ${f.hacker.phrase()}`,
+ `Stopping ${f.number.int(42)} background tasks...`,
+ 'Shutting down process...',
+ ],
+ restart: (f: Faker) => {
+ const service = f.database.engine();
+ return [
+ `Restarting ${service}...`,
+ `Waiting for queue to drain...`,
+ `Service ${service} restarted ${f.helpers.arrayElement([
+ 'successfully',
+ 'with errors',
+ 'with warnings',
+ ])}`,
+ ];
+ },
+});
+
+export const generateUnstructuredLogMessage =
+ (generators: LogMessageGenerator[] = Object.values(unstructuredLogMessageGenerators)) =>
+ (f: Faker = faker) =>
+ f.helpers.arrayElement(generators)(f);
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/rare_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/rare_logs.ts
new file mode 100644
index 0000000000000..89374e0f9a9cf
--- /dev/null
+++ b/packages/kbn-apm-synthtrace/src/scenarios/rare_logs.ts
@@ -0,0 +1,162 @@
+/*
+ * 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 { infra, LogDocument, log } from '@kbn/apm-synthtrace-client';
+import { fakerEN as faker } from '@faker-js/faker';
+import { z } from 'zod';
+import { Scenario } from '../cli/scenario';
+import { withClient } from '../lib/utils/with_client';
+import {
+ generateUnstructuredLogMessage,
+ unstructuredLogMessageGenerators,
+} from './helpers/unstructured_logs';
+
+const scenarioOptsSchema = z.object({
+ randomSeed: z.number().default(0),
+ distribution: z.enum(['uniform', 'poisson']).default('uniform'),
+ rate: z.number().default(1),
+ messageGroup: z
+ .enum(['healthyOperations', 'degradedOperations', 'errorOperations'])
+ .default('healthyOperations'),
+});
+
+const scenario: Scenario = async (runOptions) => {
+ return {
+ generate: ({ range, clients: { logsEsClient } }) => {
+ const { logger } = runOptions;
+ const scenarioOpts = scenarioOptsSchema.parse(runOptions.scenarioOpts ?? {});
+
+ faker.seed(scenarioOpts.randomSeed);
+ faker.setDefaultRefDate(range.from.toISOString());
+
+ logger.debug(
+ `Generating ${scenarioOpts.distribution} logs with a rate of ${scenarioOpts.rate}...`
+ );
+
+ // Logs Data logic
+ const LOG_LEVELS = ['info', 'debug', 'error', 'warn', 'trace', 'fatal'];
+
+ const clusterDefinions = [
+ {
+ 'orchestrator.cluster.id': faker.string.nanoid(),
+ 'orchestrator.cluster.name': 'synth-cluster-1',
+ 'orchestrator.namespace': 'default',
+ 'cloud.provider': 'gcp',
+ 'cloud.region': 'eu-central-1',
+ 'cloud.availability_zone': 'eu-central-1a',
+ 'cloud.project.id': faker.string.nanoid(),
+ },
+ {
+ 'orchestrator.cluster.id': faker.string.nanoid(),
+ 'orchestrator.cluster.name': 'synth-cluster-2',
+ 'orchestrator.namespace': 'production',
+ 'cloud.provider': 'aws',
+ 'cloud.region': 'us-east-1',
+ 'cloud.availability_zone': 'us-east-1a',
+ 'cloud.project.id': faker.string.nanoid(),
+ },
+ {
+ 'orchestrator.cluster.id': faker.string.nanoid(),
+ 'orchestrator.cluster.name': 'synth-cluster-3',
+ 'orchestrator.namespace': 'kube',
+ 'cloud.provider': 'azure',
+ 'cloud.region': 'area-51',
+ 'cloud.availability_zone': 'area-51a',
+ 'cloud.project.id': faker.string.nanoid(),
+ },
+ ];
+
+ const hostEntities = [
+ {
+ 'host.name': 'host-1',
+ 'agent.id': 'synth-agent-1',
+ 'agent.name': 'nodejs',
+ 'cloud.instance.id': faker.string.nanoid(),
+ 'orchestrator.resource.id': faker.string.nanoid(),
+ ...clusterDefinions[0],
+ },
+ {
+ 'host.name': 'host-2',
+ 'agent.id': 'synth-agent-2',
+ 'agent.name': 'custom',
+ 'cloud.instance.id': faker.string.nanoid(),
+ 'orchestrator.resource.id': faker.string.nanoid(),
+ ...clusterDefinions[1],
+ },
+ {
+ 'host.name': 'host-3',
+ 'agent.id': 'synth-agent-3',
+ 'agent.name': 'python',
+ 'cloud.instance.id': faker.string.nanoid(),
+ 'orchestrator.resource.id': faker.string.nanoid(),
+ ...clusterDefinions[2],
+ },
+ ].map((hostDefinition) =>
+ infra.minimalHost(hostDefinition['host.name']).overrides(hostDefinition)
+ );
+
+ const serviceNames = Array(3)
+ .fill(null)
+ .map((_, idx) => `synth-service-${idx}`);
+
+ const generatorFactory =
+ scenarioOpts.distribution === 'uniform'
+ ? range.interval('1s').rate(scenarioOpts.rate)
+ : range.poissonEvents(scenarioOpts.rate);
+
+ const logs = generatorFactory.generator((timestamp) => {
+ const entity = faker.helpers.arrayElement(hostEntities);
+ const serviceName = faker.helpers.arrayElement(serviceNames);
+ const level = faker.helpers.arrayElement(LOG_LEVELS);
+ const messages = logMessageGenerators[scenarioOpts.messageGroup](faker);
+
+ return messages.map((message) =>
+ log
+ .createMinimal()
+ .message(message)
+ .logLevel(level)
+ .service(serviceName)
+ .overrides({
+ ...entity.fields,
+ labels: {
+ scenario: 'rare',
+ population: scenarioOpts.distribution,
+ },
+ })
+ .timestamp(timestamp)
+ );
+ });
+
+ return [
+ withClient(
+ logsEsClient,
+ logger.perf('generating_logs', () => [logs])
+ ),
+ ];
+ },
+ };
+};
+
+export default scenario;
+
+const healthyLogMessageGenerators = [
+ unstructuredLogMessageGenerators.dbOperation,
+ unstructuredLogMessageGenerators.taskStatusSuccess,
+];
+
+const degradedLogMessageGenerators = [unstructuredLogMessageGenerators.taskStatusFailure];
+
+const errorLogMessageGenerators = [
+ unstructuredLogMessageGenerators.error,
+ unstructuredLogMessageGenerators.restart,
+];
+
+const logMessageGenerators = {
+ healthyOperations: generateUnstructuredLogMessage(healthyLogMessageGenerators),
+ degradedOperations: generateUnstructuredLogMessage(degradedLogMessageGenerators),
+ errorOperations: generateUnstructuredLogMessage(errorLogMessageGenerators),
+};
From 0a4316992842d3d4ad3eab895eb3c6ea7b8d3068 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Wed, 21 Aug 2024 15:15:39 +0000
Subject: [PATCH 02/74] Add empty logs_overview package
---
.github/CODEOWNERS | 1 +
package.json | 1 +
tsconfig.base.json | 2 ++
.../observability/logs_overview/README.md | 3 +++
.../observability/logs_overview/index.ts | 10 ++++++++++
.../logs_overview/jest.config.js | 12 ++++++++++++
.../observability/logs_overview/kibana.jsonc | 5 +++++
.../observability/logs_overview/package.json | 7 +++++++
.../observability/logs_overview/tsconfig.json | 19 +++++++++++++++++++
yarn.lock | 4 ++++
10 files changed, 64 insertions(+)
create mode 100644 x-pack/packages/observability/logs_overview/README.md
create mode 100644 x-pack/packages/observability/logs_overview/index.ts
create mode 100644 x-pack/packages/observability/logs_overview/jest.config.js
create mode 100644 x-pack/packages/observability/logs_overview/kibana.jsonc
create mode 100644 x-pack/packages/observability/logs_overview/package.json
create mode 100644 x-pack/packages/observability/logs_overview/tsconfig.json
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c3cab77ee6951..3f05369289d40 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -630,6 +630,7 @@ x-pack/packages/observability/alerting_test_data @elastic/obs-ux-management-team
x-pack/test/cases_api_integration/common/plugins/observability @elastic/response-ops
x-pack/packages/observability/get_padded_alert_time_range_util @elastic/obs-ux-management-team
x-pack/plugins/observability_solution/observability_logs_explorer @elastic/obs-ux-logs-team
+x-pack/packages/observability/logs_overview @elastic/obs-ux-logs-team
x-pack/plugins/observability_solution/observability_onboarding/e2e @elastic/obs-ux-logs-team @elastic/obs-ux-onboarding-team
x-pack/plugins/observability_solution/observability_onboarding @elastic/obs-ux-logs-team @elastic/obs-ux-onboarding-team
x-pack/plugins/observability_solution/observability @elastic/obs-ux-management-team
diff --git a/package.json b/package.json
index 3a0c99e9940be..a0d84f47fe7e1 100644
--- a/package.json
+++ b/package.json
@@ -663,6 +663,7 @@
"@kbn/observability-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/observability",
"@kbn/observability-get-padded-alert-time-range-util": "link:x-pack/packages/observability/get_padded_alert_time_range_util",
"@kbn/observability-logs-explorer-plugin": "link:x-pack/plugins/observability_solution/observability_logs_explorer",
+ "@kbn/observability-logs-overview": "link:x-pack/packages/observability/logs_overview",
"@kbn/observability-onboarding-plugin": "link:x-pack/plugins/observability_solution/observability_onboarding",
"@kbn/observability-plugin": "link:x-pack/plugins/observability_solution/observability",
"@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_solution/observability_shared",
diff --git a/tsconfig.base.json b/tsconfig.base.json
index d063f6a880441..de3ac356b7145 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -1254,6 +1254,8 @@
"@kbn/observability-get-padded-alert-time-range-util/*": ["x-pack/packages/observability/get_padded_alert_time_range_util/*"],
"@kbn/observability-logs-explorer-plugin": ["x-pack/plugins/observability_solution/observability_logs_explorer"],
"@kbn/observability-logs-explorer-plugin/*": ["x-pack/plugins/observability_solution/observability_logs_explorer/*"],
+ "@kbn/observability-logs-overview": ["x-pack/packages/observability/logs_overview"],
+ "@kbn/observability-logs-overview/*": ["x-pack/packages/observability/logs_overview/*"],
"@kbn/observability-onboarding-e2e": ["x-pack/plugins/observability_solution/observability_onboarding/e2e"],
"@kbn/observability-onboarding-e2e/*": ["x-pack/plugins/observability_solution/observability_onboarding/e2e/*"],
"@kbn/observability-onboarding-plugin": ["x-pack/plugins/observability_solution/observability_onboarding"],
diff --git a/x-pack/packages/observability/logs_overview/README.md b/x-pack/packages/observability/logs_overview/README.md
new file mode 100644
index 0000000000000..20d3f0f02b7df
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/README.md
@@ -0,0 +1,3 @@
+# @kbn/observability-logs-overview
+
+Empty package generated by @kbn/generate
diff --git a/x-pack/packages/observability/logs_overview/index.ts b/x-pack/packages/observability/logs_overview/index.ts
new file mode 100644
index 0000000000000..7210d7181a9ed
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export function foo() {
+ return 'hello world';
+}
diff --git a/x-pack/packages/observability/logs_overview/jest.config.js b/x-pack/packages/observability/logs_overview/jest.config.js
new file mode 100644
index 0000000000000..2ee88ee990253
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/jest.config.js
@@ -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.
+ */
+
+module.exports = {
+ preset: '@kbn/test',
+ rootDir: '../../../..',
+ roots: ['/x-pack/packages/observability/logs_overview'],
+};
diff --git a/x-pack/packages/observability/logs_overview/kibana.jsonc b/x-pack/packages/observability/logs_overview/kibana.jsonc
new file mode 100644
index 0000000000000..90b3375086720
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/kibana.jsonc
@@ -0,0 +1,5 @@
+{
+ "type": "shared-browser",
+ "id": "@kbn/observability-logs-overview",
+ "owner": "@elastic/obs-ux-logs-team"
+}
diff --git a/x-pack/packages/observability/logs_overview/package.json b/x-pack/packages/observability/logs_overview/package.json
new file mode 100644
index 0000000000000..77a529e7e59f7
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "@kbn/observability-logs-overview",
+ "private": true,
+ "version": "1.0.0",
+ "license": "Elastic License 2.0",
+ "sideEffects": false
+}
diff --git a/x-pack/packages/observability/logs_overview/tsconfig.json b/x-pack/packages/observability/logs_overview/tsconfig.json
new file mode 100644
index 0000000000000..5c989599ec9ad
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "target/types",
+ "types": [
+ "jest",
+ "node",
+ "react"
+ ]
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ ],
+ "exclude": [
+ "target/**/*"
+ ],
+ "kbn_references": []
+}
diff --git a/yarn.lock b/yarn.lock
index c5701f90e7584..9135da3b6f6fb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5799,6 +5799,10 @@
version "0.0.0"
uid ""
+"@kbn/observability-logs-overview@link:x-pack/packages/observability/logs_overview":
+ version "0.0.0"
+ uid ""
+
"@kbn/observability-onboarding-e2e@link:x-pack/plugins/observability_solution/observability_onboarding/e2e":
version "0.0.0"
uid ""
From d373e59e55df1bc2959b5ad017ae3356bbea979c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 2 Sep 2024 15:57:53 +0000
Subject: [PATCH 03/74] Add gaussian distribution to the unstructured logs
scenario
---
.../src/lib/gaussian_events.ts | 73 +++++++++++++++
..._events.test.ts => poisson_events.test.ts} | 2 +-
.../{random_events.ts => poisson_events.ts} | 0
.../src/lib/timerange.ts | 7 +-
...gs.ts => distributed_unstructured_logs.ts} | 89 +++++++++++++------
.../scenarios/helpers/unstructured_logs.ts | 25 ++++--
6 files changed, 157 insertions(+), 39 deletions(-)
create mode 100644 packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts
rename packages/kbn-apm-synthtrace-client/src/lib/{random_events.test.ts => poisson_events.test.ts} (97%)
rename packages/kbn-apm-synthtrace-client/src/lib/{random_events.ts => poisson_events.ts} (100%)
rename packages/kbn-apm-synthtrace/src/scenarios/{rare_logs.ts => distributed_unstructured_logs.ts} (66%)
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts b/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts
new file mode 100644
index 0000000000000..c13ebbaafe368
--- /dev/null
+++ b/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts
@@ -0,0 +1,73 @@
+/*
+ * 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 { castArray } from 'lodash';
+import { SynthtraceGenerator } from '../types';
+import { Fields } from './entity';
+import { Serializable } from './serializable';
+
+export class GaussianEvents {
+ constructor(
+ private readonly from: Date,
+ private readonly to: Date,
+ private readonly mean: Date,
+ private readonly width: number,
+ private readonly totalPoints: number
+ ) {}
+
+ *generator(
+ map: (
+ timestamp: number,
+ index: number
+ ) => Serializable | Array>
+ ): SynthtraceGenerator {
+ if (this.totalPoints <= 0) {
+ return;
+ }
+
+ const startTime = this.from.getTime();
+ const endTime = this.to.getTime();
+ const meanTime = this.mean.getTime();
+ const densityInterval = 1 / (this.totalPoints - 1);
+
+ for (let eventIndex = 0; eventIndex < this.totalPoints; eventIndex++) {
+ const quantile = eventIndex * densityInterval;
+
+ const standardScore = Math.sqrt(2) * inverseError(2 * quantile - 1);
+ const timestamp = Math.round(meanTime + standardScore * this.width);
+
+ if (timestamp >= startTime && timestamp <= endTime) {
+ yield* this.generateEvents(timestamp, eventIndex, map);
+ }
+ }
+ }
+
+ private *generateEvents(
+ timestamp: number,
+ eventIndex: number,
+ map: (
+ timestamp: number,
+ index: number
+ ) => Serializable | Array>
+ ): Generator> {
+ const events = castArray(map(timestamp, eventIndex));
+ for (const event of events) {
+ yield event;
+ }
+ }
+}
+
+function inverseError(x: number): number {
+ const a = 0.147;
+ const sign = x < 0 ? -1 : 1;
+
+ const part1 = 2 / (Math.PI * a) + Math.log(1 - x * x) / 2;
+ const part2 = Math.log(1 - x * x) / a;
+
+ return sign * Math.sqrt(Math.sqrt(part1 * part1 - part2) - part1);
+}
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/random_events.test.ts b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts
similarity index 97%
rename from packages/kbn-apm-synthtrace-client/src/lib/random_events.test.ts
rename to packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts
index 27e9e673300fc..580e89e2b2208 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/random_events.test.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import { PoissonEvents } from './random_events';
+import { PoissonEvents } from './poisson_events';
import { Serializable } from './serializable';
describe('poisson events', () => {
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/random_events.ts b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.ts
similarity index 100%
rename from packages/kbn-apm-synthtrace-client/src/lib/random_events.ts
rename to packages/kbn-apm-synthtrace-client/src/lib/poisson_events.ts
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts b/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts
index 9e9e165ca5194..12a58362eb861 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/timerange.ts
@@ -7,8 +7,9 @@
*/
import datemath from '@kbn/datemath';
import type { Moment } from 'moment';
+import { GaussianEvents } from './gaussian_events';
import { Interval } from './interval';
-import { PoissonEvents } from './random_events';
+import { PoissonEvents } from './poisson_events';
export class Timerange {
constructor(public readonly from: Date, public readonly to: Date) {}
@@ -25,6 +26,10 @@ export class Timerange {
return new PoissonEvents(this.from, this.to, rate);
}
+ gaussianEvents(mean: Date, width: number, totalPoints: number) {
+ return new GaussianEvents(this.from, this.to, mean, width, totalPoints);
+ }
+
splitInto(segmentCount: number): Timerange[] {
const duration = this.to.getTime() - this.from.getTime();
const segmentDuration = duration / segmentCount;
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/rare_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts
similarity index 66%
rename from packages/kbn-apm-synthtrace/src/scenarios/rare_logs.ts
rename to packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts
index 89374e0f9a9cf..00b6340e4257a 100644
--- a/packages/kbn-apm-synthtrace/src/scenarios/rare_logs.ts
+++ b/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts
@@ -11,18 +11,50 @@ import { z } from 'zod';
import { Scenario } from '../cli/scenario';
import { withClient } from '../lib/utils/with_client';
import {
+ LogMessageGenerator,
generateUnstructuredLogMessage,
unstructuredLogMessageGenerators,
} from './helpers/unstructured_logs';
-const scenarioOptsSchema = z.object({
- randomSeed: z.number().default(0),
- distribution: z.enum(['uniform', 'poisson']).default('uniform'),
- rate: z.number().default(1),
- messageGroup: z
- .enum(['healthyOperations', 'degradedOperations', 'errorOperations'])
- .default('healthyOperations'),
-});
+const scenarioOptsSchema = z.intersection(
+ z.object({
+ randomSeed: z.number().default(0),
+ messageGroup: z
+ .enum([
+ 'httpAccess',
+ 'userAuthentication',
+ 'networkEvent',
+ 'dbOperations',
+ 'taskOperations',
+ 'degradedOperations',
+ 'errorOperations',
+ ])
+ .default('dbOperations'),
+ }),
+ z
+ .discriminatedUnion('distribution', [
+ z.object({
+ distribution: z.literal('uniform'),
+ rate: z.number().default(1),
+ }),
+ z.object({
+ distribution: z.literal('poisson'),
+ rate: z.number().default(1),
+ }),
+ z.object({
+ distribution: z.literal('gaussian'),
+ mean: z.coerce.date().describe('Time of the peak of the gaussian distribution'),
+ width: z.number().default(5000).describe('Width of the gaussian distribution in ms'),
+ totalPoints: z
+ .number()
+ .default(100)
+ .describe('Total number of points in the gaussian distribution'),
+ }),
+ ])
+ .default({ distribution: 'uniform', rate: 1 })
+);
+
+type ScenarioOpts = z.output;
const scenario: Scenario = async (runOptions) => {
return {
@@ -33,9 +65,7 @@ const scenario: Scenario = async (runOptions) => {
faker.seed(scenarioOpts.randomSeed);
faker.setDefaultRefDate(range.from.toISOString());
- logger.debug(
- `Generating ${scenarioOpts.distribution} logs with a rate of ${scenarioOpts.rate}...`
- );
+ logger.debug(`Generating ${scenarioOpts.distribution} logs...`);
// Logs Data logic
const LOG_LEVELS = ['info', 'debug', 'error', 'warn', 'trace', 'fatal'];
@@ -106,7 +136,9 @@ const scenario: Scenario = async (runOptions) => {
const generatorFactory =
scenarioOpts.distribution === 'uniform'
? range.interval('1s').rate(scenarioOpts.rate)
- : range.poissonEvents(scenarioOpts.rate);
+ : scenarioOpts.distribution === 'poisson'
+ ? range.poissonEvents(scenarioOpts.rate)
+ : range.gaussianEvents(scenarioOpts.mean, scenarioOpts.width, scenarioOpts.totalPoints);
const logs = generatorFactory.generator((timestamp) => {
const entity = faker.helpers.arrayElement(hostEntities);
@@ -143,20 +175,21 @@ const scenario: Scenario = async (runOptions) => {
export default scenario;
-const healthyLogMessageGenerators = [
- unstructuredLogMessageGenerators.dbOperation,
- unstructuredLogMessageGenerators.taskStatusSuccess,
-];
-
-const degradedLogMessageGenerators = [unstructuredLogMessageGenerators.taskStatusFailure];
-
-const errorLogMessageGenerators = [
- unstructuredLogMessageGenerators.error,
- unstructuredLogMessageGenerators.restart,
-];
-
const logMessageGenerators = {
- healthyOperations: generateUnstructuredLogMessage(healthyLogMessageGenerators),
- degradedOperations: generateUnstructuredLogMessage(degradedLogMessageGenerators),
- errorOperations: generateUnstructuredLogMessage(errorLogMessageGenerators),
-};
+ httpAccess: generateUnstructuredLogMessage([unstructuredLogMessageGenerators.httpAccess]),
+ userAuthentication: generateUnstructuredLogMessage([
+ unstructuredLogMessageGenerators.userAuthentication,
+ ]),
+ networkEvent: generateUnstructuredLogMessage([unstructuredLogMessageGenerators.networkEvent]),
+ dbOperations: generateUnstructuredLogMessage([unstructuredLogMessageGenerators.dbOperation]),
+ taskOperations: generateUnstructuredLogMessage([
+ unstructuredLogMessageGenerators.taskStatusSuccess,
+ ]),
+ degradedOperations: generateUnstructuredLogMessage([
+ unstructuredLogMessageGenerators.taskStatusFailure,
+ ]),
+ errorOperations: generateUnstructuredLogMessage([
+ unstructuredLogMessageGenerators.error,
+ unstructuredLogMessageGenerators.restart,
+ ]),
+} satisfies Record;
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts
index 2f128d1352472..0cfc88ac55a83 100644
--- a/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts
+++ b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts
@@ -9,14 +9,7 @@ import { Faker, faker } from '@faker-js/faker';
export type LogMessageGenerator = (f: Faker) => string[];
-/**
- * Ensures the type safety of the log message generators.
- */
-const ensureGeneratorType = >(generators: T): T => {
- return generators;
-};
-
-export const unstructuredLogMessageGenerators = ensureGeneratorType({
+export const unstructuredLogMessageGenerators = {
httpAccess: (f: Faker) => [
`${f.internet.ip()} - - [${f.date
.past()
@@ -77,7 +70,21 @@ export const unstructuredLogMessageGenerators = ensureGeneratorType({
])}`,
];
},
-});
+ userAuthentication: (f: Faker) => [
+ `User ${f.internet.userName()} ${f.helpers.arrayElement([
+ 'logged in',
+ 'logged out',
+ 'failed to login',
+ ])}`,
+ ],
+ networkEvent: (f: Faker) => [
+ `Network ${f.helpers.arrayElement([
+ 'connection',
+ 'disconnection',
+ 'data transfer',
+ ])} ${f.helpers.arrayElement(['from', 'to'])} ${f.internet.ip()}`,
+ ],
+} satisfies Record;
export const generateUnstructuredLogMessage =
(generators: LogMessageGenerator[] = Object.values(unstructuredLogMessageGenerators)) =>
From 278365e908ce5e2657804a0f6385f1e31b56cc35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 2 Sep 2024 15:59:28 +0000
Subject: [PATCH 04/74] Add support for xstate5
---
package.json | 4 ++++
packages/kbn-optimizer/src/worker/webpack.config.ts | 12 ++++++++++++
yarn.lock | 13 +++++++++++++
3 files changed, 29 insertions(+)
diff --git a/package.json b/package.json
index a0d84f47fe7e1..aba2ce9b10784 100644
--- a/package.json
+++ b/package.json
@@ -94,6 +94,7 @@
"**/sharp": "0.32.6",
"**/typescript": "5.1.6",
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0",
+ "@xstate5/react/**/xstate": "^5.17.4",
"globby/fast-glob": "^3.2.11"
},
"dependencies": {
@@ -1006,6 +1007,7 @@
"@turf/helpers": "6.0.1",
"@turf/length": "^6.0.2",
"@xstate/react": "^3.2.2",
+ "@xstate5/react": "npm:@xstate/react@^4.1.1",
"adm-zip": "^0.5.9",
"ai": "^2.2.33",
"ajv": "^8.12.0",
@@ -1239,6 +1241,7 @@
"whatwg-fetch": "^3.0.0",
"xml2js": "^0.5.0",
"xstate": "^4.38.2",
+ "xstate5": "npm:xstate@^5.17.4",
"xterm": "^5.1.0",
"yauzl": "^2.10.0",
"yazl": "^2.5.1",
@@ -1260,6 +1263,7 @@
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-transform-class-properties": "^7.24.7",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.24.7",
"@babel/plugin-transform-numeric-separator": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-env": "^7.24.7",
diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts
index f81d0e89fb983..2e5fdbf3f7765 100644
--- a/packages/kbn-optimizer/src/worker/webpack.config.ts
+++ b/packages/kbn-optimizer/src/worker/webpack.config.ts
@@ -258,6 +258,18 @@ export function getWebpackConfig(
},
},
},
+ {
+ test: /node_modules\/@?xstate5\/.*\.js$/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ babelrc: false,
+ envName: worker.dist ? 'production' : 'development',
+ presets: [BABEL_PRESET],
+ plugins: ['@babel/plugin-transform-logical-assignment-operators'],
+ },
+ },
+ },
{
test: /\.(html|md|txt|tmpl)$/,
use: {
diff --git a/yarn.lock b/yarn.lock
index 9135da3b6f6fb..4448c71b0c522 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12000,6 +12000,14 @@
use-isomorphic-layout-effect "^1.1.2"
use-sync-external-store "^1.0.0"
+"@xstate5/react@npm:@xstate/react@^4.1.1":
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/@xstate/react/-/react-4.1.1.tgz#2f580fc5f83d195f95b56df6cd8061c66660d9fa"
+ integrity sha512-pFp/Y+bnczfaZ0V8B4LOhx3d6Gd71YKAPbzerGqydC2nsYN/mp7RZu3q/w6/kvI2hwR/jeDeetM7xc3JFZH2NA==
+ dependencies:
+ use-isomorphic-layout-effect "^1.1.2"
+ use-sync-external-store "^1.2.0"
+
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -32750,6 +32758,11 @@ xpath@^0.0.33:
resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.33.tgz#5136b6094227c5df92002e7c3a13516a5074eb07"
integrity sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==
+"xstate5@npm:xstate@^5.17.4", xstate@^5.17.4:
+ version "5.17.4"
+ resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.17.4.tgz#334ab2da123973634097f7ca48387ae1589c774e"
+ integrity sha512-KM2FYVOUJ04HlOO4TY3wEXqoYPR/XsDu+ewm+IWw0vilXqND0jVfvv04tEFwp8Mkk7I/oHXM8t1Ex9xJyUS4ZA==
+
xstate@^4.38.2:
version "4.38.2"
resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.2.tgz#1b74544fc9c8c6c713ba77f81c6017e65aa89804"
From 1f4bd0f2b3c3f02225232c439f4481fc27e09f20 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 2 Sep 2024 16:00:17 +0000
Subject: [PATCH 05/74] Add xstate5 console inspector
---
packages/kbn-xstate-utils/kibana.jsonc | 2 +-
.../kbn-xstate-utils/src/console_inspector.ts | 87 +++++++++++++++++++
packages/kbn-xstate-utils/src/index.ts | 1 +
3 files changed, 89 insertions(+), 1 deletion(-)
create mode 100644 packages/kbn-xstate-utils/src/console_inspector.ts
diff --git a/packages/kbn-xstate-utils/kibana.jsonc b/packages/kbn-xstate-utils/kibana.jsonc
index cd1151a3f2103..1fb3507854b98 100644
--- a/packages/kbn-xstate-utils/kibana.jsonc
+++ b/packages/kbn-xstate-utils/kibana.jsonc
@@ -1,5 +1,5 @@
{
- "type": "shared-common",
+ "type": "shared-browser",
"id": "@kbn/xstate-utils",
"owner": "@elastic/obs-ux-logs-team"
}
diff --git a/packages/kbn-xstate-utils/src/console_inspector.ts b/packages/kbn-xstate-utils/src/console_inspector.ts
new file mode 100644
index 0000000000000..b280fc90609c8
--- /dev/null
+++ b/packages/kbn-xstate-utils/src/console_inspector.ts
@@ -0,0 +1,87 @@
+/*
+ * 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 {
+ ActorRefLike,
+ AnyActorRef,
+ InspectedActorEvent,
+ InspectedEventEvent,
+ InspectedSnapshotEvent,
+ InspectionEvent,
+} from 'xstate5';
+import { isDevMode } from './dev_tools';
+
+export const createConsoleInspector = () => {
+ if (!isDevMode()) {
+ return () => {};
+ }
+
+ // eslint-disable-next-line no-console
+ const log = console.info.bind(console);
+
+ const logActorEvent = (actorEvent: InspectedActorEvent) => {
+ if (isActorRef(actorEvent.actorRef)) {
+ log(
+ '✨ %c%s%c is a new actor of type %c%s%c:',
+ ...styleAsActor(actorEvent.actorRef.id),
+ ...styleAsKeyword(actorEvent.type),
+ actorEvent.actorRef
+ );
+ } else {
+ log('✨ New %c%s%c actor without id:', ...styleAsKeyword(actorEvent.type), actorEvent);
+ }
+ };
+
+ const logEventEvent = (eventEvent: InspectedEventEvent) => {
+ if (isActorRef(eventEvent.actorRef)) {
+ log(
+ '🔔 %c%s%c received event %c%s%c from %c%s%c:',
+ ...styleAsActor(eventEvent.actorRef.id),
+ ...styleAsKeyword(eventEvent.event.type),
+ ...styleAsKeyword(eventEvent.sourceRef?.id),
+ eventEvent
+ );
+ } else {
+ log('🔔 Event', ...styleAsKeyword(eventEvent.event.type), ':', eventEvent);
+ }
+ };
+
+ const logSnapshotEvent = (snapshotEvent: InspectedSnapshotEvent) => {
+ if (isActorRef(snapshotEvent.actorRef)) {
+ log(
+ '📸 %c%s%c updated due to %c%s%c:',
+ ...styleAsActor(snapshotEvent.actorRef.id),
+ ...styleAsKeyword(snapshotEvent.event.type),
+ snapshotEvent.snapshot
+ );
+ } else {
+ log('📸 Snapshot due to %c%s%c:', ...styleAsKeyword(snapshotEvent.event.type), snapshotEvent);
+ }
+ };
+
+ return (inspectionEvent: InspectionEvent) => {
+ if (inspectionEvent.type === '@xstate.actor') {
+ logActorEvent(inspectionEvent);
+ } else if (inspectionEvent.type === '@xstate.event') {
+ logEventEvent(inspectionEvent);
+ } else if (inspectionEvent.type === '@xstate.snapshot') {
+ logSnapshotEvent(inspectionEvent);
+ } else {
+ log(`❓ Received inspection event:`, inspectionEvent);
+ }
+ };
+};
+
+const isActorRef = (actorRefLike: ActorRefLike): actorRefLike is AnyActorRef =>
+ 'id' in actorRefLike;
+
+const keywordStyle = 'font-weight: bold';
+const styleAsKeyword = (value: any) => [keywordStyle, value, ''] as const;
+
+const actorStyle = 'font-weight: bold; text-decoration: underline';
+const styleAsActor = (value: any) => [actorStyle, value, ''] as const;
diff --git a/packages/kbn-xstate-utils/src/index.ts b/packages/kbn-xstate-utils/src/index.ts
index 02cd8a2b176b1..dd8027759df9d 100644
--- a/packages/kbn-xstate-utils/src/index.ts
+++ b/packages/kbn-xstate-utils/src/index.ts
@@ -8,5 +8,6 @@
export * from './actions';
export * from './dev_tools';
+export * from './console_inspector';
export * from './notification_channel';
export * from './types';
From 76ea2a5b0eeec3f814c40d8ad6fa020168915311 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 2 Sep 2024 16:01:53 +0000
Subject: [PATCH 06/74] Add cateogorization service
---
.../observability/logs_overview/index.ts | 4 +-
.../categorize_documents.ts | 275 ++++++++++++++++++
.../categorize_logs_service.ts | 165 +++++++++++
.../count_documents.ts | 49 ++++
.../services/categorize_logs_service/index.ts | 8 +
.../categorize_logs_service/queries.ts | 127 ++++++++
.../services/categorize_logs_service/types.ts | 19 ++
.../observability/logs_overview/src/types.ts | 74 +++++
.../observability/logs_overview/tsconfig.json | 7 +-
.../logs_overview/utils/xstate5_utils.ts | 13 +
10 files changed, 736 insertions(+), 5 deletions(-)
create mode 100644 x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/index.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/types.ts
create mode 100644 x-pack/packages/observability/logs_overview/utils/xstate5_utils.ts
diff --git a/x-pack/packages/observability/logs_overview/index.ts b/x-pack/packages/observability/logs_overview/index.ts
index 7210d7181a9ed..b65160ca19875 100644
--- a/x-pack/packages/observability/logs_overview/index.ts
+++ b/x-pack/packages/observability/logs_overview/index.ts
@@ -5,6 +5,4 @@
* 2.0.
*/
-export function foo() {
- return 'hello world';
-}
+export { LogCategories } from './src/components/log_categories';
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
new file mode 100644
index 0000000000000..0d440621d7241
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
@@ -0,0 +1,275 @@
+/*
+ * 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 { ISearchGeneric } from '@kbn/search-types';
+import { lastValueFrom } from 'rxjs';
+import { fromPromise } from 'xstate5';
+import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils';
+import { z } from '@kbn/zod';
+import { LogsCategorizationParams } from './types';
+import { createCategorizationRequestParams } from './queries';
+import { LogCategory, LogCategoryChange } from '../../types';
+
+// the fraction of a category's histogram below which the category is considered rare
+const rarityThreshold = 0.2;
+
+export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
+ fromPromise<
+ {
+ categories: LogCategory[];
+ },
+ LogsCategorizationParams & {
+ samplingProbability: number;
+ ignoredQueries?: string[];
+ minDocsPerCategory?: number;
+ }
+ >(
+ async ({
+ input: {
+ index,
+ end,
+ start,
+ timeField,
+ messageField,
+ samplingProbability,
+ ignoredQueries,
+ minDocsPerCategory,
+ },
+ signal,
+ }) => {
+ const randomSampler = createRandomSamplerWrapper({
+ probability: samplingProbability,
+ seed: 1,
+ });
+
+ const requestParams = createCategorizationRequestParams({
+ index,
+ timeField,
+ messageField,
+ start,
+ end,
+ randomSampler,
+ ignoredQueries,
+ minDocsPerCategory,
+ });
+
+ const { rawResponse } = await lastValueFrom(search({ params: requestParams }));
+
+ if (rawResponse.aggregations == null) {
+ throw new Error('No aggregations found in large categories response');
+ }
+
+ const logCategoriesAggResult = randomSampler.unwrap(rawResponse.aggregations);
+
+ if (!('categories' in logCategoriesAggResult)) {
+ throw new Error('No categorization aggregation found in large categories response');
+ }
+
+ const logCategories =
+ (logCategoriesAggResult.categories.buckets as unknown[]).map(mapCategoryBucket) ?? [];
+
+ return {
+ categories: logCategories,
+ };
+ }
+ );
+
+const mapCategoryBucket = (bucket: any): LogCategory =>
+ esCategoryBucketSchema
+ .transform((parsedBucket) => ({
+ change: mapChangePoint(parsedBucket),
+ documentCount: parsedBucket.doc_count,
+ histogram: parsedBucket.histogram,
+ terms: parsedBucket.key,
+ }))
+ .parse(bucket);
+
+const mapChangePoint = ({ change, histogram }: EsCategoryBucket): LogCategoryChange => {
+ switch (change.type) {
+ case 'stationary':
+ if (isRareInHistogram(histogram)) {
+ return {
+ type: 'rare',
+ timestamp: findFirstNonZeroBucket(histogram)?.timestamp ?? histogram[0].timestamp,
+ };
+ } else {
+ return {
+ type: 'none',
+ };
+ }
+ case 'dip':
+ case 'spike':
+ return {
+ type: change.type,
+ timestamp: change.bucket.key,
+ };
+ case 'step_change':
+ return {
+ type: 'step',
+ timestamp: change.bucket.key,
+ };
+ case 'distribution_change':
+ return {
+ type: 'distribution',
+ timestamp: change.bucket.key,
+ };
+ case 'trend_change':
+ return {
+ type: 'trend',
+ timestamp: change.bucket.key,
+ correlationCoefficient: change.details.r_value,
+ };
+ case 'unknown':
+ return {
+ type: 'unknown',
+ rawChange: change.rawChange,
+ };
+ case 'non_stationary':
+ default:
+ return {
+ type: 'other',
+ };
+ }
+};
+
+/**
+ * The official types are lacking the change_point aggregation
+ */
+const esChangePointBucketSchema = z.object({
+ key: z.string().datetime(),
+ doc_count: z.number(),
+});
+
+const esChangePointDetailsSchema = z.object({
+ p_value: z.number(),
+});
+
+const esChangePointCorrelationSchema = esChangePointDetailsSchema.extend({
+ r_value: z.number(),
+});
+
+const esChangePointSchema = z.union([
+ z
+ .object({
+ bucket: esChangePointBucketSchema,
+ type: z.object({
+ dip: esChangePointDetailsSchema,
+ }),
+ })
+ .transform(({ bucket, type: { dip: details } }) => ({
+ type: 'dip' as const,
+ bucket,
+ details,
+ })),
+ z
+ .object({
+ bucket: esChangePointBucketSchema,
+ type: z.object({
+ spike: esChangePointDetailsSchema,
+ }),
+ })
+ .transform(({ bucket, type: { spike: details } }) => ({
+ type: 'spike' as const,
+ bucket,
+ details,
+ })),
+ z
+ .object({
+ bucket: esChangePointBucketSchema,
+ type: z.object({
+ step_change: esChangePointDetailsSchema,
+ }),
+ })
+ .transform(({ bucket, type: { step_change: details } }) => ({
+ type: 'step_change' as const,
+ bucket,
+ details,
+ })),
+ z
+ .object({
+ bucket: esChangePointBucketSchema,
+ type: z.object({
+ trend_change: esChangePointCorrelationSchema,
+ }),
+ })
+ .transform(({ bucket, type: { trend_change: details } }) => ({
+ type: 'trend_change' as const,
+ bucket,
+ details,
+ })),
+ z
+ .object({
+ bucket: esChangePointBucketSchema,
+ type: z.object({
+ distribution_change: esChangePointDetailsSchema,
+ }),
+ })
+ .transform(({ bucket, type: { distribution_change: details } }) => ({
+ type: 'distribution_change' as const,
+ bucket,
+ details,
+ })),
+ z
+ .object({
+ type: z.object({
+ non_stationary: esChangePointCorrelationSchema.extend({
+ trend: z.enum(['increasing', 'decreasing']),
+ }),
+ }),
+ })
+ .transform(({ type: { non_stationary: details } }) => ({
+ type: 'non_stationary' as const,
+ details,
+ })),
+ z
+ .object({
+ type: z.object({
+ stationary: z.object({}),
+ }),
+ })
+ .transform(() => ({ type: 'stationary' as const })),
+ z
+ .object({
+ type: z.object({}),
+ })
+ .transform((value) => ({ type: 'unknown' as const, rawChange: JSON.stringify(value) })),
+]);
+
+const esHistogramSchema = z
+ .object({
+ buckets: z.array(
+ z
+ .object({
+ key_as_string: z.string(),
+ doc_count: z.number(),
+ })
+ .transform((bucket) => ({
+ timestamp: bucket.key_as_string,
+ documentCount: bucket.doc_count,
+ }))
+ ),
+ })
+ .transform(({ buckets }) => buckets);
+
+type EsHistogram = z.output;
+
+const esCategoryBucketSchema = z.object({
+ key: z.string(),
+ doc_count: z.number(),
+ change: esChangePointSchema,
+ histogram: esHistogramSchema,
+});
+
+type EsCategoryBucket = z.output;
+
+// TODO: implement rarity criteria
+const isRareInHistogram = (histogram: EsHistogram): boolean =>
+ histogram.filter((bucket) => bucket.documentCount > 0).length <
+ histogram.length * rarityThreshold;
+
+const findFirstNonZeroBucket = (histogram: EsHistogram) =>
+ histogram.find((bucket) => bucket.documentCount > 0);
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
new file mode 100644
index 0000000000000..8c10efa6c6567
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
@@ -0,0 +1,165 @@
+/*
+ * 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 { MachineImplementationsFrom, assign, setup } from 'xstate5';
+import { getPlaceholderFor } from '../../../utils/xstate5_utils';
+import { categorizeDocuments } from './categorize_documents';
+import { countDocuments } from './count_documents';
+import { CategorizeLogsServiceDependencies, LogsCategorizationParams } from './types';
+import { LogCategory } from '../../types';
+
+export const categorizeLogsService = setup({
+ types: {
+ input: {} as LogsCategorizationParams,
+ output: {} as {
+ categories: LogCategory[];
+ documentCount: number;
+ samplingProbability: number;
+ },
+ context: {} as {
+ categories: LogCategory[];
+ documentCount: number;
+ parameters: LogsCategorizationParams;
+ samplingProbability: number;
+ error?: Error;
+ },
+ },
+ actors: {
+ countDocuments: getPlaceholderFor(countDocuments),
+ categorizeDocuments: getPlaceholderFor(categorizeDocuments),
+ },
+ actions: {
+ storeError: assign((_, params: { error: Error }) => ({
+ error: params.error,
+ })),
+ storeCategories: assign(({ context }, params: { categories: LogCategory[] }) => ({
+ categories: [...context.categories, ...params.categories],
+ })),
+ storeDocumentCount: assign(
+ (_, params: { documentCount: number; samplingProbability: number }) => ({
+ documentCount: params.documentCount,
+ samplingProbability: params.samplingProbability,
+ })
+ ),
+ },
+ guards: {
+ requiresSampling: ({ context }) => context.samplingProbability < 1,
+ },
+}).createMachine({
+ /** @xstate-layout N4IgpgJg5mDOIC5QGMCGAXMUD2AnAlgF5gAy2UsAdMtgK4B26+9UAItsrQLZiOwDEEbPTCVmAN2wBrUWkw4CxMhWp1GzNh2690sBBI4Z8wgNoAGALrmLiUAAdssfE2G2QAD0QAmABw-KACw+AQDMAOwAbGEAnGFmPgA0IACe3gFmlD7RIV4AjPlm6SFmXtEAvmVJclh4RKTkVDQMTCzsnDx8gsKiBjLUGDWK9SpN6q1aHbr69JJyxvTWJrk2SCAOTi70bp4Ivv5BoZExcYkpiD65lACsZrchUV5mEeFmIRVVAwp1yo1qLZrtHQCMC4XB4Sh2AA2GAAZnguP15LUlA1VM0NG1tHxprMjKZLNY3OtnPNtt4-IFguEorF4klUghQldKDSwmF7hEsmEvFc3pUQNUviiVDCwOhkAALDQAZVQXChkAAwp9anAuiIxDNpLIVUNZfLIUrdfg4ITVsTNmSELkzFdmWZsmzObkrukAld6d4vCFApEIrkwj5OT57gH3gLjcKqKLxVKWPqFRBlUiCGqQWDcBDoeg4bgEYLkWAE4ak8bTZYiY4Sa5VjsbXbKA7wpELq7Ch6zggQrkIoFShFSiVCo8IuGC0MfpQY5KNAAlMBcVDMDTJwYmgRCDW9HUpurzxfLlirr7llb2KuW2uIAMBXtNiLPHxXYIXLyehA+LyUEI-l62kK0vcY6RsM0ZijOLD7ku9ArmWwKguCUKwvCiJrsQUGHlAx6qrAZrnhspJXtaYS3o22QPiET4vrkb6dgGzK+BEvLejc9xBhU-L0NgEBwG447fA0lYETWoA7AAtBE74ScBu5RmiYwAliuhCdWWxEQEtEMjalzaREGncl4jwOj4MloaBU7gXGUDFkau68eaF6EaJiBMdEZHNmY7LekE76GW5-qxFRpQaQOfIfLJ5nTlZGEwUecEqZeznWiENzueyP4BIG3YBO+FKZQ+uQpUG0RRClplCpFS4lglTkeN4tqXF4TFXC6eQxNEtq+R1LIxAOHWZSVkTlYWk6bmANUiXVuwNZQTV2q1AbRB1HZaSUmQOtEvKUb40TpLkHFlEAA */
+ id: 'categorizeLogs',
+ context: ({ input }) => ({
+ categories: [],
+ documentCount: 0,
+ parameters: input,
+ samplingProbability: 0,
+ }),
+ initial: 'countingDocuments',
+ states: {
+ countingDocuments: {
+ invoke: {
+ src: 'countDocuments',
+ input: ({ context }) => context.parameters,
+ onDone: [
+ {
+ target: 'fetchingSampledCategories',
+ guard: 'requiresSampling',
+ actions: [
+ {
+ type: 'storeDocumentCount',
+ params: ({ event }) => event.output,
+ },
+ ],
+ },
+ {
+ target: 'fetchingRemainingCategories',
+ actions: [
+ {
+ type: 'storeDocumentCount',
+ params: ({ event }) => event.output,
+ },
+ ],
+ },
+ ],
+ onError: {
+ target: 'failed',
+ actions: [],
+ },
+ },
+ },
+
+ fetchingSampledCategories: {
+ invoke: {
+ src: 'categorizeDocuments',
+ id: 'categorizeSampledCategories',
+ input: ({ context }) => ({
+ ...context.parameters,
+ samplingProbability: context.samplingProbability,
+ ignoredQueries: [],
+ minDocsPerCategory: 10,
+ }),
+ onDone: {
+ target: 'fetchingRemainingCategories',
+ actions: [
+ {
+ type: 'storeCategories',
+ params: ({ event }) => event.output,
+ },
+ ],
+ },
+ onError: 'failed',
+ },
+ },
+
+ fetchingRemainingCategories: {
+ invoke: {
+ src: 'categorizeDocuments',
+ id: 'categorizeRemainingCategories',
+ input: ({ context }) => ({
+ ...context.parameters,
+ samplingProbability: context.samplingProbability,
+ ignoredQueries: context.categories.map((category) => category.terms),
+ minDocsPerCategory: 0,
+ }),
+ onDone: {
+ target: 'done',
+ actions: [
+ {
+ type: 'storeCategories',
+ params: ({ event }) => event.output,
+ },
+ ],
+ },
+ onError: 'failed',
+ },
+ },
+
+ failed: {
+ type: 'final',
+ },
+
+ done: {
+ type: 'final',
+ },
+ },
+ output: ({ context }) => ({
+ categories: context.categories,
+ documentCount: context.documentCount,
+ samplingProbability: context.samplingProbability,
+ }),
+});
+
+export const createCategorizeLogsServiceImplementations = ({
+ search,
+}: CategorizeLogsServiceDependencies): MachineImplementationsFrom<
+ typeof categorizeLogsService
+> => ({
+ actors: {
+ categorizeDocuments: categorizeDocuments({ search }),
+ countDocuments: countDocuments({ search }),
+ },
+});
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts
new file mode 100644
index 0000000000000..e231745b8c88d
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts
@@ -0,0 +1,49 @@
+/*
+ * 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 { getSampleProbability } from '@kbn/ml-random-sampler-utils';
+import { ISearchGeneric } from '@kbn/search-types';
+import { lastValueFrom } from 'rxjs';
+import { fromPromise } from 'xstate5';
+import { LogsCategorizationParams } from './types';
+import { createCategorizationQuery } from './queries';
+
+export const countDocuments = ({ search }: { search: ISearchGeneric }) =>
+ fromPromise<
+ {
+ documentCount: number;
+ samplingProbability: number;
+ },
+ LogsCategorizationParams
+ >(async ({ input: { index, end, start, timeField, messageField }, signal }) => {
+ const { rawResponse: totalHitsResponse } = await lastValueFrom(
+ search(
+ {
+ params: {
+ index,
+ size: 0,
+ track_total_hits: true,
+ query: createCategorizationQuery(messageField, timeField, start, end),
+ },
+ },
+ { abortSignal: signal }
+ )
+ );
+
+ const documentCount =
+ totalHitsResponse.hits.total == null
+ ? 0
+ : typeof totalHitsResponse.hits.total === 'number'
+ ? totalHitsResponse.hits.total
+ : totalHitsResponse.hits.total.value;
+ const samplingProbability = getSampleProbability(documentCount);
+
+ return {
+ documentCount,
+ samplingProbability,
+ };
+ });
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/index.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/index.ts
new file mode 100644
index 0000000000000..149359b7d2015
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/index.ts
@@ -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 './categorize_logs_service';
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
new file mode 100644
index 0000000000000..b026133fd11bb
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
@@ -0,0 +1,127 @@
+/*
+ * 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import { calculateAuto } from '@kbn/calculate-auto';
+import { RandomSamplerWrapper } from '@kbn/ml-random-sampler-utils';
+import moment from 'moment';
+
+const isoTimestampFormat = "YYYY-MM-DD'T'HH:mm:ss.SSS'Z'";
+
+export const createCategorizationQuery = (
+ messageField: string,
+ timeField: string,
+ start: string,
+ end: string,
+ ignoredQueries: string[] = []
+): QueryDslQueryContainer => {
+ return {
+ bool: {
+ filter: [
+ {
+ exists: {
+ field: messageField,
+ },
+ },
+ {
+ range: {
+ [timeField]: {
+ gte: start,
+ lte: end,
+ format: 'strict_date_time',
+ },
+ },
+ },
+ ],
+ must_not: ignoredQueries.map((ignoredQuery) => ({
+ match: {
+ [messageField]: {
+ query: ignoredQuery,
+ operator: 'AND' as const,
+ fuzziness: 0,
+ auto_generate_synonyms_phrase_query: false,
+ },
+ },
+ })),
+ },
+ };
+};
+
+export const createCategorizationRequestParams = ({
+ index,
+ timeField,
+ messageField,
+ start,
+ end,
+ randomSampler,
+ minDocsPerCategory = 0,
+ ignoredQueries = [],
+}: {
+ start: string;
+ end: string;
+ index: string;
+ timeField: string;
+ messageField: string;
+ randomSampler: RandomSamplerWrapper;
+ minDocsPerCategory?: number;
+ ignoredQueries?: string[];
+}) => {
+ const startMoment = moment(start, isoTimestampFormat);
+ const endMoment = moment(end, isoTimestampFormat);
+ const fixedIntervalDuration = calculateAuto.atLeast(
+ 24,
+ moment.duration(endMoment.diff(startMoment))
+ );
+ const fixedIntervalSize = `${fixedIntervalDuration?.asMinutes()}m`;
+
+ return {
+ index,
+ size: 0,
+ track_total_hits: false,
+ query: createCategorizationQuery(messageField, timeField, start, end, ignoredQueries),
+ aggs: randomSampler.wrap({
+ histogram: {
+ date_histogram: {
+ field: '@timestamp',
+ fixed_interval: fixedIntervalSize,
+ extended_bounds: {
+ min: start,
+ max: end,
+ },
+ },
+ },
+ categories: {
+ categorize_text: {
+ field: messageField,
+ size: 10000,
+ categorization_analyzer: {
+ tokenizer: 'standard',
+ },
+ ...(minDocsPerCategory > 0 ? { min_doc_count: minDocsPerCategory } : {}),
+ },
+ aggs: {
+ histogram: {
+ date_histogram: {
+ field: timeField,
+ fixed_interval: fixedIntervalSize,
+ extended_bounds: {
+ min: start,
+ max: end,
+ },
+ },
+ },
+ change: {
+ // @ts-expect-error the official types don't support the change_point aggregation
+ change_point: {
+ buckets_path: 'histogram>_count',
+ },
+ },
+ },
+ },
+ }),
+ };
+};
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts
new file mode 100644
index 0000000000000..c812b1b5aac3e
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import { ISearchGeneric } from '@kbn/search-types';
+
+export interface CategorizeLogsServiceDependencies {
+ search: ISearchGeneric;
+}
+
+export interface LogsCategorizationParams {
+ start: string;
+ end: string;
+ index: string;
+ timeField: string;
+ messageField: string;
+}
diff --git a/x-pack/packages/observability/logs_overview/src/types.ts b/x-pack/packages/observability/logs_overview/src/types.ts
new file mode 100644
index 0000000000000..4c3d27eca7e7c
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/types.ts
@@ -0,0 +1,74 @@
+/*
+ * 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 interface LogCategory {
+ change: LogCategoryChange;
+ documentCount: number;
+ histogram: LogCategoryHistogramBucket[];
+ terms: string;
+}
+
+export type LogCategoryChange =
+ | LogCategoryNoChange
+ | LogCategoryRareChange
+ | LogCategorySpikeChange
+ | LogCategoryDipChange
+ | LogCategoryStepChange
+ | LogCategoryDistributionChange
+ | LogCategoryTrendChange
+ | LogCategoryOtherChange
+ | LogCategoryUnknownChange;
+
+export interface LogCategoryNoChange {
+ type: 'none';
+}
+
+export interface LogCategoryRareChange {
+ type: 'rare';
+ timestamp: string;
+}
+
+export interface LogCategorySpikeChange {
+ type: 'spike';
+ timestamp: string;
+}
+
+export interface LogCategoryDipChange {
+ type: 'dip';
+ timestamp: string;
+}
+
+export interface LogCategoryStepChange {
+ type: 'step';
+ timestamp: string;
+}
+
+export interface LogCategoryTrendChange {
+ type: 'trend';
+ timestamp: string;
+ correlationCoefficient: number;
+}
+
+export interface LogCategoryDistributionChange {
+ type: 'distribution';
+ timestamp: string;
+}
+
+export interface LogCategoryOtherChange {
+ type: 'other';
+ timestamp?: string;
+}
+
+export interface LogCategoryUnknownChange {
+ type: 'unknown';
+ rawChange: string;
+}
+
+export interface LogCategoryHistogramBucket {
+ documentCount: number;
+ timestamp: string;
+}
diff --git a/x-pack/packages/observability/logs_overview/tsconfig.json b/x-pack/packages/observability/logs_overview/tsconfig.json
index 5c989599ec9ad..476238cbafcc3 100644
--- a/x-pack/packages/observability/logs_overview/tsconfig.json
+++ b/x-pack/packages/observability/logs_overview/tsconfig.json
@@ -5,12 +5,15 @@
"types": [
"jest",
"node",
- "react"
+ "react",
+ "@kbn/ambient-ui-types",
+ "@kbn/ambient-storybook-types",
+ "@emotion/react/types/css-prop"
]
},
"include": [
"**/*.ts",
- "**/*.tsx",
+ "**/*.tsx",
],
"exclude": [
"target/**/*"
diff --git a/x-pack/packages/observability/logs_overview/utils/xstate5_utils.ts b/x-pack/packages/observability/logs_overview/utils/xstate5_utils.ts
new file mode 100644
index 0000000000000..3df0bf4ea3988
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/utils/xstate5_utils.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const getPlaceholderFor = any>(
+ implementationFactory: ImplementationFactory
+): ReturnType =>
+ (() => {
+ throw new Error('Not implemented');
+ }) as ReturnType;
From 0528e8bd05fbd61ebc518625faeddcf4ddeac67c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 2 Sep 2024 16:02:12 +0000
Subject: [PATCH 07/74] Add categories grid
---
.../src/components/log_categories/index.ts | 8 +
.../log_categories/log_categories.tsx | 61 +++++++
.../log_categories/log_categories_grid.tsx | 170 ++++++++++++++++++
.../log_categories_grid_cell.tsx | 97 ++++++++++
.../log_categories_grid_change_time_cell.tsx | 54 ++++++
.../log_categories_grid_change_type_cell.tsx | 108 +++++++++++
.../log_categories_grid_count_cell.tsx | 32 ++++
.../log_categories_grid_histogram_cell.tsx | 99 ++++++++++
.../log_categories_grid_pattern_cell.tsx | 61 +++++++
.../logs_categories_loading_content.tsx | 12 ++
.../logs_categories_result_content.tsx | 28 +++
.../components/app/service_logs/index.tsx | 110 +++++++-----
12 files changed, 797 insertions(+), 43 deletions(-)
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/index.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_histogram_cell.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_result_content.tsx
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/index.ts b/x-pack/packages/observability/logs_overview/src/components/log_categories/index.ts
new file mode 100644
index 0000000000000..786475396237c
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/index.ts
@@ -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_categories';
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
new file mode 100644
index 0000000000000..1de48f74f97a8
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
@@ -0,0 +1,61 @@
+/*
+ * 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 { ISearchGeneric } from '@kbn/search-types';
+import { createConsoleInspector } from '@kbn/xstate-utils';
+import { useMachine } from '@xstate5/react';
+import React from 'react';
+import {
+ categorizeLogsService,
+ createCategorizeLogsServiceImplementations,
+} from '../../services/categorize_logs_service';
+import { LogCategoriesLoadingContent } from './logs_categories_loading_content';
+import {
+ LogCategoriesResultContent,
+ LogCategoriesResultContentDependencies,
+} from './logs_categories_result_content';
+
+interface LogCategoriesProps {
+ dependencies: LogCategoriesDependencies;
+}
+
+export type LogCategoriesDependencies = LogCategoriesResultContentDependencies & {
+ search: ISearchGeneric;
+};
+
+export const LogCategories: React.FC = ({ dependencies }) => {
+ const [categorizeLogsServiceState, _sendToCategorizeLogsService] = useMachine(
+ categorizeLogsService.provide(
+ createCategorizeLogsServiceImplementations({ search: dependencies.search })
+ ),
+ {
+ inspect: consoleInspector,
+ input: {
+ index: 'logs-*-*',
+ start: '2024-12-01T00:00:00.000Z',
+ end: '2024-12-03T00:00:00.000Z',
+ timeField: '@timestamp',
+ messageField: 'message',
+ },
+ }
+ );
+
+ if (categorizeLogsServiceState.matches('done')) {
+ return (
+
+ );
+ } else if (categorizeLogsServiceState.matches('failed')) {
+ return Error
;
+ } else {
+ return ;
+ }
+};
+
+const consoleInspector = createConsoleInspector();
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
new file mode 100644
index 0000000000000..995706bf12663
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
@@ -0,0 +1,170 @@
+/*
+ * 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 {
+ EuiDataGrid,
+ EuiDataGridColumnSortingConfig,
+ EuiDataGridPaginationProps,
+} from '@elastic/eui';
+import { createConsoleInspector } from '@kbn/xstate-utils';
+import { useMachine } from '@xstate5/react';
+import React, { useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { assign, setup } from 'xstate5';
+import _ from 'lodash';
+import { LogCategory } from '../../types';
+import {
+ LogCategoriesGridCellDependencies,
+ createCellContext,
+ logCategoriesGridColumns,
+ renderLogCategoriesGridCell,
+} from './log_categories_grid_cell';
+
+export interface LogCategoriesGridProps {
+ dependencies: LogCategoriesGridDependencies;
+ logCategories: LogCategory[];
+}
+
+export type LogCategoriesGridDependencies = LogCategoriesGridCellDependencies;
+
+export const LogCategoriesGrid: React.FC = ({
+ dependencies,
+ logCategories,
+}) => {
+ const [gridState, dispatchGridEvent] = useMachine(gridStateService, {
+ input: {
+ visibleColumns: logCategoriesGridColumns.map(({ id }) => id),
+ },
+ inspect: consoleInspector,
+ });
+
+ // TODO: sort data based on sorting columns
+ const sortedLogCategories = useMemo(() => {
+ const sortingCriteria = gridState.context.sortingColumns.map(
+ ({ id, direction }): [(logCategory: LogCategory) => any, 'asc' | 'desc'] => {
+ switch (id) {
+ case 'count':
+ return [(logCategory: LogCategory) => logCategory.documentCount, direction];
+ case 'type':
+ // TODO: use better sorting weight for change types
+ return [(logCategory: LogCategory) => logCategory.change.type, direction];
+ case 'change':
+ return [
+ (logCategory: LogCategory) =>
+ 'timestamp' in logCategory.change ? logCategory.change.timestamp ?? '' : '',
+ direction,
+ ];
+ default:
+ return [_.identity, direction];
+ }
+ }
+ );
+ return _.orderBy(
+ logCategories,
+ sortingCriteria.map(([accessor]) => accessor),
+ sortingCriteria.map(([, direction]) => direction)
+ );
+ }, [gridState.context.sortingColumns, logCategories]);
+
+ return (
+
+ dispatchGridEvent({ type: 'changeVisibleColumns', visibleColumns }),
+ }}
+ cellContext={createCellContext(sortedLogCategories, dependencies)}
+ pagination={{
+ ...gridState.context.pagination,
+ onChangeItemsPerPage: (pageSize) => dispatchGridEvent({ type: 'changePageSize', pageSize }),
+ onChangePage: (pageIndex) => dispatchGridEvent({ type: 'changePageIndex', pageIndex }),
+ }}
+ renderCellValue={renderLogCategoriesGridCell}
+ rowCount={sortedLogCategories.length}
+ sorting={{
+ columns: gridState.context.sortingColumns,
+ onSort: (sortingColumns) =>
+ dispatchGridEvent({ type: 'changeSortingColumns', sortingColumns }),
+ }}
+ />
+ );
+};
+
+const gridStateService = setup({
+ types: {
+ context: {} as {
+ visibleColumns: string[];
+ pagination: Pick;
+ sortingColumns: EuiDataGridColumnSortingConfig[];
+ },
+ events: {} as
+ | {
+ type: 'changePageSize';
+ pageSize: number;
+ }
+ | {
+ type: 'changePageIndex';
+ pageIndex: number;
+ }
+ | {
+ type: 'changeSortingColumns';
+ sortingColumns: EuiDataGridColumnSortingConfig[];
+ }
+ | {
+ type: 'changeVisibleColumns';
+ visibleColumns: string[];
+ },
+ input: {} as {
+ visibleColumns: string[];
+ },
+ },
+}).createMachine({
+ id: 'logCategoriesGridState',
+ context: ({ input }) => ({
+ visibleColumns: input.visibleColumns,
+ pagination: { pageIndex: 0, pageSize: 20, pageSizeOptions: [10, 20, 50] },
+ sortingColumns: [{ id: 'change_time', direction: 'desc' }],
+ }),
+ on: {
+ changePageSize: {
+ actions: assign(({ context, event }) => ({
+ pagination: {
+ ...context.pagination,
+ pageIndex: 0,
+ pageSize: event.pageSize,
+ },
+ })),
+ },
+ changePageIndex: {
+ actions: assign(({ context, event }) => ({
+ pagination: {
+ ...context.pagination,
+ pageIndex: event.pageIndex,
+ },
+ })),
+ },
+ changeSortingColumns: {
+ actions: assign(({ event }) => ({
+ sortingColumns: event.sortingColumns,
+ })),
+ },
+ changeVisibleColumns: {
+ actions: assign(({ event }) => ({
+ visibleColumns: event.visibleColumns,
+ })),
+ },
+ },
+});
+
+const consoleInspector = createConsoleInspector();
+
+const logCategoriesGridLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoriesGrid.euiDataGrid.logCategoriesLabel',
+ { defaultMessage: 'Log categories' }
+);
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx
new file mode 100644
index 0000000000000..9862b00270076
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx
@@ -0,0 +1,97 @@
+/*
+ * 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 { EuiDataGridColumn, RenderCellValue } from '@elastic/eui';
+import React from 'react';
+import { LogCategory } from '../../types';
+import {
+ LogCategoriesGridChangeTimeCell,
+ LogCategoriesGridChangeTimeCellDependencies,
+ logCategoriesGridChangeTimeColumn,
+} from './log_categories_grid_change_time_cell';
+import {
+ LogCategoriesGridChangeTypeCell,
+ logCategoriesGridChangeTypeColumn,
+} from './log_categories_grid_change_type_cell';
+import {
+ LogCategoriesGridCountCell,
+ logCategoriesGridCountColumn,
+} from './log_categories_grid_count_cell';
+import {
+ LogCategoriesGridHistogramCell,
+ LogCategoriesGridHistogramCellDependencies,
+ logCategoriesGridHistoryColumn,
+} from './log_categories_grid_histogram_cell';
+import {
+ LogCategoriesGridPatternCell,
+ logCategoriesGridPatternColumn,
+} from './log_categories_grid_pattern_cell';
+
+export interface LogCategoriesGridCellContext {
+ dependencies: LogCategoriesGridCellDependencies;
+ logCategories: LogCategory[];
+}
+
+export type LogCategoriesGridCellDependencies = LogCategoriesGridHistogramCellDependencies &
+ LogCategoriesGridChangeTimeCellDependencies;
+
+export const renderLogCategoriesGridCell: RenderCellValue = ({
+ rowIndex,
+ columnId,
+ isExpanded,
+ ...rest
+}) => {
+ const { dependencies, logCategories } = getCellContext(rest);
+
+ const logCategory = logCategories[rowIndex];
+
+ switch (columnId as LogCategoriesGridColumnId) {
+ case 'pattern':
+ return ;
+ case 'count':
+ return ;
+ case 'history':
+ return (
+
+ );
+ case 'change_type':
+ return ;
+ case 'change_time':
+ return (
+
+ );
+ default:
+ return <>->;
+ }
+};
+
+export const logCategoriesGridColumns = [
+ logCategoriesGridPatternColumn,
+ logCategoriesGridCountColumn,
+ logCategoriesGridChangeTypeColumn,
+ logCategoriesGridChangeTimeColumn,
+ logCategoriesGridHistoryColumn,
+] satisfies EuiDataGridColumn[];
+
+type LogCategoriesGridColumnId = (typeof logCategoriesGridColumns)[number]['id'];
+
+const cellContextKey = 'cellContext';
+
+const getCellContext = (cellContext: object): LogCategoriesGridCellContext =>
+ (cellContextKey in cellContext
+ ? cellContext[cellContextKey]
+ : {}) as LogCategoriesGridCellContext;
+
+export const createCellContext = (
+ logCategories: LogCategory[],
+ dependencies: LogCategoriesGridCellDependencies
+): { [cellContextKey]: LogCategoriesGridCellContext } => ({
+ [cellContextKey]: {
+ dependencies,
+ logCategories,
+ },
+});
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx
new file mode 100644
index 0000000000000..cd24e870f431b
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx
@@ -0,0 +1,54 @@
+/*
+ * 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 { EuiDataGridColumn } from '@elastic/eui';
+import { SettingsStart } from '@kbn/core-ui-settings-browser';
+import { i18n } from '@kbn/i18n';
+import moment from 'moment';
+import React, { useMemo } from 'react';
+import { LogCategory } from '../../types';
+
+export const logCategoriesGridChangeTimeColumn = {
+ id: 'change_time' as const,
+ display: i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoriesGrid.changeTimeColumnLabel',
+ {
+ defaultMessage: 'Change at',
+ }
+ ),
+ isSortable: true,
+ initialWidth: 200,
+ schema: 'datetime',
+} satisfies EuiDataGridColumn;
+
+export interface LogCategoriesGridChangeTimeCellProps {
+ dependencies: LogCategoriesGridChangeTimeCellDependencies;
+ logCategory: LogCategory;
+}
+
+export interface LogCategoriesGridChangeTimeCellDependencies {
+ uiSettings: SettingsStart;
+}
+
+export const LogCategoriesGridChangeTimeCell: React.FC = ({
+ dependencies,
+ logCategory,
+}) => {
+ const dateFormat = useMemo(
+ () => dependencies.uiSettings.client.get('dateFormat'),
+ [dependencies.uiSettings.client]
+ );
+ if (!('timestamp' in logCategory.change && logCategory.change.timestamp != null)) {
+ return null;
+ }
+
+ if (dateFormat) {
+ return <>{moment(logCategory.change.timestamp).format(dateFormat)}>;
+ } else {
+ return <>{logCategory.change.timestamp}>;
+ }
+};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx
new file mode 100644
index 0000000000000..a692983617069
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx
@@ -0,0 +1,108 @@
+/*
+ * 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 { EuiBadge, EuiDataGridColumn } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { LogCategory } from '../../types';
+
+export const logCategoriesGridChangeTypeColumn = {
+ id: 'change_type' as const,
+ display: i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoriesGrid.changeTypeColumnLabel',
+ {
+ defaultMessage: 'Change type',
+ }
+ ),
+ isSortable: true,
+ initialWidth: 130,
+} satisfies EuiDataGridColumn;
+
+export interface LogCategoriesGridChangeTypeCellProps {
+ logCategory: LogCategory;
+}
+
+export const LogCategoriesGridChangeTypeCell: React.FC = ({
+ logCategory,
+}) => {
+ switch (logCategory.change.type) {
+ case 'dip':
+ return {dipBadgeLabel};
+ case 'spike':
+ return {spikeBadgeLabel};
+ case 'step':
+ return {stepBadgeLabel};
+ case 'distribution':
+ return {distributionBadgeLabel};
+ case 'rare':
+ return {rareBadgeLabel};
+ case 'trend':
+ return {trendBadgeLabel};
+ case 'other':
+ return {otherBadgeLabel};
+ case 'none':
+ return <>->;
+ default:
+ return {unknownBadgeLabel};
+ }
+};
+
+const dipBadgeLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.dipChangeTypeBadgeLabel',
+ {
+ defaultMessage: 'Dip',
+ }
+);
+
+const spikeBadgeLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.spikeChangeTypeBadgeLabel',
+ {
+ defaultMessage: 'Spike',
+ }
+);
+
+const stepBadgeLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.spikeChangeTypeBadgeLabel',
+ {
+ defaultMessage: 'Step',
+ }
+);
+
+const distributionBadgeLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.distributionChangeTypeBadgeLabel',
+ {
+ defaultMessage: 'Distribution',
+ }
+);
+
+const trendBadgeLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.spikeChangeTypeBadgeLabel',
+ {
+ defaultMessage: 'Trend',
+ }
+);
+
+const otherBadgeLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.otherChangeTypeBadgeLabel',
+ {
+ defaultMessage: 'Other',
+ }
+);
+
+const unknownBadgeLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.unknownChangeTypeBadgeLabel',
+ {
+ defaultMessage: 'Unknown',
+ }
+);
+
+const rareBadgeLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.rareChangeTypeBadgeLabel',
+ {
+ defaultMessage: 'Rare',
+ }
+);
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx
new file mode 100644
index 0000000000000..4b5d8e0179e07
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx
@@ -0,0 +1,32 @@
+/*
+ * 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 { EuiDataGridColumn } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedNumber } from '@kbn/i18n-react';
+import React from 'react';
+import { LogCategory } from '../../types';
+
+export const logCategoriesGridCountColumn = {
+ id: 'count' as const,
+ display: i18n.translate('xpack.observabilityLogsOverview.logCategoriesGrid.countColumnLabel', {
+ defaultMessage: 'Events',
+ }),
+ isSortable: true,
+ schema: 'numeric',
+ initialWidth: 120,
+} satisfies EuiDataGridColumn;
+
+export interface LogCategoriesGridCountCellProps {
+ logCategory: LogCategory;
+}
+
+export const LogCategoriesGridCountCell: React.FC = ({
+ logCategory,
+}) => {
+ return ;
+};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_histogram_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_histogram_cell.tsx
new file mode 100644
index 0000000000000..2fb50b0f2f3b4
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_histogram_cell.tsx
@@ -0,0 +1,99 @@
+/*
+ * 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 {
+ BarSeries,
+ Chart,
+ LineAnnotation,
+ LineAnnotationStyle,
+ PartialTheme,
+ Settings,
+ Tooltip,
+ TooltipType,
+} from '@elastic/charts';
+import { EuiDataGridColumn } from '@elastic/eui';
+import { ChartsPluginStart } from '@kbn/charts-plugin/public';
+import { i18n } from '@kbn/i18n';
+import { RecursivePartial } from '@kbn/utility-types';
+import React from 'react';
+import { LogCategory, LogCategoryHistogramBucket } from '../../types';
+
+export const logCategoriesGridHistoryColumn = {
+ id: 'history' as const,
+ display: i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoriesGrid.histogramColumnLabel',
+ {
+ defaultMessage: 'Timeline',
+ }
+ ),
+ isSortable: false,
+ initialWidth: 250,
+ isExpandable: false,
+} satisfies EuiDataGridColumn;
+
+export interface LogCategoriesGridHistogramCellProps {
+ dependencies: LogCategoriesGridHistogramCellDependencies;
+ logCategory: LogCategory;
+}
+
+export interface LogCategoriesGridHistogramCellDependencies {
+ charts: ChartsPluginStart;
+}
+
+export const LogCategoriesGridHistogramCell: React.FC = ({
+ dependencies: { charts },
+ logCategory,
+}) => {
+ const baseTheme = charts.theme.useChartsBaseTheme();
+ const sparklineTheme = charts.theme.useSparklineOverrides();
+
+ return (
+
+
+
+
+ {'timestamp' in logCategory.change && logCategory.change.timestamp != null ? (
+
+ ) : null}
+
+ );
+};
+
+const localThemeOverrides: PartialTheme = {
+ scales: {
+ histogramPadding: 0.1,
+ },
+ background: {
+ color: 'transparent',
+ },
+};
+
+const annotationStyle: RecursivePartial = {
+ line: {
+ strokeWidth: 2,
+ },
+};
+
+const timestampAccessor = (histogram: LogCategoryHistogramBucket) =>
+ new Date(histogram.timestamp).getTime();
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
new file mode 100644
index 0000000000000..ae5471ef0868e
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
@@ -0,0 +1,61 @@
+/*
+ * 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 { EuiDataGridColumn, euiFontSize, useEuiTheme } from '@elastic/eui';
+import { css } from '@emotion/react';
+import { i18n } from '@kbn/i18n';
+import React, { useMemo } from 'react';
+import { LogCategory } from '../../types';
+
+export const logCategoriesGridPatternColumn = {
+ id: 'pattern' as const,
+ display: i18n.translate('xpack.observabilityLogsOverview.logCategoriesGrid.patternColumnLabel', {
+ defaultMessage: 'Pattern',
+ }),
+ isSortable: false,
+ schema: 'string',
+} satisfies EuiDataGridColumn;
+
+export interface LogCategoriesGridPatternCellProps {
+ logCategory: LogCategory;
+}
+
+export const LogCategoriesGridPatternCell: React.FC = ({
+ logCategory,
+}) => {
+ const theme = useEuiTheme();
+ const { euiTheme } = theme;
+ const termsList = useMemo(() => logCategory.terms.split(' '), [logCategory.terms]);
+
+ const commonStyle = css`
+ display: inline-block;
+ font-family: ${euiTheme.font.familyCode};
+ font-weight: ${euiTheme.font.weight.semiBold};
+ ${euiFontSize(theme, 'xs')};
+ margin-right: ${euiTheme.size.xs};
+ `;
+
+ const termStyle = css`
+ ${commonStyle};
+ `;
+
+ const separatorStyle = css`
+ ${commonStyle};
+ color: ${euiTheme.colors.subduedText};
+ `;
+
+ return (
+
+ {termsList.map((term, index) => (
+
+ {term}
+ {index !== termsList.length - 1 && *
}
+
+ ))}
+
+ );
+};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx
new file mode 100644
index 0000000000000..3cf5240b7bb3c
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx
@@ -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 React from 'react';
+
+export const LogCategoriesLoadingContent: React.FC = () => {
+ return Loading...
;
+};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_result_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_result_content.tsx
new file mode 100644
index 0000000000000..ab82051b9f635
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_result_content.tsx
@@ -0,0 +1,28 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { LogCategory } from '../../types';
+import { LogCategoriesGrid, LogCategoriesGridDependencies } from './log_categories_grid';
+
+export interface LogCategoriesResultContentProps {
+ dependencies: LogCategoriesResultContentDependencies;
+ logCategories: LogCategory[];
+}
+
+export type LogCategoriesResultContentDependencies = LogCategoriesGridDependencies;
+
+export const LogCategoriesResultContent: React.FC = ({
+ dependencies,
+ logCategories,
+}) => {
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
index 90f31e29b5960..16cd84babf227 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
@@ -5,57 +5,81 @@
* 2.0.
*/
-import React from 'react';
-import moment from 'moment';
-import { LogStream } from '@kbn/logs-shared-plugin/public';
+import React, { useMemo } from 'react';
+import { LogCategories } from '@kbn/observability-logs-overview';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
-import { useFetcher } from '../../../hooks/use_fetcher';
-import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
-import { APIReturnType } from '../../../services/rest/create_call_apm_api';
-
import { CONTAINER_ID, SERVICE_ENVIRONMENT, SERVICE_NAME } from '../../../../common/es_fields/apm';
-import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
-import { useTimeRange } from '../../../hooks/use_time_range';
+// import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
+import { useKibana } from '../../../context/kibana_context/use_kibana';
+// import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
+// import { useFetcher } from '../../../hooks/use_fetcher';
+// import { useTimeRange } from '../../../hooks/use_time_range';
+import { APIReturnType } from '../../../services/rest/create_call_apm_api';
export function ServiceLogs() {
- const { serviceName } = useApmServiceContext();
-
const {
- query: { environment, kuery, rangeFrom, rangeTo },
- } = useAnyOfApmParams('/services/{serviceName}/logs', '/logs-services/{serviceName}/logs');
-
- const { start, end } = useTimeRange({ rangeFrom, rangeTo });
-
- const { data } = useFetcher(
- (callApmApi) => {
- if (start && end) {
- return callApmApi('GET /internal/apm/services/{serviceName}/infrastructure_attributes', {
- params: {
- path: { serviceName },
- query: {
- environment,
- kuery,
- start,
- end,
- },
- },
- });
- }
+ services: {
+ charts,
+ data: {
+ search: { search },
+ },
+ settings: uiSettings,
},
- [environment, kuery, serviceName, start, end]
- );
+ } = useKibana();
+ // const { serviceName } = useApmServiceContext();
- return (
-
+ const logCategoriesDependencies = useMemo(
+ () => ({
+ charts,
+ search,
+ uiSettings,
+ }),
+ [charts, search, uiSettings]
);
+
+ // TODO: make charts required
+ if (typeof logCategoriesDependencies.charts === 'undefined') {
+ return null;
+ }
+
+ // const {
+ // query: { environment, kuery, rangeFrom, rangeTo },
+ // } = useAnyOfApmParams('/services/{serviceName}/logs', '/logs-services/{serviceName}/logs');
+
+ // const { start, end } = useTimeRange({ rangeFrom, rangeTo });
+
+ // const { data } = useFetcher(
+ // (callApmApi) => {
+ // if (start && end) {
+ // return callApmApi('GET /internal/apm/services/{serviceName}/infrastructure_attributes', {
+ // params: {
+ // path: { serviceName },
+ // query: {
+ // environment,
+ // kuery,
+ // start,
+ // end,
+ // },
+ // },
+ // });
+ // }
+ // },
+ // [environment, kuery, serviceName, start, end]
+ // );
+
+ return ;
+
+ // return (
+ //
+ // );
}
export function getInfrastructureKQLFilter({
From 0b7fc2ccb624009159ac2477e0a84cf2d7f790c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 2 Sep 2024 16:02:30 +0000
Subject: [PATCH 08/74] Enable translation linting for the package
---
.eslintrc.js | 1 +
x-pack/.i18nrc.json | 3 +++
2 files changed, 4 insertions(+)
diff --git a/.eslintrc.js b/.eslintrc.js
index 2b8c6c819bb3e..ba337d7e52161 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -953,6 +953,7 @@ module.exports = {
files: [
'x-pack/plugins/observability_solution/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)',
'src/plugins/ai_assistant_management/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)',
+ 'x-pack/packages/observability/logs_overview/**/!(*.stories.tsx|*.test.tsx|*.storybook_decorator.tsx|*.mock.tsx)',
],
rules: {
'@kbn/i18n/strings_should_be_translated_with_i18n': 'warn',
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index 7ff0f3e3ef766..f502bfa00b922 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -89,6 +89,9 @@
"xpack.observabilityLogsExplorer": "plugins/observability_solution/observability_logs_explorer",
"xpack.observability_onboarding": "plugins/observability_solution/observability_onboarding",
"xpack.observabilityShared": "plugins/observability_solution/observability_shared",
+ "xpack.observabilityLogsOverview": [
+ "packages/observability/logs_overview/src/components"
+ ],
"xpack.osquery": ["plugins/osquery"],
"xpack.painlessLab": "plugins/painless_lab",
"xpack.profiling": ["plugins/observability_solution/profiling"],
From b428bd45ed095e66c185f37a9f4a00382e71eb5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 2 Sep 2024 16:22:29 +0000
Subject: [PATCH 09/74] Improve typing of the sorting config
---
.../log_categories/log_categories_grid.tsx | 25 ++++++++++++++-----
.../log_categories_grid_cell.tsx | 4 ++-
2 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
index 995706bf12663..36a5cd95c1ba8 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
@@ -10,16 +10,18 @@ import {
EuiDataGridColumnSortingConfig,
EuiDataGridPaginationProps,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { createConsoleInspector } from '@kbn/xstate-utils';
import { useMachine } from '@xstate5/react';
+import _ from 'lodash';
import React, { useMemo } from 'react';
-import { i18n } from '@kbn/i18n';
import { assign, setup } from 'xstate5';
-import _ from 'lodash';
import { LogCategory } from '../../types';
import {
LogCategoriesGridCellDependencies,
+ LogCategoriesGridColumnId,
createCellContext,
+ logCategoriesGridColumnIds,
logCategoriesGridColumns,
renderLogCategoriesGridCell,
} from './log_categories_grid_cell';
@@ -49,10 +51,10 @@ export const LogCategoriesGrid: React.FC = ({
switch (id) {
case 'count':
return [(logCategory: LogCategory) => logCategory.documentCount, direction];
- case 'type':
+ case 'change_type':
// TODO: use better sorting weight for change types
return [(logCategory: LogCategory) => logCategory.change.type, direction];
- case 'change':
+ case 'change_time':
return [
(logCategory: LogCategory) =>
'timestamp' in logCategory.change ? logCategory.change.timestamp ?? '' : '',
@@ -101,7 +103,7 @@ const gridStateService = setup({
context: {} as {
visibleColumns: string[];
pagination: Pick;
- sortingColumns: EuiDataGridColumnSortingConfig[];
+ sortingColumns: LogCategoriesGridSortingConfig[];
},
events: {} as
| {
@@ -151,7 +153,10 @@ const gridStateService = setup({
},
changeSortingColumns: {
actions: assign(({ event }) => ({
- sortingColumns: event.sortingColumns,
+ sortingColumns: event.sortingColumns.filter(
+ (sortingConfig): sortingConfig is LogCategoriesGridSortingConfig =>
+ (logCategoriesGridColumnIds as string[]).includes(sortingConfig.id)
+ ),
})),
},
changeVisibleColumns: {
@@ -168,3 +173,11 @@ const logCategoriesGridLabel = i18n.translate(
'xpack.observabilityLogsOverview.logCategoriesGrid.euiDataGrid.logCategoriesLabel',
{ defaultMessage: 'Log categories' }
);
+
+interface TypedEuiDataGridColumnSortingConfig
+ extends EuiDataGridColumnSortingConfig {
+ id: ColumnId;
+}
+
+type LogCategoriesGridSortingConfig =
+ TypedEuiDataGridColumnSortingConfig;
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx
index 9862b00270076..d6ab4969eaf7b 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx
@@ -77,7 +77,9 @@ export const logCategoriesGridColumns = [
logCategoriesGridHistoryColumn,
] satisfies EuiDataGridColumn[];
-type LogCategoriesGridColumnId = (typeof logCategoriesGridColumns)[number]['id'];
+export const logCategoriesGridColumnIds = logCategoriesGridColumns.map(({ id }) => id);
+
+export type LogCategoriesGridColumnId = (typeof logCategoriesGridColumns)[number]['id'];
const cellContextKey = 'cellContext';
From 5be73af5003a937690556aa6bd59cd356d0cf9d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 2 Sep 2024 16:25:53 +0000
Subject: [PATCH 10/74] Improve default column widths
---
.../log_categories/log_categories_grid_change_time_cell.tsx | 2 +-
.../log_categories/log_categories_grid_change_type_cell.tsx | 2 +-
.../log_categories/log_categories_grid_count_cell.tsx | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx
index cd24e870f431b..5ad8cbdd49346 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_time_cell.tsx
@@ -21,7 +21,7 @@ export const logCategoriesGridChangeTimeColumn = {
}
),
isSortable: true,
- initialWidth: 200,
+ initialWidth: 220,
schema: 'datetime',
} satisfies EuiDataGridColumn;
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx
index a692983617069..af6349bd0e18c 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_change_type_cell.tsx
@@ -19,7 +19,7 @@ export const logCategoriesGridChangeTypeColumn = {
}
),
isSortable: true,
- initialWidth: 130,
+ initialWidth: 110,
} satisfies EuiDataGridColumn;
export interface LogCategoriesGridChangeTypeCellProps {
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx
index 4b5d8e0179e07..f2247aab5212e 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_count_cell.tsx
@@ -18,7 +18,7 @@ export const logCategoriesGridCountColumn = {
}),
isSortable: true,
schema: 'numeric',
- initialWidth: 120,
+ initialWidth: 100,
} satisfies EuiDataGridColumn;
export interface LogCategoriesGridCountCellProps {
From e0af1bb2d6a3c7b7133af3a9cd243df5747d07d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 2 Sep 2024 19:09:00 +0000
Subject: [PATCH 11/74] Add loading screen with cancellation
---
.../log_categories/log_categories.tsx | 28 ++++++--
.../log_categories/log_categories_grid.tsx | 1 -
.../logs_categories_loading_content.tsx | 59 ++++++++++++++-
.../categorize_logs_service.ts | 72 +++++++++++++++++--
4 files changed, 147 insertions(+), 13 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
index 1de48f74f97a8..7256dda0115ad 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
@@ -8,7 +8,8 @@
import { ISearchGeneric } from '@kbn/search-types';
import { createConsoleInspector } from '@kbn/xstate-utils';
import { useMachine } from '@xstate5/react';
-import React from 'react';
+import React, { useCallback } from 'react';
+import { i18n } from '@kbn/i18n';
import {
categorizeLogsService,
createCategorizeLogsServiceImplementations,
@@ -28,7 +29,7 @@ export type LogCategoriesDependencies = LogCategoriesResultContentDependencies &
};
export const LogCategories: React.FC = ({ dependencies }) => {
- const [categorizeLogsServiceState, _sendToCategorizeLogsService] = useMachine(
+ const [categorizeLogsServiceState, sendToCategorizeLogsService] = useMachine(
categorizeLogsService.provide(
createCategorizeLogsServiceImplementations({ search: dependencies.search })
),
@@ -44,6 +45,12 @@ export const LogCategories: React.FC = ({ dependencies }) =>
}
);
+ const cancelOperation = useCallback(() => {
+ sendToCategorizeLogsService({
+ type: 'cancel',
+ });
+ }, [sendToCategorizeLogsService]);
+
if (categorizeLogsServiceState.matches('done')) {
return (
= ({ dependencies }) =>
/>
);
} else if (categorizeLogsServiceState.matches('failed')) {
- return Error
;
+ return (
+
+ {i18n.translate('xpack.observabilityLogsOverview.logCategories.div.errorLabel', {
+ defaultMessage: 'Error',
+ })}
+
+ );
+ } else if (categorizeLogsServiceState.matches('countingDocuments')) {
+ return ;
+ } else if (
+ categorizeLogsServiceState.matches('fetchingSampledCategories') ||
+ categorizeLogsServiceState.matches('fetchingRemainingCategories')
+ ) {
+ return ;
} else {
- return ;
+ return null;
}
};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
index 36a5cd95c1ba8..d9e960685de99 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
@@ -44,7 +44,6 @@ export const LogCategoriesGrid: React.FC = ({
inspect: consoleInspector,
});
- // TODO: sort data based on sorting columns
const sortedLogCategories = useMemo(() => {
const sortingCriteria = gridState.context.sortingColumns.map(
({ id, direction }): [(logCategory: LogCategory) => any, 'asc' | 'desc'] => {
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx
index 3cf5240b7bb3c..3e8ac31eaddfc 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx
@@ -5,8 +5,63 @@
* 2.0.
*/
+import { EuiButton, EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui';
import React from 'react';
+import { i18n } from '@kbn/i18n';
-export const LogCategoriesLoadingContent: React.FC = () => {
- return Loading...
;
+export interface LogCategoriesLoadingContentProps {
+ onCancel?: () => void;
+ stage: 'counting' | 'categorizing';
+}
+
+export const LogCategoriesLoadingContent: React.FC = ({
+ onCancel,
+ stage,
+}) => {
+ return (
+ }
+ title={
+
+ {stage === 'counting'
+ ? logCategoriesLoadingStateCountingTitle
+ : logCategoriesLoadingStateCategorizingTitle}
+
+ }
+ actions={
+ onCancel != null
+ ? [
+ {
+ onCancel();
+ }}
+ >
+ {logCategoriesLoadingStateCancelButtonLabel}
+ ,
+ ]
+ : []
+ }
+ />
+ );
};
+
+const logCategoriesLoadingStateCountingTitle = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoriesGrid.loadingStageCountingTitle',
+ {
+ defaultMessage: 'Estimating log volume',
+ }
+);
+
+const logCategoriesLoadingStateCategorizingTitle = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoriesGrid.loadingStageCategorizingTitle',
+ {
+ defaultMessage: 'Categorizing logs',
+ }
+);
+
+const logCategoriesLoadingStateCancelButtonLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoriesGrid.loadingStateCancelButtonLabel',
+ {
+ defaultMessage: 'Cancel',
+ }
+);
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
index 8c10efa6c6567..0189a51942572 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
@@ -27,14 +27,17 @@ export const categorizeLogsService = setup({
samplingProbability: number;
error?: Error;
},
+ events: {} as {
+ type: 'cancel';
+ },
},
actors: {
countDocuments: getPlaceholderFor(countDocuments),
categorizeDocuments: getPlaceholderFor(categorizeDocuments),
},
actions: {
- storeError: assign((_, params: { error: Error }) => ({
- error: params.error,
+ storeError: assign((_, params: { error: unknown }) => ({
+ error: params.error instanceof Error ? params.error : new Error(String(params.error)),
})),
storeCategories: assign(({ context }, params: { categories: LogCategory[] }) => ({
categories: [...context.categories, ...params.categories],
@@ -50,7 +53,7 @@ export const categorizeLogsService = setup({
requiresSampling: ({ context }) => context.samplingProbability < 1,
},
}).createMachine({
- /** @xstate-layout N4IgpgJg5mDOIC5QGMCGAXMUD2AnAlgF5gAy2UsAdMtgK4B26+9UAItsrQLZiOwDEEbPTCVmAN2wBrUWkw4CxMhWp1GzNh2690sBBI4Z8wgNoAGALrmLiUAAdssfE2G2QAD0QAmABw-KACw+AQDMAOwAbGEAnGFmPgA0IACe3gFmlD7RIV4AjPlm6SFmXtEAvmVJclh4RKTkVDQMTCzsnDx8gsKiBjLUGDWK9SpN6q1aHbr69JJyxvTWJrk2SCAOTi70bp4Ivv5BoZExcYkpiD65lACsZrchUV5mEeFmIRVVAwp1yo1qLZrtHQCMC4XB4Sh2AA2GAAZnguP15LUlA1VM0NG1tHxprMjKZLNY3OtnPNtt4-IFguEorF4klUghQldKDSwmF7hEsmEvFc3pUQNUviiVDCwOhkAALDQAZVQXChkAAwp9anAuiIxDNpLIVUNZfLIUrdfg4ITVsTNmSELkzFdmWZsmzObkrukAld6d4vCFApEIrkwj5OT57gH3gLjcKqKLxVKWPqFRBlUiCGqQWDcBDoeg4bgEYLkWAE4ak8bTZYiY4Sa5VjsbXbKA7wpELq7Ch6zggQrkIoFShFSiVCo8IuGC0MfpQY5KNAAlMBcVDMDTJwYmgRCDW9HUpurzxfLlirr7llb2KuW2uIAMBXtNiLPHxXYIXLyehA+LyUEI-l62kK0vcY6RsM0ZijOLD7ku9ArmWwKguCUKwvCiJrsQUGHlAx6qrAZrnhspJXtaYS3o22QPiET4vrkb6dgGzK+BEvLejc9xBhU-L0NgEBwG447fA0lYETWoA7AAtBE74ScBu5RmiYwAliuhCdWWxEQEtEMjalzaREGncl4jwOj4MloaBU7gXGUDFkau68eaF6EaJiBMdEZHNmY7LekE76GW5-qxFRpQaQOfIfLJ5nTlZGEwUecEqZeznWiENzueyP4BIG3YBO+FKZQ+uQpUG0RRClplCpFS4lglTkeN4tqXF4TFXC6eQxNEtq+R1LIxAOHWZSVkTlYWk6bmANUiXVuwNZQTV2q1AbRB1HZaSUmQOtEvKUb40TpLkHFlEAA */
+ /** @xstate-layout N4IgpgJg5mDOIC5QGMCGAXMUD2AnAlgF5gAy2UsAdMtgK4B26+9UAItsrQLZiOwDEEbPTCVmAN2wBrUWkw4CxMhWp1GzNh2690sBBI4Z8wgNoAGALrmLiUAAdssfE2G2QAD0QAmAJwAOSgAWP0CAZgA2M0CzAEZwgFYYgHYkgBoQAE9EJK8vIPCYsy8k+K94pJ8YmIBfavS5LDwiUnIqGgYmFnZOHj5BYVEDGWoMRsUWlXb1Lq1e3X16STljemsTGJskEAcnF3o3TwRfAOCwyOi4xJT0rKPQmMpQxPifJNCkyLK-cNr60YVmso2mpOpoejoBGBcLg8JQ7AAbDAAMzwXBG8iaSlaqg6Gm62j4CyWRlMlmsbh2zhWB28-iCIQiUViCWSaUyiD8D3iZh5MS8kSiXhilV+IAaAKxkxBeNmEP4aHoyDA8PJW0pexpRzpp0ZFxZ13ZCHipUofhewQqKXCXkCovFmImVCRYHQyAAFhoAMqoLgIyAAYX+TTg-REYkW0lkQfG3t98ID0fwcFV9kcVNcW0OhRKlDMPneZktPjKBtu4R84UejNCXh5SUCQqSdsTkqdLvdXp9foggYxBBDUJhuDhiPQKNwaPtMa78Z7ieTlgpaY1mcQ2fiufzSULr2L5TZtySfh8lC8x9rgXigXCoT8T2bfcB2Odro9LFj3d7YyTAgVSpVi5qsu1Krgg66bgWRYlgeHJ+HkNpeO825mqEl5eA+36tpQL4diwABKYBcKgzAaF+AIhkIYZDFGj7EARREkSwZHBrAKbbMBGagFmVoQeECShO8No1jc3iIUEHwvH43w+D4RT1hhEqOth7ZvlA9HEfQpHzpC0KwgiyKouimFgOpjFQMx-asYBqa7CBXFrjxeYRPxglhF4IkIIEfKPHBZo+NeISRDUdRii2Sk4appmaUx2nyqgirKmx6p2R4DkfLxLk5G5Hn3JWDavEkMTRFJ4SBGVtQhfQ2AQHAbhTk+FBLrZnGpQgAC04QeW1G4yb1fX9aECkOkCOLTGCBK6E16b7KBDYeYUG7vAkZplXmHzFEN4wjRFnZxgmj61UBzUzfZCB8SeTk5DEJXHJ1hqFQ813rcU8QoQJm0NW2r4aFFWkHfAR3TZqMQFhBJQNreDY2h5iEBDWnLHtahRFRtIX1VhSLEbOU0rqdQqxKeCTXgkiR+EUHlnJQRWJNEr104W8QfVhlFgDjKWHPjDz8lefHGtd5OGkJlA+PENbvJekR3vcFXVEAA */
id: 'categorizeLogs',
context: ({ input }) => ({
categories: [],
@@ -87,7 +90,24 @@ export const categorizeLogsService = setup({
],
onError: {
target: 'failed',
- actions: [],
+ actions: [
+ {
+ type: 'storeError',
+ params: ({ event }) => ({ error: event.error }),
+ },
+ ],
+ },
+ },
+
+ on: {
+ cancel: {
+ target: 'failed',
+ actions: [
+ {
+ type: 'storeError',
+ params: () => ({ error: new Error('Counting cancelled') }),
+ },
+ ],
},
},
},
@@ -111,7 +131,27 @@ export const categorizeLogsService = setup({
},
],
},
- onError: 'failed',
+ onError: {
+ target: 'failed',
+ actions: [
+ {
+ type: 'storeError',
+ params: ({ event }) => ({ error: event.error }),
+ },
+ ],
+ },
+ },
+
+ on: {
+ cancel: {
+ target: 'failed',
+ actions: [
+ {
+ type: 'storeError',
+ params: () => ({ error: new Error('Categorization cancelled') }),
+ },
+ ],
+ },
},
},
@@ -134,7 +174,27 @@ export const categorizeLogsService = setup({
},
],
},
- onError: 'failed',
+ onError: {
+ target: 'failed',
+ actions: [
+ {
+ type: 'storeError',
+ params: ({ event }) => ({ error: event.error }),
+ },
+ ],
+ },
+ },
+
+ on: {
+ cancel: {
+ target: 'failed',
+ actions: [
+ {
+ type: 'storeError',
+ params: () => ({ error: new Error('Categorization cancelled') }),
+ },
+ ],
+ },
},
},
From 169df2322d0ef7701076f3269d18f3bf860488bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Tue, 3 Sep 2024 13:15:57 +0000
Subject: [PATCH 12/74] Bump XState 5 version
---
package.json | 4 ++--
yarn.lock | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package.json b/package.json
index b5434b113370a..401c3d01be909 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
"**/sharp": "0.32.6",
"**/typescript": "5.1.6",
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0",
- "@xstate5/react/**/xstate": "^5.17.4",
+ "@xstate5/react/**/xstate": "^5.18.0",
"globby/fast-glob": "^3.2.11"
},
"dependencies": {
@@ -1259,7 +1259,7 @@
"whatwg-fetch": "^3.0.0",
"xml2js": "^0.5.0",
"xstate": "^4.38.2",
- "xstate5": "npm:xstate@^5.17.4",
+ "xstate5": "npm:xstate@^5.18.0",
"xterm": "^5.1.0",
"yauzl": "^2.10.0",
"yazl": "^2.5.1",
diff --git a/yarn.lock b/yarn.lock
index 31c26e455c310..9370125107940 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -32557,10 +32557,10 @@ xpath@^0.0.33:
resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.33.tgz#5136b6094227c5df92002e7c3a13516a5074eb07"
integrity sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==
-"xstate5@npm:xstate@^5.17.4", xstate@^5.17.4:
- version "5.17.4"
- resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.17.4.tgz#334ab2da123973634097f7ca48387ae1589c774e"
- integrity sha512-KM2FYVOUJ04HlOO4TY3wEXqoYPR/XsDu+ewm+IWw0vilXqND0jVfvv04tEFwp8Mkk7I/oHXM8t1Ex9xJyUS4ZA==
+"xstate5@npm:xstate@^5.18.0", xstate@^5.18.0:
+ version "5.18.0"
+ resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.18.0.tgz#6f559301dc2e1e4db39642ac5591bb7eb005335b"
+ integrity sha512-MKlq/jhyFBYm6Z9+P0k9nhMrHYTTg1ZGmhMw8tVe67oDq9nIlEf2/u/bY5kvUvqu4LTCiVl67hnfd92RMLRyVg==
xstate@^4.38.2:
version "4.38.2"
From 901bee3c4af1876891d85c2c9a3c688fb8284b79 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Tue, 3 Sep 2024 17:31:41 +0000
Subject: [PATCH 13/74] Start cleaning up names and introducing the overview
component
---
.../log_categories/log_categories.tsx | 43 ++++++++++-----
...tsx => log_categories_loading_content.tsx} | 0
....tsx => log_categories_result_content.tsx} | 0
.../src/components/logs_overview/index.ts | 8 +++
.../logs_overview/logs_overview.tsx | 20 +++++++
.../categorize_documents.ts | 8 +--
.../categorize_logs_service.ts | 6 +-
.../count_documents.ts | 8 +--
.../services/categorize_logs_service/types.ts | 8 +--
.../logs_overview/utils/logs_source.ts | 55 +++++++++++++++++++
.../components/app/service_logs/index.tsx | 18 +++---
.../routing/service_detail/index.tsx | 2 +-
12 files changed, 139 insertions(+), 37 deletions(-)
rename x-pack/packages/observability/logs_overview/src/components/log_categories/{logs_categories_loading_content.tsx => log_categories_loading_content.tsx} (100%)
rename x-pack/packages/observability/logs_overview/src/components/log_categories/{logs_categories_result_content.tsx => log_categories_result_content.tsx} (100%)
create mode 100644 x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
create mode 100644 x-pack/packages/observability/logs_overview/utils/logs_source.ts
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
index 7256dda0115ad..573822acebf2e 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
@@ -5,43 +5,60 @@
* 2.0.
*/
+import { i18n } from '@kbn/i18n';
import { ISearchGeneric } from '@kbn/search-types';
import { createConsoleInspector } from '@kbn/xstate-utils';
import { useMachine } from '@xstate5/react';
-import React, { useCallback } from 'react';
-import { i18n } from '@kbn/i18n';
+import React, { useCallback, useMemo } from 'react';
+import { LogsSourceConfiguration, normalizeLogsSource } from '../../../utils/logs_source';
import {
categorizeLogsService,
createCategorizeLogsServiceImplementations,
} from '../../services/categorize_logs_service';
-import { LogCategoriesLoadingContent } from './logs_categories_loading_content';
+import { LogCategoriesLoadingContent } from './log_categories_loading_content';
import {
LogCategoriesResultContent,
LogCategoriesResultContentDependencies,
-} from './logs_categories_result_content';
+} from './log_categories_result_content';
-interface LogCategoriesProps {
+export interface LogCategoriesProps {
dependencies: LogCategoriesDependencies;
+ logsSource: LogsSourceConfiguration;
+ // The time range could be made optional if we want to support an internal
+ // time range picker
+ timeRange: {
+ start: string;
+ end: string;
+ };
}
export type LogCategoriesDependencies = LogCategoriesResultContentDependencies & {
search: ISearchGeneric;
};
-export const LogCategories: React.FC = ({ dependencies }) => {
+export const LogCategories: React.FC = ({
+ dependencies,
+ logsSource,
+ timeRange,
+}) => {
+ const categorizeLogsServiceInput = useMemo(() => {
+ const normalizedLogsSource = normalizeLogsSource(logsSource);
+ return {
+ index: normalizedLogsSource.indexName,
+ startTimestamp: timeRange.start,
+ endTimestamp: timeRange.end,
+ timeField: normalizedLogsSource.timestampField,
+ messageField: normalizedLogsSource.messageField,
+ };
+ }, [logsSource, timeRange.end, timeRange.start]);
+
const [categorizeLogsServiceState, sendToCategorizeLogsService] = useMachine(
categorizeLogsService.provide(
createCategorizeLogsServiceImplementations({ search: dependencies.search })
),
{
inspect: consoleInspector,
- input: {
- index: 'logs-*-*',
- start: '2024-12-01T00:00:00.000Z',
- end: '2024-12-03T00:00:00.000Z',
- timeField: '@timestamp',
- messageField: 'message',
- },
+ input: categorizeLogsServiceInput,
}
);
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_loading_content.tsx
similarity index 100%
rename from x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_loading_content.tsx
rename to x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_loading_content.tsx
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_result_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
similarity index 100%
rename from x-pack/packages/observability/logs_overview/src/components/log_categories/logs_categories_result_content.tsx
rename to x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts b/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts
new file mode 100644
index 0000000000000..627cdc8447eea
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts
@@ -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 './logs_overview';
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
new file mode 100644
index 0000000000000..09f26a76587b7
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
@@ -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 React from 'react';
+import { LogCategories, LogCategoriesDependencies } from '../log_categories';
+
+export interface LogsOverviewProps {
+ dependencies: LogsOverviewDependencies;
+}
+
+export type LogsOverviewDependencies = LogCategoriesDependencies;
+
+export const LogsOverview: React.FC = ({ dependencies }) => {
+ // TODO: pass the right props
+ return ;
+};
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
index 0d440621d7241..c95c874cd0132 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
@@ -10,7 +10,7 @@ import { lastValueFrom } from 'rxjs';
import { fromPromise } from 'xstate5';
import { createRandomSamplerWrapper } from '@kbn/ml-random-sampler-utils';
import { z } from '@kbn/zod';
-import { LogsCategorizationParams } from './types';
+import { LogCategorizationParams } from './types';
import { createCategorizationRequestParams } from './queries';
import { LogCategory, LogCategoryChange } from '../../types';
@@ -22,7 +22,7 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
{
categories: LogCategory[];
},
- LogsCategorizationParams & {
+ LogCategorizationParams & {
samplingProbability: number;
ignoredQueries?: string[];
minDocsPerCategory?: number;
@@ -31,8 +31,8 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
async ({
input: {
index,
- end,
- start,
+ endTimestamp,
+ startTimestamp,
timeField,
messageField,
samplingProbability,
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
index 0189a51942572..6c94215ce8b4d 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
@@ -9,12 +9,12 @@ import { MachineImplementationsFrom, assign, setup } from 'xstate5';
import { getPlaceholderFor } from '../../../utils/xstate5_utils';
import { categorizeDocuments } from './categorize_documents';
import { countDocuments } from './count_documents';
-import { CategorizeLogsServiceDependencies, LogsCategorizationParams } from './types';
+import { CategorizeLogsServiceDependencies, LogCategorizationParams } from './types';
import { LogCategory } from '../../types';
export const categorizeLogsService = setup({
types: {
- input: {} as LogsCategorizationParams,
+ input: {} as LogCategorizationParams,
output: {} as {
categories: LogCategory[];
documentCount: number;
@@ -23,7 +23,7 @@ export const categorizeLogsService = setup({
context: {} as {
categories: LogCategory[];
documentCount: number;
- parameters: LogsCategorizationParams;
+ parameters: LogCategorizationParams;
samplingProbability: number;
error?: Error;
},
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts
index e231745b8c88d..241e1158c7349 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts
@@ -9,7 +9,7 @@ import { getSampleProbability } from '@kbn/ml-random-sampler-utils';
import { ISearchGeneric } from '@kbn/search-types';
import { lastValueFrom } from 'rxjs';
import { fromPromise } from 'xstate5';
-import { LogsCategorizationParams } from './types';
+import { LogCategorizationParams } from './types';
import { createCategorizationQuery } from './queries';
export const countDocuments = ({ search }: { search: ISearchGeneric }) =>
@@ -18,8 +18,8 @@ export const countDocuments = ({ search }: { search: ISearchGeneric }) =>
documentCount: number;
samplingProbability: number;
},
- LogsCategorizationParams
- >(async ({ input: { index, end, start, timeField, messageField }, signal }) => {
+ LogCategorizationParams
+ >(async ({ input: { index, endTimestamp, startTimestamp, timeField, messageField }, signal }) => {
const { rawResponse: totalHitsResponse } = await lastValueFrom(
search(
{
@@ -27,7 +27,7 @@ export const countDocuments = ({ search }: { search: ISearchGeneric }) =>
index,
size: 0,
track_total_hits: true,
- query: createCategorizationQuery(messageField, timeField, start, end),
+ query: createCategorizationQuery(messageField, timeField, startTimestamp, endTimestamp),
},
},
{ abortSignal: signal }
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts
index c812b1b5aac3e..a1e4a72fbfd56 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts
@@ -10,10 +10,10 @@ export interface CategorizeLogsServiceDependencies {
search: ISearchGeneric;
}
-export interface LogsCategorizationParams {
- start: string;
- end: string;
+export interface LogCategorizationParams {
+ endTimestamp: string;
index: string;
- timeField: string;
messageField: string;
+ startTimestamp: string;
+ timeField: string;
}
diff --git a/x-pack/packages/observability/logs_overview/utils/logs_source.ts b/x-pack/packages/observability/logs_overview/utils/logs_source.ts
new file mode 100644
index 0000000000000..80cb097391e9a
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/utils/logs_source.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 AbstractDataView } from '@kbn/data-views-plugin/common';
+
+export type LogsSourceConfiguration =
+ | AdvancedSettingLogsSourceConfiguration
+ | IndexNameLogsSourceConfiguration
+ | DataViewLogsSourceConfiguration;
+
+export interface AdvancedSettingLogsSourceConfiguration {
+ type: 'advanced_setting';
+ timestampField?: string;
+ messageField?: string;
+}
+
+export interface IndexNameLogsSourceConfiguration {
+ type: 'index_name';
+ indexName: string;
+ timestampField: string;
+ messageField: string;
+}
+
+export interface DataViewLogsSourceConfiguration {
+ type: 'data_view';
+ dataView: AbstractDataView;
+ messageField?: string;
+}
+
+export const normalizeLogsSource = (
+ logsSource: LogsSourceConfiguration
+): IndexNameLogsSourceConfiguration => {
+ switch (logsSource.type) {
+ case 'index_name':
+ return logsSource;
+ case 'advanced_setting':
+ return {
+ type: 'index_name',
+ indexName: '', // TODO: get index name from advanced settings
+ timestampField: logsSource.timestampField ?? '@timestamp',
+ messageField: logsSource.messageField ?? 'message',
+ };
+ case 'data_view':
+ return {
+ type: 'index_name',
+ indexName: logsSource.dataView.getIndexPattern(),
+ timestampField: logsSource.dataView.timeFieldName ?? '@timestamp',
+ messageField: logsSource.messageField ?? 'message',
+ };
+ }
+};
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
index 16cd84babf227..5d4e7725409b6 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
@@ -9,11 +9,11 @@ import React, { useMemo } from 'react';
import { LogCategories } from '@kbn/observability-logs-overview';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { CONTAINER_ID, SERVICE_ENVIRONMENT, SERVICE_NAME } from '../../../../common/es_fields/apm';
-// import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
+import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useKibana } from '../../../context/kibana_context/use_kibana';
-// import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
+import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
// import { useFetcher } from '../../../hooks/use_fetcher';
-// import { useTimeRange } from '../../../hooks/use_time_range';
+import { useTimeRange } from '../../../hooks/use_time_range';
import { APIReturnType } from '../../../services/rest/create_call_apm_api';
export function ServiceLogs() {
@@ -26,7 +26,11 @@ export function ServiceLogs() {
settings: uiSettings,
},
} = useKibana();
- // const { serviceName } = useApmServiceContext();
+ const { serviceName } = useApmServiceContext();
+ const {
+ query: { environment, kuery, rangeFrom, rangeTo },
+ } = useAnyOfApmParams('/services/{serviceName}/logs');
+ const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const logCategoriesDependencies = useMemo(
() => ({
@@ -42,10 +46,6 @@ export function ServiceLogs() {
return null;
}
- // const {
- // query: { environment, kuery, rangeFrom, rangeTo },
- // } = useAnyOfApmParams('/services/{serviceName}/logs', '/logs-services/{serviceName}/logs');
-
// const { start, end } = useTimeRange({ rangeFrom, rangeTo });
// const { data } = useFetcher(
@@ -67,6 +67,8 @@ export function ServiceLogs() {
// [environment, kuery, serviceName, start, end]
// );
+ // TODO: filter by service name and environment
+ // TODO: filter by time
return ;
// return (
diff --git a/x-pack/plugins/observability_solution/apm/public/components/routing/service_detail/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/routing/service_detail/index.tsx
index 024af3bdd79a4..c431351f51398 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/routing/service_detail/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/routing/service_detail/index.tsx
@@ -308,7 +308,7 @@ export const serviceDetailRoute = {
}),
element: ,
searchBarOptions: {
- showUnifiedSearchBar: false,
+ showQueryInput: false,
},
}),
'/services/{serviceName}/infrastructure': {
From 1023d69a6db65f74ac8fbfffa91e5cd74d357fed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Wed, 4 Sep 2024 17:48:03 +0000
Subject: [PATCH 14/74] Provide a self-contained overview via logs_shared
---
.../observability/logs_overview/index.ts | 12 +++-
.../log_categories/log_categories.tsx | 25 ++++----
.../log_categories_loading_content.tsx | 1 +
.../logs_overview/logs_overview.tsx | 42 +++++++++++--
.../logs_overview_error_content.tsx | 27 +++++++++
.../logs_overview_loading_content.tsx | 23 +++++++
.../categorize_logs_service.ts | 2 +-
.../logs_overview/src/utils/logs_source.ts | 60 +++++++++++++++++++
.../{ => src}/utils/xstate5_utils.ts | 0
.../observability/logs_overview/tsconfig.json | 15 ++++-
.../logs_overview/utils/logs_source.ts | 55 -----------------
.../components/app/service_logs/index.tsx | 31 ++++------
.../apm/public/plugin.ts | 2 +
.../logs_shared/kibana.jsonc | 6 +-
.../public/components/logs_overview.tsx | 24 ++++++++
.../logs_shared/public/index.ts | 1 +
.../logs_shared/public/plugin.ts | 20 ++++++-
.../logs_shared/public/types.ts | 14 +++--
18 files changed, 254 insertions(+), 106 deletions(-)
create mode 100644 x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_loading_content.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/utils/logs_source.ts
rename x-pack/packages/observability/logs_overview/{ => src}/utils/xstate5_utils.ts (100%)
delete mode 100644 x-pack/packages/observability/logs_overview/utils/logs_source.ts
create mode 100644 x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx
diff --git a/x-pack/packages/observability/logs_overview/index.ts b/x-pack/packages/observability/logs_overview/index.ts
index b65160ca19875..e937c4fbc3fea 100644
--- a/x-pack/packages/observability/logs_overview/index.ts
+++ b/x-pack/packages/observability/logs_overview/index.ts
@@ -5,4 +5,14 @@
* 2.0.
*/
-export { LogCategories } from './src/components/log_categories';
+export {
+ LogsOverview,
+ type LogsOverviewDependencies,
+ type LogsOverviewProps,
+} from './src/components/logs_overview';
+export type {
+ DataViewLogsSourceConfiguration,
+ IndexNameLogsSourceConfiguration,
+ LogsSourceConfiguration,
+ SharedSettingLogsSourceConfiguration,
+} from './src/utils/logs_source';
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
index 573822acebf2e..fe5828d1a97f1 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
@@ -9,12 +9,12 @@ import { i18n } from '@kbn/i18n';
import { ISearchGeneric } from '@kbn/search-types';
import { createConsoleInspector } from '@kbn/xstate-utils';
import { useMachine } from '@xstate5/react';
-import React, { useCallback, useMemo } from 'react';
-import { LogsSourceConfiguration, normalizeLogsSource } from '../../../utils/logs_source';
+import React, { useCallback } from 'react';
import {
categorizeLogsService,
createCategorizeLogsServiceImplementations,
} from '../../services/categorize_logs_service';
+import { IndexNameLogsSourceConfiguration } from '../../utils/logs_source';
import { LogCategoriesLoadingContent } from './log_categories_loading_content';
import {
LogCategoriesResultContent,
@@ -23,7 +23,7 @@ import {
export interface LogCategoriesProps {
dependencies: LogCategoriesDependencies;
- logsSource: LogsSourceConfiguration;
+ logsSource: IndexNameLogsSourceConfiguration;
// The time range could be made optional if we want to support an internal
// time range picker
timeRange: {
@@ -41,24 +41,19 @@ export const LogCategories: React.FC = ({
logsSource,
timeRange,
}) => {
- const categorizeLogsServiceInput = useMemo(() => {
- const normalizedLogsSource = normalizeLogsSource(logsSource);
- return {
- index: normalizedLogsSource.indexName,
- startTimestamp: timeRange.start,
- endTimestamp: timeRange.end,
- timeField: normalizedLogsSource.timestampField,
- messageField: normalizedLogsSource.messageField,
- };
- }, [logsSource, timeRange.end, timeRange.start]);
-
const [categorizeLogsServiceState, sendToCategorizeLogsService] = useMachine(
categorizeLogsService.provide(
createCategorizeLogsServiceImplementations({ search: dependencies.search })
),
{
inspect: consoleInspector,
- input: categorizeLogsServiceInput,
+ input: {
+ index: logsSource.indexName,
+ startTimestamp: timeRange.start,
+ endTimestamp: timeRange.end,
+ timeField: logsSource.timestampField,
+ messageField: logsSource.messageField,
+ },
}
);
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_loading_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_loading_content.tsx
index 3e8ac31eaddfc..0fde469fe717d 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_loading_content.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_loading_content.tsx
@@ -32,6 +32,7 @@ export const LogCategoriesLoadingContent: React.FC {
onCancel();
}}
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
index 09f26a76587b7..721f40ff2fa45 100644
--- a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
@@ -5,16 +5,50 @@
* 2.0.
*/
+import { type LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
import React from 'react';
+import useAsync from 'react-use/lib/useAsync';
+import { LogsSourceConfiguration, normalizeLogsSource } from '../../utils/logs_source';
import { LogCategories, LogCategoriesDependencies } from '../log_categories';
+import { LogsOverviewLoadingContent } from './logs_overview_loading_content';
export interface LogsOverviewProps {
dependencies: LogsOverviewDependencies;
+ logsSource?: LogsSourceConfiguration;
+ timeRange: {
+ start: string;
+ end: string;
+ };
}
-export type LogsOverviewDependencies = LogCategoriesDependencies;
+export type LogsOverviewDependencies = LogCategoriesDependencies & {
+ logsDataAccess: LogsDataAccessPluginStart;
+};
+
+export const LogsOverview: React.FC = ({
+ dependencies,
+ logsSource = { type: 'shared_setting' },
+ timeRange,
+}) => {
+ const normalizedLogsSource = useAsync(
+ () => normalizeLogsSource({ logsDataAccess: dependencies.logsDataAccess })(logsSource),
+ [dependencies.logsDataAccess, logsSource]
+ );
+
+ if (normalizedLogsSource.loading) {
+ return ;
+ }
+
+ if (normalizedLogsSource.error != null || normalizedLogsSource.value == null) {
+ // eslint-disable-next-line @kbn/i18n/strings_should_be_translated_with_i18n
+ return <>Error>;
+ }
-export const LogsOverview: React.FC = ({ dependencies }) => {
- // TODO: pass the right props
- return ;
+ return (
+
+ );
};
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx
new file mode 100644
index 0000000000000..2f76384d66962
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx
@@ -0,0 +1,27 @@
+/*
+ * 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 { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+
+export interface LogsOverviewErrorContentProps {
+ error: Error;
+}
+
+export const LogsOverviewErrorContent: React.FC = ({}) => {
+ return (
+ }
+ title={{logsOverviewErrorTitle}
}
+ />
+ );
+};
+
+const logsOverviewErrorTitle = i18n.translate('xpack.observabilityLogsOverview.errorTitle', {
+ defaultMessage: 'Error',
+});
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_loading_content.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_loading_content.tsx
new file mode 100644
index 0000000000000..7645fdb90f0ac
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_loading_content.tsx
@@ -0,0 +1,23 @@
+/*
+ * 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 { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+
+export const LogsOverviewLoadingContent: React.FC = ({}) => {
+ return (
+ }
+ title={{logsOverviewLoadingTitle}
}
+ />
+ );
+};
+
+const logsOverviewLoadingTitle = i18n.translate('xpack.observabilityLogsOverview.loadingTitle', {
+ defaultMessage: 'Loading',
+});
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
index 6c94215ce8b4d..eb3f3b4e551f4 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
@@ -6,7 +6,7 @@
*/
import { MachineImplementationsFrom, assign, setup } from 'xstate5';
-import { getPlaceholderFor } from '../../../utils/xstate5_utils';
+import { getPlaceholderFor } from '../../utils/xstate5_utils';
import { categorizeDocuments } from './categorize_documents';
import { countDocuments } from './count_documents';
import { CategorizeLogsServiceDependencies, LogCategorizationParams } from './types';
diff --git a/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts b/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts
new file mode 100644
index 0000000000000..0c8767c8702d4
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts
@@ -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 AbstractDataView } from '@kbn/data-views-plugin/common';
+import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
+
+export type LogsSourceConfiguration =
+ | SharedSettingLogsSourceConfiguration
+ | IndexNameLogsSourceConfiguration
+ | DataViewLogsSourceConfiguration;
+
+export interface SharedSettingLogsSourceConfiguration {
+ type: 'shared_setting';
+ timestampField?: string;
+ messageField?: string;
+}
+
+export interface IndexNameLogsSourceConfiguration {
+ type: 'index_name';
+ indexName: string;
+ timestampField: string;
+ messageField: string;
+}
+
+export interface DataViewLogsSourceConfiguration {
+ type: 'data_view';
+ dataView: AbstractDataView;
+ messageField?: string;
+}
+
+export const normalizeLogsSource =
+ ({ logsDataAccess }: { logsDataAccess: LogsDataAccessPluginStart }) =>
+ async (logsSource: LogsSourceConfiguration): Promise => {
+ switch (logsSource.type) {
+ case 'index_name':
+ return logsSource;
+ case 'shared_setting':
+ const logSourcesFromSharedSettings =
+ await logsDataAccess.services.logSourcesService.getLogSources();
+ return {
+ type: 'index_name',
+ indexName: logSourcesFromSharedSettings
+ .map((logSource) => logSource.indexPattern)
+ .join(','),
+ timestampField: logsSource.timestampField ?? '@timestamp',
+ messageField: logsSource.messageField ?? 'message',
+ };
+ case 'data_view':
+ return {
+ type: 'index_name',
+ indexName: logsSource.dataView.getIndexPattern(),
+ timestampField: logsSource.dataView.timeFieldName ?? '@timestamp',
+ messageField: logsSource.messageField ?? 'message',
+ };
+ }
+ };
diff --git a/x-pack/packages/observability/logs_overview/utils/xstate5_utils.ts b/x-pack/packages/observability/logs_overview/src/utils/xstate5_utils.ts
similarity index 100%
rename from x-pack/packages/observability/logs_overview/utils/xstate5_utils.ts
rename to x-pack/packages/observability/logs_overview/src/utils/xstate5_utils.ts
diff --git a/x-pack/packages/observability/logs_overview/tsconfig.json b/x-pack/packages/observability/logs_overview/tsconfig.json
index 476238cbafcc3..37fb31cdc68c2 100644
--- a/x-pack/packages/observability/logs_overview/tsconfig.json
+++ b/x-pack/packages/observability/logs_overview/tsconfig.json
@@ -18,5 +18,18 @@
"exclude": [
"target/**/*"
],
- "kbn_references": []
+ "kbn_references": [
+ "@kbn/data-views-plugin",
+ "@kbn/i18n",
+ "@kbn/search-types",
+ "@kbn/xstate-utils",
+ "@kbn/core-ui-settings-browser",
+ "@kbn/i18n-react",
+ "@kbn/charts-plugin",
+ "@kbn/utility-types",
+ "@kbn/logs-data-access-plugin",
+ "@kbn/ml-random-sampler-utils",
+ "@kbn/zod",
+ "@kbn/calculate-auto",
+ ]
}
diff --git a/x-pack/packages/observability/logs_overview/utils/logs_source.ts b/x-pack/packages/observability/logs_overview/utils/logs_source.ts
deleted file mode 100644
index 80cb097391e9a..0000000000000
--- a/x-pack/packages/observability/logs_overview/utils/logs_source.ts
+++ /dev/null
@@ -1,55 +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 AbstractDataView } from '@kbn/data-views-plugin/common';
-
-export type LogsSourceConfiguration =
- | AdvancedSettingLogsSourceConfiguration
- | IndexNameLogsSourceConfiguration
- | DataViewLogsSourceConfiguration;
-
-export interface AdvancedSettingLogsSourceConfiguration {
- type: 'advanced_setting';
- timestampField?: string;
- messageField?: string;
-}
-
-export interface IndexNameLogsSourceConfiguration {
- type: 'index_name';
- indexName: string;
- timestampField: string;
- messageField: string;
-}
-
-export interface DataViewLogsSourceConfiguration {
- type: 'data_view';
- dataView: AbstractDataView;
- messageField?: string;
-}
-
-export const normalizeLogsSource = (
- logsSource: LogsSourceConfiguration
-): IndexNameLogsSourceConfiguration => {
- switch (logsSource.type) {
- case 'index_name':
- return logsSource;
- case 'advanced_setting':
- return {
- type: 'index_name',
- indexName: '', // TODO: get index name from advanced settings
- timestampField: logsSource.timestampField ?? '@timestamp',
- messageField: logsSource.messageField ?? 'message',
- };
- case 'data_view':
- return {
- type: 'index_name',
- indexName: logsSource.dataView.getIndexPattern(),
- timestampField: logsSource.dataView.timeFieldName ?? '@timestamp',
- messageField: logsSource.messageField ?? 'message',
- };
- }
-};
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
index 5d4e7725409b6..9cb842eab199e 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
@@ -5,8 +5,7 @@
* 2.0.
*/
-import React, { useMemo } from 'react';
-import { LogCategories } from '@kbn/observability-logs-overview';
+import React from 'react';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { CONTAINER_ID, SERVICE_ENVIRONMENT, SERVICE_NAME } from '../../../../common/es_fields/apm';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
@@ -19,11 +18,10 @@ import { APIReturnType } from '../../../services/rest/create_call_apm_api';
export function ServiceLogs() {
const {
services: {
- charts,
data: {
search: { search },
},
- settings: uiSettings,
+ logsShared,
},
} = useKibana();
const { serviceName } = useApmServiceContext();
@@ -32,22 +30,6 @@ export function ServiceLogs() {
} = useAnyOfApmParams('/services/{serviceName}/logs');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
- const logCategoriesDependencies = useMemo(
- () => ({
- charts,
- search,
- uiSettings,
- }),
- [charts, search, uiSettings]
- );
-
- // TODO: make charts required
- if (typeof logCategoriesDependencies.charts === 'undefined') {
- return null;
- }
-
- // const { start, end } = useTimeRange({ rangeFrom, rangeTo });
-
// const { data } = useFetcher(
// (callApmApi) => {
// if (start && end) {
@@ -69,7 +51,14 @@ export function ServiceLogs() {
// TODO: filter by service name and environment
// TODO: filter by time
- return ;
+ return (
+
+ );
// return (
//
+ import('@kbn/observability-logs-overview').then((mod) => ({ default: mod.LogsOverview }))
+);
+
+export type LogsOverviewProps = Omit;
+
+export const createLogsOverview =
+ (dependencies: LogsOverviewDependencies) => (props: LogsOverviewProps) => {
+ return ;
+ };
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/index.ts b/x-pack/plugins/observability_solution/logs_shared/public/index.ts
index 7a0f731b4a93b..c5eed0885295d 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/index.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/public/index.ts
@@ -50,6 +50,7 @@ export type {
UpdatedDateRange,
VisibleInterval,
} from './components/logging/log_text_stream/scrollable_log_text_stream_view';
+export type { LogsOverviewProps } from './components/logs_overview';
export const WithSummary = dynamic(() => import('./containers/logs/log_summary/with_summary'));
export const LogEntryFlyout = dynamic(
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
index d6f4ac81fe266..21e42a81cef42 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
@@ -12,6 +12,7 @@ import {
TraceLogsLocatorDefinition,
} from '../common/locators';
import { createLogAIAssistant, createLogsAIAssistantRenderer } from './components/log_ai_assistant';
+import { createLogsOverview } from './components/logs_overview';
import { LogViewsService } from './services/log_views';
import {
LogsSharedClientCoreSetup,
@@ -52,7 +53,15 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
public start(core: CoreStart, plugins: LogsSharedClientStartDeps) {
const { http } = core;
- const { data, dataViews, discoverShared, observabilityAIAssistant, logsDataAccess } = plugins;
+ const {
+ charts,
+ data,
+ dataViews,
+ discoverShared,
+ logsDataAccess,
+ observabilityAIAssistant,
+ uiSettings,
+ } = plugins;
const logViews = this.logViews.start({
http,
@@ -61,9 +70,17 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
search: data.search,
});
+ const LogsOverview = createLogsOverview({
+ charts,
+ logsDataAccess,
+ search: data.search.search,
+ uiSettings,
+ });
+
if (!observabilityAIAssistant) {
return {
logViews,
+ LogsOverview,
};
}
@@ -77,6 +94,7 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
return {
logViews,
LogAIAssistant,
+ LogsOverview,
};
}
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/types.ts
index 58b180ee8b6ef..479b4d0e5c962 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/types.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/public/types.ts
@@ -5,19 +5,20 @@
* 2.0.
*/
+import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
+import type { SettingsStart } from '@kbn/core-ui-settings-browser';
import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public';
-import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
+import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
-
-import { LogsSharedLocators } from '../common/locators';
+import type { LogsSharedLocators } from '../common/locators';
import type { LogAIAssistantProps } from './components/log_ai_assistant/log_ai_assistant';
-// import type { OsqueryPluginStart } from '../../osquery/public';
-import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views';
+import type { LogsOverviewProps } from './components/logs_overview';
+import type { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views';
// Our own setup and start contract values
export interface LogsSharedClientSetupExports {
@@ -28,6 +29,7 @@ export interface LogsSharedClientSetupExports {
export interface LogsSharedClientStartExports {
logViews: LogViewsServiceStart;
LogAIAssistant?: (props: Omit) => JSX.Element;
+ LogsOverview: (props: LogsOverviewProps) => JSX.Element;
}
export interface LogsSharedClientSetupDeps {
@@ -35,6 +37,7 @@ export interface LogsSharedClientSetupDeps {
}
export interface LogsSharedClientStartDeps {
+ charts: ChartsPluginStart;
data: DataPublicPluginStart;
dataViews: DataViewsPublicPluginStart;
discoverShared: DiscoverSharedPublicStart;
@@ -42,6 +45,7 @@ export interface LogsSharedClientStartDeps {
observabilityAIAssistant?: ObservabilityAIAssistantPublicStart;
share: SharePluginStart;
uiActions: UiActionsStart;
+ uiSettings: SettingsStart;
}
export type LogsSharedClientCoreSetup = CoreSetup<
From 0d35529037011ae070aafc6653352e207cb36bfa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Wed, 4 Sep 2024 17:55:15 +0000
Subject: [PATCH 15/74] Fix unnecessary uiSettings dependency
---
.../logs_shared/kibana.jsonc | 1 -
.../logs_shared/public/plugin.ts | 15 ++++-----------
.../logs_shared/public/types.ts | 2 --
3 files changed, 4 insertions(+), 14 deletions(-)
diff --git a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc
index cdf6f75257c6c..10c8fe32cfe9c 100644
--- a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc
+++ b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc
@@ -16,7 +16,6 @@
"logsDataAccess",
"observabilityShared",
"share",
- "uiSettings",
"usageCollection",
],
"optionalPlugins": [
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
index 21e42a81cef42..7cd0a9ab5258b 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
@@ -52,16 +52,9 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
}
public start(core: CoreStart, plugins: LogsSharedClientStartDeps) {
- const { http } = core;
- const {
- charts,
- data,
- dataViews,
- discoverShared,
- logsDataAccess,
- observabilityAIAssistant,
- uiSettings,
- } = plugins;
+ const { http, settings } = core;
+ const { charts, data, dataViews, discoverShared, logsDataAccess, observabilityAIAssistant } =
+ plugins;
const logViews = this.logViews.start({
http,
@@ -74,7 +67,7 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
charts,
logsDataAccess,
search: data.search.search,
- uiSettings,
+ uiSettings: settings,
});
if (!observabilityAIAssistant) {
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/types.ts
index 479b4d0e5c962..e033cf0b204ff 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/types.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/public/types.ts
@@ -6,7 +6,6 @@
*/
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
-import type { SettingsStart } from '@kbn/core-ui-settings-browser';
import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
@@ -45,7 +44,6 @@ export interface LogsSharedClientStartDeps {
observabilityAIAssistant?: ObservabilityAIAssistantPublicStart;
share: SharePluginStart;
uiActions: UiActionsStart;
- uiSettings: SettingsStart;
}
export type LogsSharedClientCoreSetup = CoreSetup<
From d551da4b029c89b1aff77328cf52de15c539a3a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Wed, 4 Sep 2024 19:39:08 +0000
Subject: [PATCH 16/74] Fix some refactoring mistakes
---
.../categorize_documents.ts | 4 +-
.../categorize_logs_service/queries.ts | 38 +++++++++++--------
.../components/app/service_logs/index.tsx | 2 +-
3 files changed, 25 insertions(+), 19 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
index c95c874cd0132..5c1569e71faef 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
@@ -50,8 +50,8 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
index,
timeField,
messageField,
- start,
- end,
+ startTimestamp,
+ endTimestamp,
randomSampler,
ignoredQueries,
minDocsPerCategory,
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
index b026133fd11bb..146203e3c0abc 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
@@ -15,8 +15,8 @@ const isoTimestampFormat = "YYYY-MM-DD'T'HH:mm:ss.SSS'Z'";
export const createCategorizationQuery = (
messageField: string,
timeField: string,
- start: string,
- end: string,
+ startTimestamp: string,
+ endTimestamp: string,
ignoredQueries: string[] = []
): QueryDslQueryContainer => {
return {
@@ -30,8 +30,8 @@ export const createCategorizationQuery = (
{
range: {
[timeField]: {
- gte: start,
- lte: end,
+ gte: startTimestamp,
+ lte: endTimestamp,
format: 'strict_date_time',
},
},
@@ -55,14 +55,14 @@ export const createCategorizationRequestParams = ({
index,
timeField,
messageField,
- start,
- end,
+ startTimestamp,
+ endTimestamp,
randomSampler,
minDocsPerCategory = 0,
ignoredQueries = [],
}: {
- start: string;
- end: string;
+ startTimestamp: string;
+ endTimestamp: string;
index: string;
timeField: string;
messageField: string;
@@ -70,27 +70,33 @@ export const createCategorizationRequestParams = ({
minDocsPerCategory?: number;
ignoredQueries?: string[];
}) => {
- const startMoment = moment(start, isoTimestampFormat);
- const endMoment = moment(end, isoTimestampFormat);
+ const startMoment = moment(startTimestamp, isoTimestampFormat);
+ const endMoment = moment(endTimestamp, isoTimestampFormat);
const fixedIntervalDuration = calculateAuto.atLeast(
24,
moment.duration(endMoment.diff(startMoment))
);
- const fixedIntervalSize = `${fixedIntervalDuration?.asMinutes()}m`;
+ const fixedIntervalSize = `${Math.ceil(fixedIntervalDuration?.asMinutes() ?? 1)}m`;
return {
index,
size: 0,
track_total_hits: false,
- query: createCategorizationQuery(messageField, timeField, start, end, ignoredQueries),
+ query: createCategorizationQuery(
+ messageField,
+ timeField,
+ startTimestamp,
+ endTimestamp,
+ ignoredQueries
+ ),
aggs: randomSampler.wrap({
histogram: {
date_histogram: {
field: '@timestamp',
fixed_interval: fixedIntervalSize,
extended_bounds: {
- min: start,
- max: end,
+ min: startTimestamp,
+ max: endTimestamp,
},
},
},
@@ -109,8 +115,8 @@ export const createCategorizationRequestParams = ({
field: timeField,
fixed_interval: fixedIntervalSize,
extended_bounds: {
- min: start,
- max: end,
+ min: startTimestamp,
+ max: endTimestamp,
},
},
},
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
index 9cb842eab199e..6b216d99c94e8 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
@@ -50,7 +50,7 @@ export function ServiceLogs() {
// );
// TODO: filter by service name and environment
- // TODO: filter by time
+ // TODO: fix loop when no logsSource is given
return (
Date: Wed, 4 Sep 2024 20:43:23 +0000
Subject: [PATCH 17/74] Memoize time overview props
---
.../logs_overview/logs_overview.tsx | 48 +++++++++----------
.../components/app/service_logs/index.tsx | 13 ++---
2 files changed, 27 insertions(+), 34 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
index 721f40ff2fa45..e3de15e5be42c 100644
--- a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
@@ -25,30 +25,30 @@ export type LogsOverviewDependencies = LogCategoriesDependencies & {
logsDataAccess: LogsDataAccessPluginStart;
};
-export const LogsOverview: React.FC = ({
- dependencies,
- logsSource = { type: 'shared_setting' },
- timeRange,
-}) => {
- const normalizedLogsSource = useAsync(
- () => normalizeLogsSource({ logsDataAccess: dependencies.logsDataAccess })(logsSource),
- [dependencies.logsDataAccess, logsSource]
- );
-
- if (normalizedLogsSource.loading) {
- return ;
- }
+export const LogsOverview: React.FC = React.memo(
+ ({ dependencies, logsSource = defaultLogsSource, timeRange }) => {
+ const normalizedLogsSource = useAsync(
+ () => normalizeLogsSource({ logsDataAccess: dependencies.logsDataAccess })(logsSource),
+ [dependencies.logsDataAccess, logsSource]
+ );
+
+ if (normalizedLogsSource.loading) {
+ return ;
+ }
- if (normalizedLogsSource.error != null || normalizedLogsSource.value == null) {
- // eslint-disable-next-line @kbn/i18n/strings_should_be_translated_with_i18n
- return <>Error>;
+ if (normalizedLogsSource.error != null || normalizedLogsSource.value == null) {
+ // eslint-disable-next-line @kbn/i18n/strings_should_be_translated_with_i18n
+ return <>Error>;
+ }
+
+ return (
+
+ );
}
+);
- return (
-
- );
-};
+const defaultLogsSource: LogsSourceConfiguration = { type: 'shared_setting' };
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
index 6b216d99c94e8..4d948cca3cd45 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React from 'react';
+import React, { useMemo } from 'react';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { CONTAINER_ID, SERVICE_ENVIRONMENT, SERVICE_NAME } from '../../../../common/es_fields/apm';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
@@ -29,6 +29,7 @@ export function ServiceLogs() {
query: { environment, kuery, rangeFrom, rangeTo },
} = useAnyOfApmParams('/services/{serviceName}/logs');
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
+ const timeRange = useMemo(() => ({ start, end }), [start, end]);
// const { data } = useFetcher(
// (callApmApi) => {
@@ -50,15 +51,7 @@ export function ServiceLogs() {
// );
// TODO: filter by service name and environment
- // TODO: fix loop when no logsSource is given
- return (
-
- );
+ return ;
// return (
//
Date: Thu, 5 Sep 2024 14:20:13 +0000
Subject: [PATCH 18/74] Update zod import
---
.../src/scenarios/distributed_unstructured_logs.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts
index 00b6340e4257a..24bb5e95a2987 100644
--- a/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts
+++ b/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts
@@ -7,7 +7,7 @@
*/
import { infra, LogDocument, log } from '@kbn/apm-synthtrace-client';
import { fakerEN as faker } from '@faker-js/faker';
-import { z } from 'zod';
+import { z } from '@kbn/zod';
import { Scenario } from '../cli/scenario';
import { withClient } from '../lib/utils/with_client';
import {
From 379387793793a2434af8e2872353c7c81b8d0101 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Thu, 5 Sep 2024 14:20:42 +0000
Subject: [PATCH 19/74] Add service filters and error reporting
---
.../log_categories/log_categories.tsx | 14 +-
.../log_categories_error_content.tsx | 44 +++++
.../logs_overview/logs_overview.tsx | 16 +-
.../logs_overview_error_content.tsx | 22 ++-
.../categorize_documents.ts | 8 +-
.../count_documents.ts | 61 ++++---
.../categorize_logs_service/queries.ts | 62 ++++---
.../services/categorize_logs_service/types.ts | 2 +
.../components/app/service_logs/index.tsx | 151 +++++++++++++++---
9 files changed, 288 insertions(+), 92 deletions(-)
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
index fe5828d1a97f1..93002bd388384 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { i18n } from '@kbn/i18n';
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { ISearchGeneric } from '@kbn/search-types';
import { createConsoleInspector } from '@kbn/xstate-utils';
import { useMachine } from '@xstate5/react';
@@ -15,6 +15,7 @@ import {
createCategorizeLogsServiceImplementations,
} from '../../services/categorize_logs_service';
import { IndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+import { LogCategoriesErrorContent } from './log_categories_error_content';
import { LogCategoriesLoadingContent } from './log_categories_loading_content';
import {
LogCategoriesResultContent,
@@ -23,6 +24,7 @@ import {
export interface LogCategoriesProps {
dependencies: LogCategoriesDependencies;
+ documentFilters?: QueryDslQueryContainer[];
logsSource: IndexNameLogsSourceConfiguration;
// The time range could be made optional if we want to support an internal
// time range picker
@@ -38,6 +40,7 @@ export type LogCategoriesDependencies = LogCategoriesResultContentDependencies &
export const LogCategories: React.FC = ({
dependencies,
+ documentFilters = [],
logsSource,
timeRange,
}) => {
@@ -53,6 +56,7 @@ export const LogCategories: React.FC = ({
endTimestamp: timeRange.end,
timeField: logsSource.timestampField,
messageField: logsSource.messageField,
+ documentFilters,
},
}
);
@@ -71,13 +75,7 @@ export const LogCategories: React.FC = ({
/>
);
} else if (categorizeLogsServiceState.matches('failed')) {
- return (
-
- {i18n.translate('xpack.observabilityLogsOverview.logCategories.div.errorLabel', {
- defaultMessage: 'Error',
- })}
-
- );
+ return ;
} else if (categorizeLogsServiceState.matches('countingDocuments')) {
return ;
} else if (
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx
new file mode 100644
index 0000000000000..445eef7802598
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiCodeBlock, EuiEmptyPrompt } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+
+export interface LogCategoriesErrorContentProps {
+ error?: Error;
+}
+
+export const LogCategoriesErrorContent: React.FC = ({ error }) => {
+ return (
+ {logsOverviewErrorTitle}}
+ body={
+
+ {error?.stack ?? error ?? unknownErrorDescription}
+
+ }
+ layout="vertical"
+ />
+ );
+};
+
+const logsOverviewErrorTitle = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.errorTitle',
+ {
+ defaultMessage: 'Failed to categorize logs',
+ }
+);
+
+const unknownErrorDescription = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.unknownErrorDescription',
+ {
+ defaultMessage: 'An unspecified error occurred.',
+ }
+);
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
index e3de15e5be42c..988656eb1571e 100644
--- a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
@@ -5,15 +5,18 @@
* 2.0.
*/
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { type LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
import React from 'react';
import useAsync from 'react-use/lib/useAsync';
import { LogsSourceConfiguration, normalizeLogsSource } from '../../utils/logs_source';
import { LogCategories, LogCategoriesDependencies } from '../log_categories';
+import { LogsOverviewErrorContent } from './logs_overview_error_content';
import { LogsOverviewLoadingContent } from './logs_overview_loading_content';
export interface LogsOverviewProps {
dependencies: LogsOverviewDependencies;
+ documentFilters?: QueryDslQueryContainer[];
logsSource?: LogsSourceConfiguration;
timeRange: {
start: string;
@@ -26,7 +29,12 @@ export type LogsOverviewDependencies = LogCategoriesDependencies & {
};
export const LogsOverview: React.FC = React.memo(
- ({ dependencies, logsSource = defaultLogsSource, timeRange }) => {
+ ({
+ dependencies,
+ documentFilters = defaultDocumentFilters,
+ logsSource = defaultLogsSource,
+ timeRange,
+ }) => {
const normalizedLogsSource = useAsync(
() => normalizeLogsSource({ logsDataAccess: dependencies.logsDataAccess })(logsSource),
[dependencies.logsDataAccess, logsSource]
@@ -37,13 +45,13 @@ export const LogsOverview: React.FC = React.memo(
}
if (normalizedLogsSource.error != null || normalizedLogsSource.value == null) {
- // eslint-disable-next-line @kbn/i18n/strings_should_be_translated_with_i18n
- return <>Error>;
+ return ;
}
return (
@@ -51,4 +59,6 @@ export const LogsOverview: React.FC = React.memo(
}
);
+const defaultDocumentFilters: QueryDslQueryContainer[] = [];
+
const defaultLogsSource: LogsSourceConfiguration = { type: 'shared_setting' };
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx
index 2f76384d66962..0f3c08d536884 100644
--- a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx
@@ -5,19 +5,26 @@
* 2.0.
*/
-import { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui';
+import { EuiCodeBlock, EuiEmptyPrompt } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
export interface LogsOverviewErrorContentProps {
- error: Error;
+ error?: Error;
}
-export const LogsOverviewErrorContent: React.FC = ({}) => {
+export const LogsOverviewErrorContent: React.FC = ({ error }) => {
return (
}
+ color="danger"
+ iconType="error"
title={{logsOverviewErrorTitle}
}
+ body={
+
+ {error?.stack ?? error ?? unknownErrorDescription}
+
+ }
+ layout="vertical"
/>
);
};
@@ -25,3 +32,10 @@ export const LogsOverviewErrorContent: React.FC =
const logsOverviewErrorTitle = i18n.translate('xpack.observabilityLogsOverview.errorTitle', {
defaultMessage: 'Error',
});
+
+const unknownErrorDescription = i18n.translate(
+ 'xpack.observabilityLogsOverview.unknownErrorDescription',
+ {
+ defaultMessage: 'An unspecified error occurred.',
+ }
+);
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
index 5c1569e71faef..5db7f296abe3a 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
@@ -24,7 +24,7 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
},
LogCategorizationParams & {
samplingProbability: number;
- ignoredQueries?: string[];
+ ignoredCategoryTerms?: string[];
minDocsPerCategory?: number;
}
>(
@@ -36,7 +36,8 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
timeField,
messageField,
samplingProbability,
- ignoredQueries,
+ ignoredCategoryTerms = [],
+ documentFilters = [],
minDocsPerCategory,
},
signal,
@@ -53,7 +54,8 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
startTimestamp,
endTimestamp,
randomSampler,
- ignoredQueries,
+ additionalFilters: documentFilters,
+ ignoredCategoryTerms,
minDocsPerCategory,
});
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts
index 241e1158c7349..359f9ddac2bd8 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/count_documents.ts
@@ -19,31 +19,42 @@ export const countDocuments = ({ search }: { search: ISearchGeneric }) =>
samplingProbability: number;
},
LogCategorizationParams
- >(async ({ input: { index, endTimestamp, startTimestamp, timeField, messageField }, signal }) => {
- const { rawResponse: totalHitsResponse } = await lastValueFrom(
- search(
- {
- params: {
- index,
- size: 0,
- track_total_hits: true,
- query: createCategorizationQuery(messageField, timeField, startTimestamp, endTimestamp),
+ >(
+ async ({
+ input: { index, endTimestamp, startTimestamp, timeField, messageField, documentFilters },
+ signal,
+ }) => {
+ const { rawResponse: totalHitsResponse } = await lastValueFrom(
+ search(
+ {
+ params: {
+ index,
+ size: 0,
+ track_total_hits: true,
+ query: createCategorizationQuery({
+ messageField,
+ timeField,
+ startTimestamp,
+ endTimestamp,
+ additionalFilters: documentFilters,
+ }),
+ },
},
- },
- { abortSignal: signal }
- )
- );
+ { abortSignal: signal }
+ )
+ );
- const documentCount =
- totalHitsResponse.hits.total == null
- ? 0
- : typeof totalHitsResponse.hits.total === 'number'
- ? totalHitsResponse.hits.total
- : totalHitsResponse.hits.total.value;
- const samplingProbability = getSampleProbability(documentCount);
+ const documentCount =
+ totalHitsResponse.hits.total == null
+ ? 0
+ : typeof totalHitsResponse.hits.total === 'number'
+ ? totalHitsResponse.hits.total
+ : totalHitsResponse.hits.total.value;
+ const samplingProbability = getSampleProbability(documentCount);
- return {
- documentCount,
- samplingProbability,
- };
- });
+ return {
+ documentCount,
+ samplingProbability,
+ };
+ }
+ );
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
index 146203e3c0abc..10488a827ece6 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
@@ -12,13 +12,21 @@ import moment from 'moment';
const isoTimestampFormat = "YYYY-MM-DD'T'HH:mm:ss.SSS'Z'";
-export const createCategorizationQuery = (
- messageField: string,
- timeField: string,
- startTimestamp: string,
- endTimestamp: string,
- ignoredQueries: string[] = []
-): QueryDslQueryContainer => {
+export const createCategorizationQuery = ({
+ messageField,
+ timeField,
+ startTimestamp,
+ endTimestamp,
+ additionalFilters = [],
+ ignoredCategoryTerms = [],
+}: {
+ messageField: string;
+ timeField: string;
+ startTimestamp: string;
+ endTimestamp: string;
+ additionalFilters?: QueryDslQueryContainer[];
+ ignoredCategoryTerms?: string[];
+}): QueryDslQueryContainer => {
return {
bool: {
filter: [
@@ -36,17 +44,9 @@ export const createCategorizationQuery = (
},
},
},
+ ...additionalFilters,
],
- must_not: ignoredQueries.map((ignoredQuery) => ({
- match: {
- [messageField]: {
- query: ignoredQuery,
- operator: 'AND' as const,
- fuzziness: 0,
- auto_generate_synonyms_phrase_query: false,
- },
- },
- })),
+ must_not: ignoredCategoryTerms.map(createCategoryQuery(messageField)),
},
};
};
@@ -59,7 +59,8 @@ export const createCategorizationRequestParams = ({
endTimestamp,
randomSampler,
minDocsPerCategory = 0,
- ignoredQueries = [],
+ additionalFilters = [],
+ ignoredCategoryTerms = [],
}: {
startTimestamp: string;
endTimestamp: string;
@@ -68,7 +69,8 @@ export const createCategorizationRequestParams = ({
messageField: string;
randomSampler: RandomSamplerWrapper;
minDocsPerCategory?: number;
- ignoredQueries?: string[];
+ additionalFilters?: QueryDslQueryContainer[];
+ ignoredCategoryTerms?: string[];
}) => {
const startMoment = moment(startTimestamp, isoTimestampFormat);
const endMoment = moment(endTimestamp, isoTimestampFormat);
@@ -82,13 +84,14 @@ export const createCategorizationRequestParams = ({
index,
size: 0,
track_total_hits: false,
- query: createCategorizationQuery(
+ query: createCategorizationQuery({
messageField,
timeField,
startTimestamp,
endTimestamp,
- ignoredQueries
- ),
+ additionalFilters,
+ ignoredCategoryTerms,
+ }),
aggs: randomSampler.wrap({
histogram: {
date_histogram: {
@@ -103,7 +106,7 @@ export const createCategorizationRequestParams = ({
categories: {
categorize_text: {
field: messageField,
- size: 10000,
+ size: 1000,
categorization_analyzer: {
tokenizer: 'standard',
},
@@ -131,3 +134,16 @@ export const createCategorizationRequestParams = ({
}),
};
};
+
+export const createCategoryQuery =
+ (messageField: string) =>
+ (categoryTerms: string): QueryDslQueryContainer => ({
+ match: {
+ [messageField]: {
+ query: categoryTerms,
+ operator: 'AND' as const,
+ fuzziness: 0,
+ auto_generate_synonyms_phrase_query: false,
+ },
+ },
+ });
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts
index a1e4a72fbfd56..e094317a98d62 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/types.ts
@@ -4,6 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { ISearchGeneric } from '@kbn/search-types';
export interface CategorizeLogsServiceDependencies {
@@ -11,6 +12,7 @@ export interface CategorizeLogsServiceDependencies {
}
export interface LogCategorizationParams {
+ documentFilters: QueryDslQueryContainer[];
endTimestamp: string;
index: string;
messageField: string;
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
index 4d948cca3cd45..99361ddd76a1a 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
@@ -6,23 +6,19 @@
*/
import React, { useMemo } from 'react';
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { CONTAINER_ID, SERVICE_ENVIRONMENT, SERVICE_NAME } from '../../../../common/es_fields/apm';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useKibana } from '../../../context/kibana_context/use_kibana';
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
-// import { useFetcher } from '../../../hooks/use_fetcher';
+import { useFetcher } from '../../../hooks/use_fetcher';
import { useTimeRange } from '../../../hooks/use_time_range';
import { APIReturnType } from '../../../services/rest/create_call_apm_api';
export function ServiceLogs() {
const {
- services: {
- data: {
- search: { search },
- },
- logsShared,
- },
+ services: { logsShared },
} = useKibana();
const { serviceName } = useApmServiceContext();
const {
@@ -31,27 +27,34 @@ export function ServiceLogs() {
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const timeRange = useMemo(() => ({ start, end }), [start, end]);
- // const { data } = useFetcher(
- // (callApmApi) => {
- // if (start && end) {
- // return callApmApi('GET /internal/apm/services/{serviceName}/infrastructure_attributes', {
- // params: {
- // path: { serviceName },
- // query: {
- // environment,
- // kuery,
- // start,
- // end,
- // },
- // },
- // });
- // }
- // },
- // [environment, kuery, serviceName, start, end]
- // );
+ const { data: logFilters } = useFetcher(
+ async (callApmApi) => {
+ if (start == null || end == null) {
+ return;
+ }
+
+ const { containerIds } = await callApmApi(
+ 'GET /internal/apm/services/{serviceName}/infrastructure_attributes',
+ {
+ params: {
+ path: { serviceName },
+ query: {
+ environment,
+ kuery,
+ start,
+ end,
+ },
+ },
+ }
+ );
+
+ return [getInfrastructureFilter({ containerIds, environment, serviceName })];
+ },
+ [environment, kuery, serviceName, start, end]
+ );
// TODO: filter by service name and environment
- return ;
+ return ;
// return (
//
Date: Thu, 5 Sep 2024 14:26:51 +0000
Subject: [PATCH 20/74] Fix fetching remaining categories
---
.../categorize_logs_service/categorize_documents.ts | 6 +++---
.../categorize_logs_service/categorize_logs_service.ts | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
index 5db7f296abe3a..d787fd6b50148 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
@@ -24,8 +24,8 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
},
LogCategorizationParams & {
samplingProbability: number;
- ignoredCategoryTerms?: string[];
- minDocsPerCategory?: number;
+ ignoredCategoryTerms: string[];
+ minDocsPerCategory: number;
}
>(
async ({
@@ -36,7 +36,7 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
timeField,
messageField,
samplingProbability,
- ignoredCategoryTerms = [],
+ ignoredCategoryTerms,
documentFilters = [],
minDocsPerCategory,
},
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
index eb3f3b4e551f4..6aa071e12603f 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
@@ -119,7 +119,7 @@ export const categorizeLogsService = setup({
input: ({ context }) => ({
...context.parameters,
samplingProbability: context.samplingProbability,
- ignoredQueries: [],
+ ignoredCategoryTerms: [],
minDocsPerCategory: 10,
}),
onDone: {
@@ -162,7 +162,7 @@ export const categorizeLogsService = setup({
input: ({ context }) => ({
...context.parameters,
samplingProbability: context.samplingProbability,
- ignoredQueries: context.categories.map((category) => category.terms),
+ ignoredCategoryTerms: context.categories.map((category) => category.terms),
minDocsPerCategory: 0,
}),
onDone: {
From 870d3474c24907aa454b7e64b6300d98c36cd9f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Fri, 6 Sep 2024 12:12:51 +0000
Subject: [PATCH 21/74] Add logs overview feature flag
---
.../settings/setting_ids/index.ts | 1 +
.../components/app/service_logs/index.tsx | 72 +++++++++++++++----
.../public/components/logs_overview.tsx | 24 ++++++-
.../logs_shared/public/types.ts | 4 +-
.../logs_shared/server/feature_flags.ts | 33 +++++++++
.../logs_shared/server/plugin.ts | 28 ++++----
6 files changed, 131 insertions(+), 31 deletions(-)
create mode 100644 x-pack/plugins/observability_solution/logs_shared/server/feature_flags.ts
diff --git a/packages/kbn-management/settings/setting_ids/index.ts b/packages/kbn-management/settings/setting_ids/index.ts
index 9f0c226717079..6c475946fa050 100644
--- a/packages/kbn-management/settings/setting_ids/index.ts
+++ b/packages/kbn-management/settings/setting_ids/index.ts
@@ -141,6 +141,7 @@ export const OBSERVABILITY_APM_ENABLE_SERVICE_INVENTORY_TABLE_SEARCH_BAR =
'observability:apmEnableServiceInventoryTableSearchBar';
export const OBSERVABILITY_LOGS_EXPLORER_ALLOWED_DATA_VIEWS_ID =
'observability:logsExplorer:allowedDataViews';
+export const OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID = 'observability:newLogsOverview';
export const OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE = 'observability:entityCentricExperience';
export const OBSERVABILITY_LOGS_DATA_ACCESS_LOG_SOURCES_ID = 'observability:logSources';
export const OBSERVABILITY_AI_ASSISTANT_LOGS_INDEX_PATTERN_ID =
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
index 99361ddd76a1a..b2c479f46f32b 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
@@ -6,7 +6,9 @@
*/
import React, { useMemo } from 'react';
+import moment from 'moment';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import { LogStream } from '@kbn/logs-shared-plugin/public';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { CONTAINER_ID, SERVICE_ENVIRONMENT, SERVICE_NAME } from '../../../../common/es_fields/apm';
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
@@ -17,6 +19,63 @@ import { useTimeRange } from '../../../hooks/use_time_range';
import { APIReturnType } from '../../../services/rest/create_call_apm_api';
export function ServiceLogs() {
+ const {
+ services: {
+ logsShared: { LogsOverview },
+ },
+ } = useKibana();
+
+ const isLogsOverviewEnabled = LogsOverview.useIsEnabled();
+
+ if (isLogsOverviewEnabled) {
+ return ;
+ } else {
+ return ;
+ }
+}
+
+export function ClassicServiceLogsStream() {
+ const { serviceName } = useApmServiceContext();
+
+ const {
+ query: { environment, kuery, rangeFrom, rangeTo },
+ } = useAnyOfApmParams('/services/{serviceName}/logs');
+
+ const { start, end } = useTimeRange({ rangeFrom, rangeTo });
+
+ const { data } = useFetcher(
+ (callApmApi) => {
+ if (start && end) {
+ return callApmApi('GET /internal/apm/services/{serviceName}/infrastructure_attributes', {
+ params: {
+ path: { serviceName },
+ query: {
+ environment,
+ kuery,
+ start,
+ end,
+ },
+ },
+ });
+ }
+ },
+ [environment, kuery, serviceName, start, end]
+ );
+
+ return (
+
+ );
+}
+
+export function ServiceLogsOverview() {
const {
services: { logsShared },
} = useKibana();
@@ -53,20 +112,7 @@ export function ServiceLogs() {
[environment, kuery, serviceName, start, end]
);
- // TODO: filter by service name and environment
return ;
-
- // return (
- //
- // );
}
export function getInfrastructureKQLFilter({
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx
index 862f747a70c46..e0baff2237ff6 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx
+++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx
@@ -5,12 +5,14 @@
* 2.0.
*/
+import { OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID } from '@kbn/management-settings-ids';
import type {
- LogsOverviewDependencies,
LogsOverviewProps as FullLogsOverviewProps,
+ LogsOverviewDependencies,
} from '@kbn/observability-logs-overview';
import { dynamic } from '@kbn/shared-ux-utility';
import React from 'react';
+import useObservable from 'react-use/lib/useObservable';
const LazyLogsOverview = dynamic(() =>
import('@kbn/observability-logs-overview').then((mod) => ({ default: mod.LogsOverview }))
@@ -18,7 +20,23 @@ const LazyLogsOverview = dynamic(() =>
export type LogsOverviewProps = Omit;
-export const createLogsOverview =
- (dependencies: LogsOverviewDependencies) => (props: LogsOverviewProps) => {
+export const createLogsOverview = (dependencies: LogsOverviewDependencies) => {
+ const SelfContainedLogsOverview = (props: LogsOverviewProps) => {
return ;
};
+
+ const isEnabled$ = dependencies.uiSettings.client.get$(
+ OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID,
+ defaultIsEnabled
+ );
+
+ SelfContainedLogsOverview.useIsEnabled = (): boolean => {
+ return useObservable(isEnabled$, defaultIsEnabled);
+ };
+
+ return SelfContainedLogsOverview;
+};
+
+const defaultIsEnabled = false;
+
+export type SelfContainedLogsOverview = ReturnType;
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/types.ts
index e033cf0b204ff..4237c28c621b8 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/types.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/public/types.ts
@@ -16,7 +16,7 @@ import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/publi
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
import type { LogsSharedLocators } from '../common/locators';
import type { LogAIAssistantProps } from './components/log_ai_assistant/log_ai_assistant';
-import type { LogsOverviewProps } from './components/logs_overview';
+import type { SelfContainedLogsOverview } from './components/logs_overview';
import type { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views';
// Our own setup and start contract values
@@ -28,7 +28,7 @@ export interface LogsSharedClientSetupExports {
export interface LogsSharedClientStartExports {
logViews: LogViewsServiceStart;
LogAIAssistant?: (props: Omit) => JSX.Element;
- LogsOverview: (props: LogsOverviewProps) => JSX.Element;
+ LogsOverview: SelfContainedLogsOverview;
}
export interface LogsSharedClientSetupDeps {
diff --git a/x-pack/plugins/observability_solution/logs_shared/server/feature_flags.ts b/x-pack/plugins/observability_solution/logs_shared/server/feature_flags.ts
new file mode 100644
index 0000000000000..0298416bd3f26
--- /dev/null
+++ b/x-pack/plugins/observability_solution/logs_shared/server/feature_flags.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { schema } from '@kbn/config-schema';
+import { UiSettingsParams } from '@kbn/core-ui-settings-common';
+import { i18n } from '@kbn/i18n';
+import { OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID } from '@kbn/management-settings-ids';
+
+const technicalPreviewLabel = i18n.translate('xpack.logsShared.technicalPreviewSettingLabel', {
+ defaultMessage: 'Technical Preview',
+});
+
+export const featureFlagUiSettings: Record = {
+ [OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID]: {
+ category: ['observability'],
+ name: i18n.translate('xpack.logsShared.newLogsOverviewSettingName', {
+ defaultMessage: 'New logs overview',
+ }),
+ value: false,
+ description: i18n.translate('xpack.logsShared.newLogsOverviewSettingDescription', {
+ defaultMessage: '{technicalPreviewLabel} Enable the new logs overview experience.',
+
+ values: { technicalPreviewLabel: `[${technicalPreviewLabel}]` },
+ }),
+ type: 'boolean',
+ schema: schema.boolean(),
+ requiresPageReload: true,
+ },
+};
diff --git a/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts
index 7c97e175ed64f..d1f6399104fc2 100644
--- a/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/server/plugin.ts
@@ -5,8 +5,19 @@
* 2.0.
*/
-import { PluginInitializerContext, CoreStart, Plugin, Logger } from '@kbn/core/server';
-
+import { CoreStart, Logger, Plugin, PluginInitializerContext } from '@kbn/core/server';
+import { defaultLogViewId } from '../common/log_views';
+import { LogsSharedConfig } from '../common/plugin_config';
+import { registerDeprecations } from './deprecations';
+import { featureFlagUiSettings } from './feature_flags';
+import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapter';
+import { LogsSharedKibanaLogEntriesAdapter } from './lib/adapters/log_entries/kibana_log_entries_adapter';
+import { LogsSharedLogEntriesDomain } from './lib/domains/log_entries_domain';
+import { LogsSharedBackendLibs, LogsSharedDomainLibs } from './lib/logs_shared_types';
+import { initLogsSharedServer } from './logs_shared_server';
+import { logViewSavedObjectType } from './saved_objects';
+import { LogEntriesService } from './services/log_entries';
+import { LogViewsService } from './services/log_views';
import {
LogsSharedPluginCoreSetup,
LogsSharedPluginSetup,
@@ -15,17 +26,6 @@ import {
LogsSharedServerPluginStartDeps,
UsageCollector,
} from './types';
-import { logViewSavedObjectType } from './saved_objects';
-import { initLogsSharedServer } from './logs_shared_server';
-import { LogViewsService } from './services/log_views';
-import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapter';
-import { LogsSharedBackendLibs, LogsSharedDomainLibs } from './lib/logs_shared_types';
-import { LogsSharedLogEntriesDomain } from './lib/domains/log_entries_domain';
-import { LogsSharedKibanaLogEntriesAdapter } from './lib/adapters/log_entries/kibana_log_entries_adapter';
-import { LogEntriesService } from './services/log_entries';
-import { LogsSharedConfig } from '../common/plugin_config';
-import { registerDeprecations } from './deprecations';
-import { defaultLogViewId } from '../common/log_views';
export class LogsSharedPlugin
implements
@@ -88,6 +88,8 @@ export class LogsSharedPlugin
registerDeprecations({ core });
+ core.uiSettings.register(featureFlagUiSettings);
+
return {
...domainLibs,
logViews,
From d08f104f4f8a40167d84b7ba0518f9a250aa3740 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Fri, 6 Sep 2024 16:09:06 +0000
Subject: [PATCH 22/74] Add logs overview in infra hosts
---
.../components/tabs/logs/logs_tab_content.tsx | 92 ++++++++++++++-----
1 file changed, 71 insertions(+), 21 deletions(-)
diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
index 27344ccd1f108..3752e284c1b62 100644
--- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
+++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
@@ -5,21 +5,37 @@
* 2.0.
*/
-import React, { useMemo } from 'react';
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { LogStream } from '@kbn/logs-shared-plugin/public';
-import { i18n } from '@kbn/i18n';
+import React, { useMemo } from 'react';
import { InfraLoadingPanel } from '../../../../../../components/loading';
+import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana';
+import { useLogViewReference } from '../../../../../../hooks/use_log_view_reference';
+import { buildCombinedAssetFilter } from '../../../../../../utils/filters/build';
import { useHostsViewContext } from '../../../hooks/use_hosts_view';
-import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
import { useLogsSearchUrlState } from '../../../hooks/use_logs_search_url_state';
+import { useUnifiedSearchContext } from '../../../hooks/use_unified_search';
import { LogsLinkToStream } from './logs_link_to_stream';
import { LogsSearchBar } from './logs_search_bar';
-import { buildCombinedAssetFilter } from '../../../../../../utils/filters/build';
-import { useLogViewReference } from '../../../../../../hooks/use_log_view_reference';
export const LogsTabContent = () => {
+ const {
+ services: {
+ logsShared: { LogsOverview },
+ },
+ } = useKibanaContextForPlugin();
+ const isLogsOverviewEnabled = LogsOverview.useIsEnabled();
+ if (isLogsOverviewEnabled) {
+ return ;
+ } else {
+ return ;
+ }
+};
+
+export const LogsTabLogStreamContent = () => {
const [filterQuery] = useLogsSearchUrlState();
const { getDateRangeAsTimestamp } = useUnifiedSearchContext();
const { from, to } = useMemo(() => getDateRangeAsTimestamp(), [getDateRangeAsTimestamp]);
@@ -53,22 +69,7 @@ export const LogsTabContent = () => {
}, [filterQuery.query, hostNodes]);
if (loading || logViewLoading || !logView) {
- return (
-
-
-
- }
- />
-
-
- );
+ return ;
}
return (
@@ -84,6 +85,7 @@ export const LogsTabContent = () => {
query={logsLinkToStreamQuery}
logView={logView}
/>
+ ]
@@ -112,3 +114,51 @@ const createHostsFilterQueryParam = (hostNodes: string[]): string => {
return hostsQueryParam;
};
+
+const LogsTabLogsOverviewContent = () => {
+ const {
+ services: {
+ logsShared: { LogsOverview },
+ },
+ } = useKibanaContextForPlugin();
+
+ const { parsedDateRange } = useUnifiedSearchContext();
+ const timeRange = useMemo(
+ () => ({ start: parsedDateRange.from, end: parsedDateRange.to }),
+ [parsedDateRange.from, parsedDateRange.to]
+ );
+
+ const { hostNodes, loading } = useHostsViewContext();
+ const logFilters = useMemo(
+ () => [
+ buildCombinedAssetFilter({
+ field: 'host.name',
+ values: hostNodes.map((p) => p.name),
+ }).query as QueryDslQueryContainer,
+ ],
+ [hostNodes]
+ );
+
+ if (loading) {
+ return ;
+ }
+
+ return ;
+};
+
+const LogsTabLoadingContent = () => (
+
+
+
+ }
+ />
+
+
+);
From 6ecd0561324e846809122ec41eae024279e2ad32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Fri, 6 Sep 2024 16:25:09 +0000
Subject: [PATCH 23/74] Fix sampling probability of second pass
---
.../services/categorize_logs_service/categorize_logs_service.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
index 6aa071e12603f..1626d201722dc 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
@@ -161,7 +161,7 @@ export const categorizeLogsService = setup({
id: 'categorizeRemainingCategories',
input: ({ context }) => ({
...context.parameters,
- samplingProbability: context.samplingProbability,
+ samplingProbability: 1,
ignoredCategoryTerms: context.categories.map((category) => category.terms),
minDocsPerCategory: 0,
}),
From b0b1fe741556725a358b7e724a080c160d510630 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Fri, 6 Sep 2024 19:41:52 +0000
Subject: [PATCH 24/74] Signal categories limit being reached
---
.../categorize_documents.ts | 4 ++
.../categorize_logs_service.ts | 43 +++++++++++++++----
.../categorize_logs_service/queries.ts | 4 +-
3 files changed, 41 insertions(+), 10 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
index d787fd6b50148..02e24b9174c5d 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
@@ -16,11 +16,13 @@ import { LogCategory, LogCategoryChange } from '../../types';
// the fraction of a category's histogram below which the category is considered rare
const rarityThreshold = 0.2;
+const maxCategoriesCount = 1000;
export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
fromPromise<
{
categories: LogCategory[];
+ hasReachedLimit: boolean;
},
LogCategorizationParams & {
samplingProbability: number;
@@ -57,6 +59,7 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
additionalFilters: documentFilters,
ignoredCategoryTerms,
minDocsPerCategory,
+ maxCategoriesCount,
});
const { rawResponse } = await lastValueFrom(search({ params: requestParams }));
@@ -76,6 +79,7 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
return {
categories: logCategories,
+ hasReachedLimit: logCategories.length >= maxCategoriesCount,
};
}
);
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
index 1626d201722dc..deeb758d2d737 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_logs_service.ts
@@ -6,11 +6,11 @@
*/
import { MachineImplementationsFrom, assign, setup } from 'xstate5';
+import { LogCategory } from '../../types';
import { getPlaceholderFor } from '../../utils/xstate5_utils';
import { categorizeDocuments } from './categorize_documents';
import { countDocuments } from './count_documents';
import { CategorizeLogsServiceDependencies, LogCategorizationParams } from './types';
-import { LogCategory } from '../../types';
export const categorizeLogsService = setup({
types: {
@@ -18,14 +18,16 @@ export const categorizeLogsService = setup({
output: {} as {
categories: LogCategory[];
documentCount: number;
+ hasReachedLimit: boolean;
samplingProbability: number;
},
context: {} as {
categories: LogCategory[];
documentCount: number;
+ error?: Error;
+ hasReachedLimit: boolean;
parameters: LogCategorizationParams;
samplingProbability: number;
- error?: Error;
},
events: {} as {
type: 'cancel';
@@ -39,9 +41,12 @@ export const categorizeLogsService = setup({
storeError: assign((_, params: { error: unknown }) => ({
error: params.error instanceof Error ? params.error : new Error(String(params.error)),
})),
- storeCategories: assign(({ context }, params: { categories: LogCategory[] }) => ({
- categories: [...context.categories, ...params.categories],
- })),
+ storeCategories: assign(
+ ({ context }, params: { categories: LogCategory[]; hasReachedLimit: boolean }) => ({
+ categories: [...context.categories, ...params.categories],
+ hasReachedLimit: params.hasReachedLimit,
+ })
+ ),
storeDocumentCount: assign(
(_, params: { documentCount: number; samplingProbability: number }) => ({
documentCount: params.documentCount,
@@ -50,16 +55,19 @@ export const categorizeLogsService = setup({
),
},
guards: {
- requiresSampling: ({ context }) => context.samplingProbability < 1,
+ hasTooFewDocuments: (_guardArgs, params: { documentCount: number }) => params.documentCount < 1,
+ requiresSampling: (_guardArgs, params: { samplingProbability: number }) =>
+ params.samplingProbability < 1,
},
}).createMachine({
- /** @xstate-layout N4IgpgJg5mDOIC5QGMCGAXMUD2AnAlgF5gAy2UsAdMtgK4B26+9UAItsrQLZiOwDEEbPTCVmAN2wBrUWkw4CxMhWp1GzNh2690sBBI4Z8wgNoAGALrmLiUAAdssfE2G2QAD0QAmAJwAOSgAWP0CAZgA2M0CzAEZwgFYYgHYkgBoQAE9EJK8vIPCYsy8k+K94pJ8YmIBfavS5LDwiUnIqGgYmFnZOHj5BYVEDGWoMRsUWlXb1Lq1e3X16STljemsTGJskEAcnF3o3TwRfAOCwyOi4xJT0rKPQmMpQxPifJNCkyLK-cNr60YVmso2mpOpoejoBGBcLg8JQ7AAbDAAMzwXBG8iaSlaqg6Gm62j4CyWRlMlmsbh2zhWB28-iCIQiUViCWSaUyiD8D3iZh5MS8kSiXhilV+IAaAKxkxBeNmEP4aHoyDA8PJW0pexpRzpp0ZFxZ13ZCHipUofhewQqKXCXkCovFmImVCRYHQyAAFhoAMqoLgIyAAYX+TTg-REYkW0lkQfG3t98ID0fwcFV9kcVNcW0OhRKlDMPneZktPjKBtu4R84UejNCXh5SUCQqSdsTkqdLvdXp9foggYxBBDUJhuDhiPQKNwaPtMa78Z7ieTlgpaY1mcQ2fiufzSULr2L5TZtySfh8lC8x9rgXigXCoT8T2bfcB2Odro9LFj3d7YyTAgVSpVi5qsu1Krgg66bgWRYlgeHJ+HkNpeO825mqEl5eA+36tpQL4diwABKYBcKgzAaF+AIhkIYZDFGj7EARREkSwZHBrAKbbMBGagFmVoQeECShO8No1jc3iIUEHwvH43w+D4RT1hhEqOth7ZvlA9HEfQpHzpC0KwgiyKouimFgOpjFQMx-asYBqa7CBXFrjxeYRPxglhF4IkIIEfKPHBZo+NeISRDUdRii2Sk4appmaUx2nyqgirKmx6p2R4DkfLxLk5G5Hn3JWDavEkMTRFJ4SBGVtQhfQ2AQHAbhTk+FBLrZnGpQgAC04QeW1G4yb1fX9aECkOkCOLTGCBK6E16b7KBDYeYUG7vAkZplXmHzFEN4wjRFnZxgmj61UBzUzfZCB8SeTk5DEJXHJ1hqFQ813rcU8QoQJm0NW2r4aFFWkHfAR3TZqMQFhBJQNreDY2h5iEBDWnLHtahRFRtIX1VhSLEbOU0rqdQqxKeCTXgkiR+EUHlnJQRWJNEr104W8QfVhlFgDjKWHPjDz8lefHGtd5OGkJlA+PENbvJekR3vcFXVEAA */
+ /** @xstate-layout N4IgpgJg5mDOIC5QGMCGAXMUD2AnAlgF5gAy2UsAdMtgK4B26+9UAItsrQLZiOwDEEbPTCVmAN2wBrUWkw4CxMhWp1GzNh2690sBBI4Z8wgNoAGALrmLiUAAdssfE2G2QAD0QBmMwA5KACy+AQFmob4AjABMwQBsADQgAJ6IkYEAnJkA7FmxZlERmQGxAL4liXJYeESk5FQ0DEws7Jw8fILCogYy1BhVirUqDerNWm26+vSScsb01iYRNkggDk4u9G6eCD7+QSFhftFxiSkIvgCsWZSxEVlRsbFZ52Zm515lFX0KNcr1ak2aVo6ARCERiKbSWRfapKOqqRoaFraPiTaZGUyWExRJb2RzOWabbx+QLBULhI7FE7eWL+F45GnRPIRZkfECVb6wob-RFjYH8MC4XB4Sh2AA2GAAZnguL15DDBn8EaMgSiDDMMVZLG5VvjXMstjsSftyTFKclEOdzgFKF5zukvA8zBFnl50udWez5b94SNAcjdPw0PRkGBRdZtXj1oTtsS9mTDqaEuaEBF8udKFkIr5fK6olkzOksgEPdCBt6JWB0MgABYaADKqC4YsgAGFS-g4B0wd0oXKBg2m6LW+24OHljqo-rEMzbpQos8-K7fC9CknTrF0rEbbb0oVMoWIgF3eU2e3OVQK1XaywB82IG2+x2BAKhbgReL0FLcDLPf3G3eH36J8x1xNYCSnFNmSuecXhzdJlydTcqQQLJfHSOc0PyLJN3SMxYiPEtH3PShLxret-yHe8RwEIMQzDLVx0jcDQC2GdoIXOCENXZDsyiOcAiiKJ0iiPDLi8V1CKA4jSOvKAACUwC4VBmA0QDvk7UEughHpfxqBSlJUlg1OqUcGNA3UNggrMs347IjzdaIvGQwSvECXI8k3Z43gEiJJI5BUSMrMiWH05T6FU6j+UFYUxUlaVZSksBQsMqBjIIUycRWJi9RY6dIn8KIAjsu1zkc5CAmiG1fBiaIzB8B0QmPT4iICmSNGS8KjMi2jQxArKwJyjw8pswriocqInOTLwIi3ASD1yQpswCd5WXobAIDgNxdPPCMBss3KEAAWjXRBDvTfcLsu9Jlr8r04WGAEkXGeBGL26MBOQzIt2ut4cwmirCt8W6yzhNqbwo4dH0216LOjTMIjnBdYhK1DYgdHjihtZbUIdWIXJuYGflBoLZI6iKoZe8zJwOw9KtGt1kbuTcsmQrwi0oeCQjzZ5blwt1Cek5TKN22GIIKZbAgKC45pyLyeLwtz4Kyabs1QgWAs0kXqaGhBxdcnzpaE2XXmch0MORmaBJeLwjbKMogA */
id: 'categorizeLogs',
context: ({ input }) => ({
categories: [],
documentCount: 0,
+ hasReachedLimit: false,
parameters: input,
- samplingProbability: 0,
+ samplingProbability: 1,
}),
initial: 'countingDocuments',
states: {
@@ -68,9 +76,25 @@ export const categorizeLogsService = setup({
src: 'countDocuments',
input: ({ context }) => context.parameters,
onDone: [
+ {
+ target: 'done',
+ guard: {
+ type: 'hasTooFewDocuments',
+ params: ({ event }) => event.output,
+ },
+ actions: [
+ {
+ type: 'storeDocumentCount',
+ params: ({ event }) => event.output,
+ },
+ ],
+ },
{
target: 'fetchingSampledCategories',
- guard: 'requiresSampling',
+ guard: {
+ type: 'requiresSampling',
+ params: ({ event }) => event.output,
+ },
actions: [
{
type: 'storeDocumentCount',
@@ -209,6 +233,7 @@ export const categorizeLogsService = setup({
output: ({ context }) => ({
categories: context.categories,
documentCount: context.documentCount,
+ hasReachedLimit: context.hasReachedLimit,
samplingProbability: context.samplingProbability,
}),
});
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
index 10488a827ece6..849567895f934 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
@@ -61,6 +61,7 @@ export const createCategorizationRequestParams = ({
minDocsPerCategory = 0,
additionalFilters = [],
ignoredCategoryTerms = [],
+ maxCategoriesCount = 1000,
}: {
startTimestamp: string;
endTimestamp: string;
@@ -71,6 +72,7 @@ export const createCategorizationRequestParams = ({
minDocsPerCategory?: number;
additionalFilters?: QueryDslQueryContainer[];
ignoredCategoryTerms?: string[];
+ maxCategoriesCount?: number;
}) => {
const startMoment = moment(startTimestamp, isoTimestampFormat);
const endMoment = moment(endTimestamp, isoTimestampFormat);
@@ -106,7 +108,7 @@ export const createCategorizationRequestParams = ({
categories: {
categorize_text: {
field: messageField,
- size: 1000,
+ size: maxCategoriesCount,
categorization_analyzer: {
tokenizer: 'standard',
},
From 8c09788753271efc7c8a5838641d8d50af5c5f32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Fri, 6 Sep 2024 19:42:49 +0000
Subject: [PATCH 25/74] Respect the abort signal when categorizing
---
.../services/categorize_logs_service/categorize_documents.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
index 02e24b9174c5d..9d1ba3f2cbc44 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
@@ -62,7 +62,9 @@ export const categorizeDocuments = ({ search }: { search: ISearchGeneric }) =>
maxCategoriesCount,
});
- const { rawResponse } = await lastValueFrom(search({ params: requestParams }));
+ const { rawResponse } = await lastValueFrom(
+ search({ params: requestParams }, { abortSignal: signal })
+ );
if (rawResponse.aggregations == null) {
throw new Error('No aggregations found in large categories response');
From b028eabcb2be50313ec0edf82148bb3efc7d924b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Fri, 6 Sep 2024 20:01:03 +0000
Subject: [PATCH 26/74] Add empty state screen
---
.../log_categories_result_content.tsx | 39 +++++++++++++++++--
1 file changed, 36 insertions(+), 3 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
index ab82051b9f635..72413b2004c65 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { EuiEmptyPrompt } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import React from 'react';
import { LogCategory } from '../../types';
import { LogCategoriesGrid, LogCategoriesGridDependencies } from './log_categories_grid';
@@ -20,9 +22,40 @@ export const LogCategoriesResultContent: React.FC {
+ if (logCategories.length === 0) {
+ return ;
+ } else {
+ return (
+
+
+
+ );
+ }
+};
+
+export const LogCategoriesEmptyResultContent: React.FC = () => {
return (
-
-
-
+ {emptyResultContentDescription}
}
+ color="subdued"
+ layout="horizontal"
+ title={{emptyResultContentTitle}
}
+ titleSize="m"
+ />
);
};
+
+const emptyResultContentTitle = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.emptyResultContentTitle',
+ {
+ defaultMessage: 'No log categories found',
+ }
+);
+
+const emptyResultContentDescription = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategories.emptyResultContentDescription',
+ {
+ defaultMessage:
+ 'No suitable documents within the time range. Try searching for a longer time period.',
+ }
+);
From 6aad1a15fe6b6e77cd9a9b7edfedaeeb8069bf7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Thu, 12 Sep 2024 15:15:25 +0000
Subject: [PATCH 27/74] Fix race condition when rendering apm service details
---
.../observability/logs_overview/index.ts | 3 +++
.../src/components/logs_overview/index.ts | 2 ++
.../public/components/app/service_logs/index.tsx | 14 +++++++++++---
.../public/components/logs_overview.tsx | 16 ++++++++++++++++
4 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/index.ts b/x-pack/packages/observability/logs_overview/index.ts
index e937c4fbc3fea..057d1d3acd152 100644
--- a/x-pack/packages/observability/logs_overview/index.ts
+++ b/x-pack/packages/observability/logs_overview/index.ts
@@ -7,7 +7,10 @@
export {
LogsOverview,
+ LogsOverviewErrorContent,
+ LogsOverviewLoadingContent,
type LogsOverviewDependencies,
+ type LogsOverviewErrorContentProps,
type LogsOverviewProps,
} from './src/components/logs_overview';
export type {
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts b/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts
index 627cdc8447eea..878f634f078ad 100644
--- a/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/index.ts
@@ -6,3 +6,5 @@
*/
export * from './logs_overview';
+export * from './logs_overview_error_content';
+export * from './logs_overview_loading_content';
diff --git a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
index b2c479f46f32b..a1dadbf186b91 100644
--- a/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
+++ b/x-pack/plugins/observability_solution/apm/public/components/app/service_logs/index.tsx
@@ -14,7 +14,7 @@ import { CONTAINER_ID, SERVICE_ENVIRONMENT, SERVICE_NAME } from '../../../../com
import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
import { useKibana } from '../../../context/kibana_context/use_kibana';
import { useAnyOfApmParams } from '../../../hooks/use_apm_params';
-import { useFetcher } from '../../../hooks/use_fetcher';
+import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher';
import { useTimeRange } from '../../../hooks/use_time_range';
import { APIReturnType } from '../../../services/rest/create_call_apm_api';
@@ -86,7 +86,7 @@ export function ServiceLogsOverview() {
const { start, end } = useTimeRange({ rangeFrom, rangeTo });
const timeRange = useMemo(() => ({ start, end }), [start, end]);
- const { data: logFilters } = useFetcher(
+ const { data: logFilters, status } = useFetcher(
async (callApmApi) => {
if (start == null || end == null) {
return;
@@ -112,7 +112,15 @@ export function ServiceLogsOverview() {
[environment, kuery, serviceName, start, end]
);
- return ;
+ if (status === FETCH_STATUS.SUCCESS) {
+ return ;
+ } else if (status === FETCH_STATUS.FAILURE) {
+ return (
+
+ );
+ } else {
+ return ;
+ }
}
export function getInfrastructureKQLFilter({
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx
index e0baff2237ff6..88bb77899d6d2 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx
+++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx
@@ -18,6 +18,18 @@ const LazyLogsOverview = dynamic(() =>
import('@kbn/observability-logs-overview').then((mod) => ({ default: mod.LogsOverview }))
);
+const LazyLogsOverviewErrorContent = dynamic(() =>
+ import('@kbn/observability-logs-overview').then((mod) => ({
+ default: mod.LogsOverviewErrorContent,
+ }))
+);
+
+const LazyLogsOverviewLoadingContent = dynamic(() =>
+ import('@kbn/observability-logs-overview').then((mod) => ({
+ default: mod.LogsOverviewLoadingContent,
+ }))
+);
+
export type LogsOverviewProps = Omit;
export const createLogsOverview = (dependencies: LogsOverviewDependencies) => {
@@ -34,6 +46,10 @@ export const createLogsOverview = (dependencies: LogsOverviewDependencies) => {
return useObservable(isEnabled$, defaultIsEnabled);
};
+ SelfContainedLogsOverview.ErrorContent = LazyLogsOverviewErrorContent;
+
+ SelfContainedLogsOverview.LoadingContent = LazyLogsOverviewLoadingContent;
+
return SelfContainedLogsOverview;
};
From 7f1fb14e6db3788fa2232fe9319fa1911fbed289 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Thu, 12 Sep 2024 15:19:43 +0000
Subject: [PATCH 28/74] Improve loading content in hosts
---
.../hosts/components/tabs/logs/logs_tab_content.tsx | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
index 3752e284c1b62..78443c9a6ec81 100644
--- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
+++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
@@ -128,7 +128,7 @@ const LogsTabLogsOverviewContent = () => {
[parsedDateRange.from, parsedDateRange.to]
);
- const { hostNodes, loading } = useHostsViewContext();
+ const { hostNodes, loading, error } = useHostsViewContext();
const logFilters = useMemo(
() => [
buildCombinedAssetFilter({
@@ -140,10 +140,12 @@ const LogsTabLogsOverviewContent = () => {
);
if (loading) {
- return ;
+ return ;
+ } else if (error != null) {
+ return ;
+ } else {
+ return ;
}
-
- return ;
};
const LogsTabLoadingContent = () => (
From 4e95f041a98580bef37a4b38554f20c077f553d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 16 Sep 2024 10:42:15 +0000
Subject: [PATCH 29/74] Update XState v5 version
---
package.json | 6 +++---
yarn.lock | 16 ++++++++--------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/package.json b/package.json
index 00e06d9a25017..219887b00390b 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
"**/sharp": "0.32.6",
"**/typescript": "5.1.6",
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0",
- "@xstate5/react/**/xstate": "^5.18.0",
+ "@xstate5/react/**/xstate": "^5.18.1",
"globby/fast-glob": "^3.2.11"
},
"dependencies": {
@@ -1025,7 +1025,7 @@
"@turf/helpers": "6.0.1",
"@turf/length": "^6.0.2",
"@xstate/react": "^3.2.2",
- "@xstate5/react": "npm:@xstate/react@^4.1.1",
+ "@xstate5/react": "npm:@xstate/react@^4.1.2",
"adm-zip": "^0.5.9",
"ai": "^2.2.33",
"ajv": "^8.12.0",
@@ -1261,7 +1261,7 @@
"whatwg-fetch": "^3.0.0",
"xml2js": "^0.5.0",
"xstate": "^4.38.2",
- "xstate5": "npm:xstate@^5.18.0",
+ "xstate5": "npm:xstate@^5.18.1",
"xterm": "^5.1.0",
"yauzl": "^2.10.0",
"yazl": "^2.5.1",
diff --git a/yarn.lock b/yarn.lock
index 80a3a0d7335a8..d0078333007bf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -12022,10 +12022,10 @@
use-isomorphic-layout-effect "^1.1.2"
use-sync-external-store "^1.0.0"
-"@xstate5/react@npm:@xstate/react@^4.1.1":
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/@xstate/react/-/react-4.1.1.tgz#2f580fc5f83d195f95b56df6cd8061c66660d9fa"
- integrity sha512-pFp/Y+bnczfaZ0V8B4LOhx3d6Gd71YKAPbzerGqydC2nsYN/mp7RZu3q/w6/kvI2hwR/jeDeetM7xc3JFZH2NA==
+"@xstate5/react@npm:@xstate/react@^4.1.2":
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/@xstate/react/-/react-4.1.2.tgz#4bfcdf2d9e9ef1eaea7388d1896649345e6679cd"
+ integrity sha512-orAidFrKCrU0ZwN5l/ABPlBfW2ziRDT2RrYoktRlZ0WRoLvA2E/uAC1JpZt43mCLtc8jrdwYCgJiqx1V8NvGTw==
dependencies:
use-isomorphic-layout-effect "^1.1.2"
use-sync-external-store "^1.2.0"
@@ -32640,10 +32640,10 @@ xpath@^0.0.33:
resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.33.tgz#5136b6094227c5df92002e7c3a13516a5074eb07"
integrity sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==
-"xstate5@npm:xstate@^5.18.0", xstate@^5.18.0:
- version "5.18.0"
- resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.18.0.tgz#6f559301dc2e1e4db39642ac5591bb7eb005335b"
- integrity sha512-MKlq/jhyFBYm6Z9+P0k9nhMrHYTTg1ZGmhMw8tVe67oDq9nIlEf2/u/bY5kvUvqu4LTCiVl67hnfd92RMLRyVg==
+"xstate5@npm:xstate@^5.18.1", xstate@^5.18.1:
+ version "5.18.1"
+ resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.18.1.tgz#c4d43ceaba6e6c31705d36bd96e285de4be4f7f4"
+ integrity sha512-m02IqcCQbaE/kBQLunwub/5i8epvkD2mFutnL17Oeg1eXTShe1sRF4D5mhv1dlaFO4vbW5gRGRhraeAD5c938g==
xstate@^4.38.2:
version "4.38.2"
From a6ab7e1beb74d0c00ed79d222675208f07202f66 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 16 Sep 2024 11:36:47 +0000
Subject: [PATCH 30/74] Clean up TODOs and comments
---
.../log_categories/log_categories_grid_pattern_cell.tsx | 7 +++----
.../categorize_logs_service/categorize_documents.ts | 1 -
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
index ae5471ef0868e..ce01bc8136416 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
@@ -34,8 +34,6 @@ export const LogCategoriesGridPatternCell: React.FC
+ …
{termsList.map((term, index) => (
{term}
- {index !== termsList.length - 1 && *
}
+ …
))}
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
index 9d1ba3f2cbc44..7260efe63d435 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/categorize_documents.ts
@@ -274,7 +274,6 @@ const esCategoryBucketSchema = z.object({
type EsCategoryBucket = z.output;
-// TODO: implement rarity criteria
const isRareInHistogram = (histogram: EsHistogram): boolean =>
histogram.filter((bucket) => bucket.documentCount > 0).length <
histogram.length * rarityThreshold;
From 5bbda50195f57fdf71922a93a76be7270cbb84ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 16 Sep 2024 11:37:51 +0000
Subject: [PATCH 31/74] Remove temporary test
---
.../scenarios/helpers/unstructured_logs.test.ts | 17 -----------------
1 file changed, 17 deletions(-)
delete mode 100644 packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.test.ts
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.test.ts b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.test.ts
deleted file mode 100644
index 2fdf52254540d..0000000000000
--- a/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.test.ts
+++ /dev/null
@@ -1,17 +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 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 { generateUnstructuredLogMessage } from './unstructured_logs';
-
-test('generates unstructured logs', () => {
- const messages = Array(10)
- .fill('')
- .map(() => generateUnstructuredLogMessage()());
-
- console.log(messages);
-});
From 7843215e248b9c22c7ef4085624f999b2afef752 Mon Sep 17 00:00:00 2001
From: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Date: Mon, 16 Sep 2024 11:50:13 +0000
Subject: [PATCH 32/74] [CI] Auto-commit changed files from 'node
scripts/notice'
---
packages/kbn-apm-synthtrace/tsconfig.json | 1 +
.../plugins/observability_solution/logs_shared/tsconfig.json | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/packages/kbn-apm-synthtrace/tsconfig.json b/packages/kbn-apm-synthtrace/tsconfig.json
index d0f5c5801597a..db93e36421b83 100644
--- a/packages/kbn-apm-synthtrace/tsconfig.json
+++ b/packages/kbn-apm-synthtrace/tsconfig.json
@@ -10,6 +10,7 @@
"@kbn/apm-synthtrace-client",
"@kbn/dev-utils",
"@kbn/elastic-agent-utils",
+ "@kbn/zod",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json
index 38cbba7c252c0..788f55c9b6fc5 100644
--- a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json
+++ b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json
@@ -44,5 +44,9 @@
"@kbn/logs-data-access-plugin",
"@kbn/core-deprecations-common",
"@kbn/core-deprecations-server",
+ "@kbn/management-settings-ids",
+ "@kbn/observability-logs-overview",
+ "@kbn/charts-plugin",
+ "@kbn/core-ui-settings-common",
]
}
From e3f30430be6010143d4f524af0c1488be6d1fb22 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 16 Sep 2024 12:27:11 +0000
Subject: [PATCH 33/74] Update license headers
---
.../src/lib/gaussian_events.ts | 9 +++++----
.../src/lib/poisson_events.test.ts | 9 +++++----
.../src/lib/poisson_events.ts | 9 +++++----
.../src/scenarios/distributed_unstructured_logs.ts | 10 ++++++----
.../src/scenarios/helpers/unstructured_logs.ts | 10 ++++++----
packages/kbn-xstate-utils/src/console_inspector.ts | 9 +++++----
6 files changed, 32 insertions(+), 24 deletions(-)
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts b/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts
index c13ebbaafe368..4f1db28017d29 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/gaussian_events.ts
@@ -1,9 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
*/
import { castArray } from 'lodash';
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts
index 580e89e2b2208..0741884550f32 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.test.ts
@@ -1,9 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
*/
import { PoissonEvents } from './poisson_events';
diff --git a/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.ts b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.ts
index 05d8d8793d1b8..e7fd24b8323e7 100644
--- a/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.ts
+++ b/packages/kbn-apm-synthtrace-client/src/lib/poisson_events.ts
@@ -1,9 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
*/
import { castArray } from 'lodash';
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts
index 24bb5e95a2987..83860635ae64a 100644
--- a/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts
+++ b/packages/kbn-apm-synthtrace/src/scenarios/distributed_unstructured_logs.ts
@@ -1,10 +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 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.
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
*/
+
import { infra, LogDocument, log } from '@kbn/apm-synthtrace-client';
import { fakerEN as faker } from '@faker-js/faker';
import { z } from '@kbn/zod';
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts
index 0cfc88ac55a83..490bd449e2b60 100644
--- a/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts
+++ b/packages/kbn-apm-synthtrace/src/scenarios/helpers/unstructured_logs.ts
@@ -1,10 +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 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.
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
*/
+
import { Faker, faker } from '@faker-js/faker';
export type LogMessageGenerator = (f: Faker) => string[];
diff --git a/packages/kbn-xstate-utils/src/console_inspector.ts b/packages/kbn-xstate-utils/src/console_inspector.ts
index b280fc90609c8..8792ab44f3c28 100644
--- a/packages/kbn-xstate-utils/src/console_inspector.ts
+++ b/packages/kbn-xstate-utils/src/console_inspector.ts
@@ -1,9 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
*/
import {
From 34022938147f455c8f2b15e926d85df73b1552eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 16 Sep 2024 12:27:26 +0000
Subject: [PATCH 34/74] Remove unused import
---
.../log_categories/log_categories_grid_pattern_cell.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
index ce01bc8136416..87155e02ea048 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiDataGridColumn, euiFontSize, useEuiTheme } from '@elastic/eui';
+import { EuiDataGridColumn, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
From 542d1f1048f43c9e6f89f2f3a5106c4f7486ad3d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 16 Sep 2024 15:31:31 +0000
Subject: [PATCH 35/74] Revert term separators back to asterisks
---
.../log_categories/log_categories_grid_pattern_cell.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
index 87155e02ea048..d507487a99e3c 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
@@ -48,11 +48,11 @@ export const LogCategoriesGridPatternCell: React.FC
- …
+ *
{termsList.map((term, index) => (
{term}
- …
+ *
))}
From eb089a655735608aa99a65d5d4b79a4970110566 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 16 Sep 2024 19:15:34 +0000
Subject: [PATCH 36/74] Add a logs_overview mock
---
.../public/components/logs_overview/index.tsx | 8 +++++
.../logs_overview/logs_overview.mock.tsx | 32 +++++++++++++++++++
.../{ => logs_overview}/logs_overview.tsx | 18 +++++++++--
.../logs_shared/public/mocks.tsx | 2 ++
4 files changed, 57 insertions(+), 3 deletions(-)
create mode 100644 x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/index.tsx
create mode 100644 x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.mock.tsx
rename x-pack/plugins/observability_solution/logs_shared/public/components/{ => logs_overview}/logs_overview.tsx (76%)
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/index.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/index.tsx
new file mode 100644
index 0000000000000..627cdc8447eea
--- /dev/null
+++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/index.tsx
@@ -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 './logs_overview';
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.mock.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.mock.tsx
new file mode 100644
index 0000000000000..435766bff793d
--- /dev/null
+++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.mock.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import type {
+ LogsOverviewProps,
+ SelfContainedLogsOverviewComponent,
+ SelfContainedLogsOverviewHelpers,
+} from './logs_overview';
+
+export const createLogsOverviewMock = () => {
+ const LogsOverviewMock = jest.fn(LogsOverviewMockImpl) as unknown as ILogsOverviewMock;
+
+ LogsOverviewMock.useIsEnabled = jest.fn(() => true);
+
+ LogsOverviewMock.ErrorContent = jest.fn(() => );
+
+ LogsOverviewMock.LoadingContent = jest.fn(() => );
+
+ return LogsOverviewMock;
+};
+
+const LogsOverviewMockImpl = (_props: LogsOverviewProps) => {
+ return ;
+};
+
+type ILogsOverviewMock = jest.Mocked &
+ jest.Mocked;
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.tsx
similarity index 76%
rename from x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx
rename to x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.tsx
index 88bb77899d6d2..7b60aee5be57c 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview.tsx
+++ b/x-pack/plugins/observability_solution/logs_shared/public/components/logs_overview/logs_overview.tsx
@@ -9,6 +9,7 @@ import { OBSERVABILITY_LOGS_SHARED_NEW_LOGS_OVERVIEW_ID } from '@kbn/management-
import type {
LogsOverviewProps as FullLogsOverviewProps,
LogsOverviewDependencies,
+ LogsOverviewErrorContentProps,
} from '@kbn/observability-logs-overview';
import { dynamic } from '@kbn/shared-ux-utility';
import React from 'react';
@@ -32,7 +33,20 @@ const LazyLogsOverviewLoadingContent = dynamic(() =>
export type LogsOverviewProps = Omit;
-export const createLogsOverview = (dependencies: LogsOverviewDependencies) => {
+export interface SelfContainedLogsOverviewHelpers {
+ useIsEnabled: () => boolean;
+ ErrorContent: React.ComponentType;
+ LoadingContent: React.ComponentType;
+}
+
+export type SelfContainedLogsOverviewComponent = React.ComponentType;
+
+export type SelfContainedLogsOverview = SelfContainedLogsOverviewComponent &
+ SelfContainedLogsOverviewHelpers;
+
+export const createLogsOverview = (
+ dependencies: LogsOverviewDependencies
+): SelfContainedLogsOverview => {
const SelfContainedLogsOverview = (props: LogsOverviewProps) => {
return ;
};
@@ -54,5 +68,3 @@ export const createLogsOverview = (dependencies: LogsOverviewDependencies) => {
};
const defaultIsEnabled = false;
-
-export type SelfContainedLogsOverview = ReturnType;
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/mocks.tsx b/x-pack/plugins/observability_solution/logs_shared/public/mocks.tsx
index a9b0ebd6a6aa3..ffb867abbcc17 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/mocks.tsx
+++ b/x-pack/plugins/observability_solution/logs_shared/public/mocks.tsx
@@ -6,12 +6,14 @@
*/
import { createLogAIAssistantMock } from './components/log_ai_assistant/log_ai_assistant.mock';
+import { createLogsOverviewMock } from './components/logs_overview/logs_overview.mock';
import { createLogViewsServiceStartMock } from './services/log_views/log_views_service.mock';
import { LogsSharedClientStartExports } from './types';
export const createLogsSharedPluginStartMock = (): jest.Mocked => ({
logViews: createLogViewsServiceStartMock(),
LogAIAssistant: createLogAIAssistantMock(),
+ LogsOverview: createLogsOverviewMock(),
});
export const _ensureTypeCompatibility = (): LogsSharedClientStartExports =>
From 78b0d4cb042a6cbb73065a2879828ac717be2291 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Mon, 16 Sep 2024 20:19:31 +0000
Subject: [PATCH 37/74] Fix telemetry schema
---
.../server/collectors/management/schema.ts | 6 ++++++
.../server/collectors/management/types.ts | 1 +
src/plugins/telemetry/schema/oss_plugins.json | 6 ++++++
3 files changed, 13 insertions(+)
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
index d1ab81f3e60a7..0b7b0b6bbb894 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
@@ -694,4 +694,10 @@ export const stackManagementSchema: MakeSchemaFrom = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
+ 'observability:newLogsOverview': {
+ type: 'boolean',
+ _meta: {
+ description: 'Enable the new logs overview component.',
+ },
+ },
};
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
index c66f4f07a296e..355619b50dbe4 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
@@ -55,6 +55,7 @@ export interface UsageStats {
'observability:apmEnableServiceInventoryTableSearchBar': boolean;
'observability:logsExplorer:allowedDataViews': string[];
'observability:logSources': string[];
+ 'observability:newLogsOverview': boolean;
'observability:aiAssistantLogsIndexPattern': string;
'observability:aiAssistantSimulatedFunctionCalling': boolean;
'observability:aiAssistantSearchConnectorIndexPattern': string;
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index 70fbeec73bc5d..e000b3f510d36 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -10630,6 +10630,12 @@
"_meta": {
"description": "Non-default value of setting."
}
+ },
+ "observability:newLogsOverview": {
+ "type": "boolean",
+ "_meta": {
+ "description": "Enable the new logs overview component."
+ }
}
}
},
From c1962c0c0a187eed2faad49d8a9bd5ec3122a9d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Wed, 18 Sep 2024 16:18:37 +0000
Subject: [PATCH 38/74] Add link to open all log entries in discover
---
.../discover_link/discover_link.tsx | 108 ++++++++++++++++++
.../src/components/discover_link/index.ts | 8 ++
.../log_categories/log_categories.tsx | 3 +
.../log_categories_control_bar.tsx | 44 +++++++
.../log_categories_result_content.tsx | 36 +++++-
.../logs_shared/public/plugin.ts | 12 +-
6 files changed, 204 insertions(+), 7 deletions(-)
create mode 100644 x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/discover_link/index.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx
diff --git a/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx
new file mode 100644
index 0000000000000..f921cc8223424
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx
@@ -0,0 +1,108 @@
+/*
+ * 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 { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import { EuiButton } from '@elastic/eui';
+import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common';
+import { FilterStateStore, buildCustomFilter } from '@kbn/es-query';
+import { i18n } from '@kbn/i18n';
+import { getRouterLinkProps } from '@kbn/router-utils';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
+import React, { useCallback, useMemo } from 'react';
+import type { IndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+
+export interface DiscoverLinkProps {
+ documentFilters?: QueryDslQueryContainer[];
+ logsSource: IndexNameLogsSourceConfiguration;
+ timeRange: {
+ start: string;
+ end: string;
+ };
+ dependencies: DiscoverLinkDependencies;
+}
+
+export interface DiscoverLinkDependencies {
+ share: SharePluginStart;
+}
+
+export const DiscoverLink = React.memo(
+ ({ dependencies: { share }, documentFilters, logsSource, timeRange }: DiscoverLinkProps) => {
+ const discoverLocatorParams = useMemo(
+ () => ({
+ dataViewSpec: {
+ title: logsSource.indexName,
+ timeFieldName: logsSource.timestampField,
+ },
+ timeRange: {
+ from: timeRange.start,
+ to: timeRange.end,
+ },
+ filters: documentFilters?.map((filter) =>
+ buildCustomFilter(
+ logsSource.indexName,
+ filter,
+ false,
+ false,
+ categorizedLogsFilterLabel,
+ FilterStateStore.APP_STATE
+ )
+ ),
+ }),
+ [
+ documentFilters,
+ logsSource.indexName,
+ logsSource.timestampField,
+ timeRange.end,
+ timeRange.start,
+ ]
+ );
+
+ const discoverLocator = useMemo(
+ () => share.url.locators.get('DISCOVER_APP_LOCATOR'),
+ [share.url.locators]
+ );
+
+ const discoverUrl = useMemo(
+ () => discoverLocator?.getRedirectUrl(discoverLocatorParams),
+ [discoverLocatorParams, discoverLocator]
+ );
+
+ const navigateToDiscover = useCallback(() => {
+ discoverLocator?.navigate(discoverLocatorParams);
+ }, [discoverLocatorParams, discoverLocator]);
+
+ const discoverLinkProps = getRouterLinkProps({
+ href: discoverUrl,
+ onClick: navigateToDiscover,
+ });
+
+ return (
+
+ {discoverLinkTitle}
+
+ );
+ }
+);
+
+export const discoverLinkTitle = i18n.translate(
+ 'xpack.observabilityLogsOverview.discoverLinkTitle',
+ {
+ defaultMessage: 'Open in Discover',
+ }
+);
+
+export const categorizedLogsFilterLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.categorizedLogsFilterLabel',
+ {
+ defaultMessage: 'Categorized log entries',
+ }
+);
diff --git a/x-pack/packages/observability/logs_overview/src/components/discover_link/index.ts b/x-pack/packages/observability/logs_overview/src/components/discover_link/index.ts
new file mode 100644
index 0000000000000..738bf51d4529d
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/discover_link/index.ts
@@ -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 './discover_link';
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
index 93002bd388384..6204667827281 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
@@ -71,7 +71,10 @@ export const LogCategories: React.FC = ({
return (
);
} else if (categorizeLogsServiceState.matches('failed')) {
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx
new file mode 100644
index 0000000000000..4538b0ec2fd5d
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx
@@ -0,0 +1,44 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
+import React from 'react';
+import type { IndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+import { DiscoverLink } from '../discover_link';
+
+export interface LogCategoriesControlBarProps {
+ documentFilters?: QueryDslQueryContainer[];
+ logsSource: IndexNameLogsSourceConfiguration;
+ timeRange: {
+ start: string;
+ end: string;
+ };
+ dependencies: LogCategoriesControlBarDependencies;
+}
+
+export interface LogCategoriesControlBarDependencies {
+ share: SharePluginStart;
+}
+
+export const LogCategoriesControlBar: React.FC = React.memo(
+ ({ dependencies, documentFilters, logsSource, timeRange }) => {
+ return (
+
+
+
+
+
+ );
+ }
+);
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
index 72413b2004c65..e16bdda7cb44a 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
@@ -5,30 +5,56 @@
* 2.0.
*/
-import { EuiEmptyPrompt } from '@elastic/eui';
+import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import { LogCategory } from '../../types';
+import { IndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+import {
+ LogCategoriesControlBar,
+ LogCategoriesControlBarDependencies,
+} from './log_categories_control_bar';
import { LogCategoriesGrid, LogCategoriesGridDependencies } from './log_categories_grid';
export interface LogCategoriesResultContentProps {
dependencies: LogCategoriesResultContentDependencies;
+ documentFilters?: QueryDslQueryContainer[];
logCategories: LogCategory[];
+ logsSource: IndexNameLogsSourceConfiguration;
+ timeRange: {
+ start: string;
+ end: string;
+ };
}
-export type LogCategoriesResultContentDependencies = LogCategoriesGridDependencies;
+export type LogCategoriesResultContentDependencies = LogCategoriesControlBarDependencies &
+ LogCategoriesGridDependencies;
export const LogCategoriesResultContent: React.FC = ({
dependencies,
+ documentFilters,
logCategories,
+ logsSource,
+ timeRange,
}) => {
if (logCategories.length === 0) {
return ;
} else {
return (
-
-
-
+
+
+
+
+
+
+
+
);
}
};
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
index 7cd0a9ab5258b..fc17e9b17cc82 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
@@ -53,8 +53,15 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
public start(core: CoreStart, plugins: LogsSharedClientStartDeps) {
const { http, settings } = core;
- const { charts, data, dataViews, discoverShared, logsDataAccess, observabilityAIAssistant } =
- plugins;
+ const {
+ charts,
+ data,
+ dataViews,
+ discoverShared,
+ logsDataAccess,
+ observabilityAIAssistant,
+ share,
+ } = plugins;
const logViews = this.logViews.start({
http,
@@ -68,6 +75,7 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
logsDataAccess,
search: data.search.search,
uiSettings: settings,
+ share,
});
if (!observabilityAIAssistant) {
From 90282ccdd37ae5a4cf4b45fdd9cf5ce25eed58cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Wed, 18 Sep 2024 16:24:14 +0000
Subject: [PATCH 39/74] Fix dataview spec passed to locator
---
.../src/components/discover_link/discover_link.tsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx
index f921cc8223424..fe108289985a9 100644
--- a/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx
@@ -34,6 +34,8 @@ export const DiscoverLink = React.memo(
const discoverLocatorParams = useMemo(
() => ({
dataViewSpec: {
+ id: logsSource.indexName,
+ name: logsSource.indexName,
title: logsSource.indexName,
timeFieldName: logsSource.timestampField,
},
From 1a90cb864306831dc43b5f14ea8b812332e7843c Mon Sep 17 00:00:00 2001
From: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Date: Wed, 18 Sep 2024 16:54:28 +0000
Subject: [PATCH 40/74] [CI] Auto-commit changed files from 'node
scripts/notice'
---
x-pack/packages/observability/logs_overview/tsconfig.json | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/x-pack/packages/observability/logs_overview/tsconfig.json b/x-pack/packages/observability/logs_overview/tsconfig.json
index 37fb31cdc68c2..886062ae8855f 100644
--- a/x-pack/packages/observability/logs_overview/tsconfig.json
+++ b/x-pack/packages/observability/logs_overview/tsconfig.json
@@ -31,5 +31,9 @@
"@kbn/ml-random-sampler-utils",
"@kbn/zod",
"@kbn/calculate-auto",
+ "@kbn/discover-plugin",
+ "@kbn/es-query",
+ "@kbn/router-utils",
+ "@kbn/share-plugin",
]
}
From 598d9a9b82d3d5396c0c163d35b72ceeba1a8d04 Mon Sep 17 00:00:00 2001
From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com>
Date: Thu, 3 Oct 2024 11:19:21 +0100
Subject: [PATCH 41/74] package.json changes from yarn kbn bootstrap
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 1d736eb483855..4f05ba83b424b 100644
--- a/package.json
+++ b/package.json
@@ -94,9 +94,9 @@
"**/sharp": "0.32.6",
"**/typescript": "5.1.6",
"@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0",
- "@xstate5/react/**/xstate": "^5.18.1",
"@types/react": "~18.2.0",
"@types/react-dom": "~18.2.0",
+ "@xstate5/react/**/xstate": "^5.18.1",
"globby/fast-glob": "^3.2.11"
},
"dependencies": {
From a34ab227e0b9322c31f3c2825aec6a344a9345a6 Mon Sep 17 00:00:00 2001
From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com>
Date: Thu, 3 Oct 2024 14:28:41 +0100
Subject: [PATCH 42/74] Fix two types
---
.../components/log_categories/log_categories_error_content.tsx | 2 +-
.../components/logs_overview/logs_overview_error_content.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx
index 445eef7802598..1a335e3265294 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_error_content.tsx
@@ -21,7 +21,7 @@ export const LogCategoriesErrorContent: React.FC
title={{logsOverviewErrorTitle}
}
body={
- {error?.stack ?? error ?? unknownErrorDescription}
+ {error?.stack ?? error?.toString() ?? unknownErrorDescription}
}
layout="vertical"
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx
index 0f3c08d536884..73586756bb908 100644
--- a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview_error_content.tsx
@@ -21,7 +21,7 @@ export const LogsOverviewErrorContent: React.FC =
title={{logsOverviewErrorTitle}
}
body={
- {error?.stack ?? error ?? unknownErrorDescription}
+ {error?.stack ?? error?.toString() ?? unknownErrorDescription}
}
layout="vertical"
From c6d26db8c88cc22f47dd72a029979f24ef11ed36 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Felix=20St=C3=BCrmer?=
Date: Wed, 9 Oct 2024 14:54:44 +0000
Subject: [PATCH 43/74] Use timeField parameter instead of hard-coded field
name
---
.../src/services/categorize_logs_service/queries.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
index 849567895f934..aef12da303bcc 100644
--- a/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
+++ b/x-pack/packages/observability/logs_overview/src/services/categorize_logs_service/queries.ts
@@ -97,7 +97,7 @@ export const createCategorizationRequestParams = ({
aggs: randomSampler.wrap({
histogram: {
date_histogram: {
- field: '@timestamp',
+ field: timeField,
fixed_interval: fixedIntervalSize,
extended_bounds: {
min: startTimestamp,
From 7e34bc030322061162054543afbc81ae7a588429 Mon Sep 17 00:00:00 2001
From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com>
Date: Mon, 30 Sep 2024 14:35:19 +0100
Subject: [PATCH 44/74] Add a flyout to show category document examples
---
.../data_types/logs/cell_actions_popover.tsx | 50 ++---
.../logs/summary_column/summary_column.tsx | 7 +-
src/plugins/discover/public/plugin.tsx | 28 +++
src/plugins/discover/public/types.ts | 6 +
.../discover_link/discover_link.tsx | 4 +-
.../log_categories/log_categories.tsx | 49 ++++-
.../log_categories_control_bar.tsx | 4 +-
.../log_categories/log_categories_grid.tsx | 30 +++
.../log_categories_grid_cell.tsx | 2 +-
.../log_categories_grid_expand_button.tsx | 71 +++++++
.../log_categories_grid_pattern_cell.tsx | 37 +---
.../log_categories_result_content.tsx | 38 +++-
.../log_category_details_error_content.tsx | 41 ++++
.../log_category_details_flyout.tsx | 137 +++++++++++++
.../log_category_details_loading_content.tsx | 19 ++
.../log_category_document_examples_table.tsx | 139 +++++++++++++
.../logs_overview/logs_overview.tsx | 10 +-
.../shared/log_category_pattern.tsx | 50 +++++
.../category_details_service.ts | 191 ++++++++++++++++++
.../category_documents.ts | 63 ++++++
.../category_details_service/index.ts | 8 +
.../category_details_service/queries.ts | 58 ++++++
.../category_details_service/types.ts | 31 +++
.../logs_overview/src/utils/log_category.ts | 12 ++
.../logs_overview/src/utils/logs_source.ts | 53 ++++-
.../observability/logs_overview/tsconfig.json | 2 +
.../logs_shared/kibana.jsonc | 2 +
.../logs_shared/public/plugin.ts | 7 +
.../logs_shared/public/types.ts | 4 +
.../logs_shared/tsconfig.json | 1 +
30 files changed, 1072 insertions(+), 82 deletions(-)
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_expand_button.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_error_content.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_flyout.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_loading_content.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_document_examples_table.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/components/shared/log_category_pattern.tsx
create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/category_details_service.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/category_documents.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/index.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/services/category_details_service/types.ts
create mode 100644 x-pack/packages/observability/logs_overview/src/utils/log_category.ts
diff --git a/src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx b/src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx
index 7b9d68e8f3dd7..1858bb2323637 100644
--- a/src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx
+++ b/src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx
@@ -109,30 +109,32 @@ export function CellActionsPopover({
/>
-
-
-
- {filterForText}
-
-
- {filterOutText}
-
-
-
+ {onFilter ? (
+
+
+
+ {filterForText}
+
+
+ {filterOutText}
+
+
+
+ ) : null}
{(copy) => (
diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.tsx b/src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.tsx
index ac7e20b944a4a..1e0d79d59aedc 100644
--- a/src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.tsx
+++ b/src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.tsx
@@ -39,8 +39,9 @@ export interface SummaryColumnFactoryDeps {
}
export type SummaryColumnProps = DataGridCellValueElementProps;
+export type AllSummaryColumnProps = SummaryColumnProps & SummaryColumnFactoryDeps;
-const SummaryColumn = (props: SummaryColumnProps & SummaryColumnFactoryDeps) => {
+const SummaryColumn = (props: AllSummaryColumnProps) => {
const { isDetails } = props;
if (isDetails) {
@@ -57,7 +58,7 @@ const SummaryCell = ({
density: maybeNullishDensity,
rowHeight: maybeNullishRowHeight,
...props
-}: SummaryColumnProps & SummaryColumnFactoryDeps) => {
+}: AllSummaryColumnProps) => {
const { onFilter, row } = props;
const density = maybeNullishDensity ?? DataGridDensity.COMPACT;
@@ -96,7 +97,7 @@ const SummaryCell = ({
);
};
-const SummaryCellPopover = (props: SummaryColumnProps & SummaryColumnFactoryDeps) => {
+const SummaryCellPopover = (props: AllSummaryColumnProps) => {
const { row, dataView, fieldFormats, onFilter, closePopover } = props;
const resourceFields = createResourceFields(row);
diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx
index dbbcc90a7d451..572888f872193 100644
--- a/src/plugins/discover/public/plugin.tsx
+++ b/src/plugins/discover/public/plugin.tsx
@@ -24,6 +24,9 @@ import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
import { SEARCH_EMBEDDABLE_TYPE, TRUNCATE_MAX_HEIGHT } from '@kbn/discover-utils';
import { SavedSearchAttributes, SavedSearchType } from '@kbn/saved-search-plugin/common';
import { i18n } from '@kbn/i18n';
+import { dynamic } from '@kbn/shared-ux-utility';
+import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
+import type { DataGridCellValueElementProps } from '@kbn/unified-data-table';
import { PLUGIN_ID } from '../common';
import { registerFeature } from './register_feature';
import { buildServices, UrlTracker } from './build_services';
@@ -60,6 +63,12 @@ import { DataSourceProfileService } from './context_awareness/profiles/data_sour
import { DocumentProfileService } from './context_awareness/profiles/document_profile';
import { ProfilesManager } from './context_awareness/profiles_manager';
import { DiscoverEBTManager } from './services/discover_ebt_manager';
+import type { AllSummaryColumnProps } from './components/data_types/logs/summary_column/summary_column';
+import { getLogLevelBadgeCell } from './components/data_types/logs/log_level_badge_cell';
+
+const LazySummaryColumn = dynamic(
+ () => import('./components/data_types/logs/summary_column/summary_column')
+);
/**
* Contains Discover, one of the oldest parts of Kibana
@@ -314,6 +323,25 @@ export class DiscoverPlugin
DiscoverContainer: (props: DiscoverContainerProps) => (
),
+ logColumns: {
+ SummaryColumn: (props: AllSummaryColumnProps) => {
+ return (
+
+
+
+ );
+ },
+ getLogLevelCell: (
+ logLevelField: string
+ ): React.ComponentType => {
+ const LogLevelCell = getLogLevelBadgeCell(logLevelField);
+ return (props: DataGridCellValueElementProps) => (
+
+
+
+ );
+ },
+ },
};
}
diff --git a/src/plugins/discover/public/types.ts b/src/plugins/discover/public/types.ts
index 3b24341e1a654..ea389ef25a67b 100644
--- a/src/plugins/discover/public/types.ts
+++ b/src/plugins/discover/public/types.ts
@@ -42,9 +42,11 @@ import type { AiopsPluginStart } from '@kbn/aiops-plugin/public';
import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public';
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
+import type { DataGridCellValueElementProps } from '@kbn/unified-data-table';
import { DiscoverAppLocator } from '../common';
import { DiscoverCustomizationContext } from './customizations';
import { type DiscoverContainerProps } from './components/discover_container';
+import type { AllSummaryColumnProps } from './components/data_types/logs/summary_column/summary_column';
/**
* @public
@@ -121,6 +123,10 @@ export interface DiscoverStart {
*/
readonly locator: undefined | DiscoverAppLocator;
readonly DiscoverContainer: ComponentType;
+ logColumns: {
+ SummaryColumn: (props: AllSummaryColumnProps) => React.JSX.Element;
+ getLogLevelCell: (logLevelField: string) => React.ComponentType;
+ };
}
/**
diff --git a/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx
index fe108289985a9..71dc7c26f343e 100644
--- a/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/discover_link/discover_link.tsx
@@ -13,11 +13,11 @@ import { i18n } from '@kbn/i18n';
import { getRouterLinkProps } from '@kbn/router-utils';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import React, { useCallback, useMemo } from 'react';
-import type { IndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+import type { ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source';
export interface DiscoverLinkProps {
documentFilters?: QueryDslQueryContainer[];
- logsSource: IndexNameLogsSourceConfiguration;
+ logsSource: ResolvedIndexNameLogsSourceConfiguration;
timeRange: {
start: string;
end: string;
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
index 6204667827281..e54cab30537fc 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories.tsx
@@ -14,18 +14,23 @@ import {
categorizeLogsService,
createCategorizeLogsServiceImplementations,
} from '../../services/categorize_logs_service';
-import { IndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+import { ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source';
import { LogCategoriesErrorContent } from './log_categories_error_content';
import { LogCategoriesLoadingContent } from './log_categories_loading_content';
import {
LogCategoriesResultContent,
LogCategoriesResultContentDependencies,
} from './log_categories_result_content';
+import {
+ categoryDetailsService,
+ createCategoryDetailsServiceImplementations,
+} from '../../services/category_details_service';
+import { LogCategory } from '../../types';
export interface LogCategoriesProps {
dependencies: LogCategoriesDependencies;
documentFilters?: QueryDslQueryContainer[];
- logsSource: IndexNameLogsSourceConfiguration;
+ logsSource: ResolvedIndexNameLogsSourceConfiguration;
// The time range could be made optional if we want to support an internal
// time range picker
timeRange: {
@@ -61,12 +66,49 @@ export const LogCategories: React.FC = ({
}
);
+ const [categoryDetailsServiceState, sendToCategoryDetailsService] = useMachine(
+ categoryDetailsService.provide(
+ createCategoryDetailsServiceImplementations({ search: dependencies.search })
+ ),
+ {
+ inspect: consoleInspector,
+ input: {
+ index: logsSource.indexName,
+ startTimestamp: timeRange.start,
+ endTimestamp: timeRange.end,
+ timeField: logsSource.timestampField,
+ messageField: logsSource.messageField,
+ additionalFilters: documentFilters,
+ dataView: logsSource.dataView,
+ },
+ }
+ );
+
const cancelOperation = useCallback(() => {
sendToCategorizeLogsService({
type: 'cancel',
});
}, [sendToCategorizeLogsService]);
+ const closeFlyout = useCallback(() => {
+ sendToCategoryDetailsService({
+ type: 'setExpandedCategory',
+ category: null,
+ rowIndex: null,
+ });
+ }, [sendToCategoryDetailsService]);
+
+ const expandCategory = useCallback(
+ (category: LogCategory | null, rowIndex: number | null) => {
+ sendToCategoryDetailsService({
+ type: 'setExpandedCategory',
+ category,
+ rowIndex,
+ });
+ },
+ [sendToCategoryDetailsService]
+ );
+
if (categorizeLogsServiceState.matches('done')) {
return (
= ({
logCategories={categorizeLogsServiceState.context.categories}
logsSource={logsSource}
timeRange={timeRange}
+ categoryDetailsServiceState={categoryDetailsServiceState}
+ onCloseFlyout={closeFlyout}
+ onExpandCategory={expandCategory}
/>
);
} else if (categorizeLogsServiceState.matches('failed')) {
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx
index 4538b0ec2fd5d..0ec9121b2c972 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_control_bar.tsx
@@ -9,12 +9,12 @@ import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/type
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import React from 'react';
-import type { IndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+import type { ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source';
import { DiscoverLink } from '../discover_link';
export interface LogCategoriesControlBarProps {
documentFilters?: QueryDslQueryContainer[];
- logsSource: IndexNameLogsSourceConfiguration;
+ logsSource: ResolvedIndexNameLogsSourceConfiguration;
timeRange: {
start: string;
end: string;
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
index d9e960685de99..6d4295c2534d1 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid.tsx
@@ -9,6 +9,7 @@ import {
EuiDataGrid,
EuiDataGridColumnSortingConfig,
EuiDataGridPaginationProps,
+ EuiScreenReaderOnly,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { createConsoleInspector } from '@kbn/xstate-utils';
@@ -25,17 +26,26 @@ import {
logCategoriesGridColumns,
renderLogCategoriesGridCell,
} from './log_categories_grid_cell';
+import { createLogCategoriesGridExpandButton } from './log_categories_grid_expand_button';
export interface LogCategoriesGridProps {
dependencies: LogCategoriesGridDependencies;
logCategories: LogCategory[];
+ expandedRowIndex: number | null;
+ onExpandCategory: (category: LogCategory, rowIndex: number) => void;
+ onCloseFlyout: () => void;
}
export type LogCategoriesGridDependencies = LogCategoriesGridCellDependencies;
+const DEFAULT_CONTROL_COLUMN_WIDTH = 40;
+
export const LogCategoriesGrid: React.FC = ({
dependencies,
logCategories,
+ expandedRowIndex,
+ onExpandCategory,
+ onCloseFlyout,
}) => {
const [gridState, dispatchGridEvent] = useMachine(gridStateService, {
input: {
@@ -93,6 +103,26 @@ export const LogCategoriesGrid: React.FC = ({
onSort: (sortingColumns) =>
dispatchGridEvent({ type: 'changeSortingColumns', sortingColumns }),
}}
+ leadingControlColumns={[
+ {
+ id: 'toggleFlyout',
+ width: DEFAULT_CONTROL_COLUMN_WIDTH,
+ headerCellRender: () => (
+
+
+ {i18n.translate('xpack.observabilityLogsOverview.controlColumnHeader', {
+ defaultMessage: 'Control column',
+ })}
+
+
+ ),
+ rowCellRender: createLogCategoriesGridExpandButton({
+ expandedRowIndex,
+ onExpandCategory,
+ onCloseFlyout,
+ }),
+ },
+ ]}
/>
);
};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx
index d6ab4969eaf7b..7e40d192df227 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_cell.tsx
@@ -83,7 +83,7 @@ export type LogCategoriesGridColumnId = (typeof logCategoriesGridColumns)[number
const cellContextKey = 'cellContext';
-const getCellContext = (cellContext: object): LogCategoriesGridCellContext =>
+export const getCellContext = (cellContext: object): LogCategoriesGridCellContext =>
(cellContextKey in cellContext
? cellContext[cellContextKey]
: {}) as LogCategoriesGridCellContext;
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_expand_button.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_expand_button.tsx
new file mode 100644
index 0000000000000..8b44ccf47cc1e
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_expand_button.tsx
@@ -0,0 +1,71 @@
+/*
+ * 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 { EuiButtonIcon, EuiToolTip, RenderCellValue } from '@elastic/eui';
+import React, { useCallback } from 'react';
+import { i18n } from '@kbn/i18n';
+import { getCellContext } from './log_categories_grid_cell';
+import { LogCategory } from '../../types';
+
+const buttonLabel = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoriesGrid.controlColumns.toggleFlyout',
+ {
+ defaultMessage: 'Toggle flyout with details',
+ }
+);
+
+interface CreateLogCategoriesGridExpandButtonProps {
+ expandedRowIndex: number | null;
+ onExpandCategory: (category: LogCategory, rowIndex: number) => void;
+ onCloseFlyout: () => void;
+}
+
+export const createLogCategoriesGridExpandButton =
+ ({
+ expandedRowIndex,
+ onExpandCategory,
+ onCloseFlyout,
+ }: CreateLogCategoriesGridExpandButtonProps): RenderCellValue =>
+ (props) => {
+ const { rowIndex } = props;
+ const { logCategories } = getCellContext(props);
+ const logCategory = logCategories[rowIndex];
+ const isCurrentRowExpanded = expandedRowIndex === rowIndex;
+ const onClickHandler = useCallback(() => {
+ if (isCurrentRowExpanded) {
+ onCloseFlyout();
+ } else {
+ onExpandCategory(logCategory, rowIndex);
+ }
+ }, [isCurrentRowExpanded, logCategory, rowIndex]);
+
+ return (
+
+ );
+ };
+
+interface ExpandButtonProps {
+ isCurrentRowExpanded: boolean;
+ onClickHandler: () => void;
+}
+
+const ExpandButton: React.FC = ({ isCurrentRowExpanded, onClickHandler }) => {
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
index d507487a99e3c..7507ab5b23f44 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_grid_pattern_cell.tsx
@@ -5,11 +5,11 @@
* 2.0.
*/
-import { EuiDataGridColumn, useEuiTheme } from '@elastic/eui';
-import { css } from '@emotion/react';
+import { EuiDataGridColumn } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import React, { useMemo } from 'react';
+import React from 'react';
import { LogCategory } from '../../types';
+import { LogCategoryPattern } from '../shared/log_category_pattern';
export const logCategoriesGridPatternColumn = {
id: 'pattern' as const,
@@ -27,34 +27,5 @@ export interface LogCategoriesGridPatternCellProps {
export const LogCategoriesGridPatternCell: React.FC = ({
logCategory,
}) => {
- const theme = useEuiTheme();
- const { euiTheme } = theme;
- const termsList = useMemo(() => logCategory.terms.split(' '), [logCategory.terms]);
-
- const commonStyle = css`
- display: inline-block;
- font-family: ${euiTheme.font.familyCode};
- margin-right: ${euiTheme.size.xs};
- `;
-
- const termStyle = css`
- ${commonStyle};
- `;
-
- const separatorStyle = css`
- ${commonStyle};
- color: ${euiTheme.colors.successText};
- `;
-
- return (
-
- *
- {termsList.map((term, index) => (
-
- {term}
- *
-
- ))}
-
- );
+ return ;
};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
index e16bdda7cb44a..ad578e0599f83 100644
--- a/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/log_categories/log_categories_result_content.tsx
@@ -9,27 +9,37 @@ import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/type
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
+import { StateFrom } from 'xstate5';
import { LogCategory } from '../../types';
-import { IndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+import { ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source';
import {
LogCategoriesControlBar,
LogCategoriesControlBarDependencies,
} from './log_categories_control_bar';
import { LogCategoriesGrid, LogCategoriesGridDependencies } from './log_categories_grid';
+import {
+ LogCategoriesFlyoutDependencies,
+ LogCategoryDetailsFlyout,
+} from '../log_category_details/log_category_details_flyout';
+import { categoryDetailsService } from '../../services/category_details_service';
export interface LogCategoriesResultContentProps {
dependencies: LogCategoriesResultContentDependencies;
documentFilters?: QueryDslQueryContainer[];
logCategories: LogCategory[];
- logsSource: IndexNameLogsSourceConfiguration;
+ logsSource: ResolvedIndexNameLogsSourceConfiguration;
timeRange: {
start: string;
end: string;
};
+ categoryDetailsServiceState: StateFrom;
+ onCloseFlyout: () => void;
+ onExpandCategory: (category: LogCategory, rowIndex: number) => void;
}
export type LogCategoriesResultContentDependencies = LogCategoriesControlBarDependencies &
- LogCategoriesGridDependencies;
+ LogCategoriesGridDependencies &
+ LogCategoriesFlyoutDependencies;
export const LogCategoriesResultContent: React.FC = ({
dependencies,
@@ -37,6 +47,9 @@ export const LogCategoriesResultContent: React.FC {
if (logCategories.length === 0) {
return ;
@@ -52,7 +65,24 @@ export const LogCategoriesResultContent: React.FC
-
+
+ {categoryDetailsServiceState.context.expandedCategory && (
+
+ )}
);
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_error_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_error_content.tsx
new file mode 100644
index 0000000000000..509d35b0068e5
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_error_content.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 { EuiCodeBlock, EuiEmptyPrompt } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+
+export interface LogCategoryDetailsErrorContentProps {
+ error?: Error;
+ title: string;
+}
+
+export const LogCategoryDetailsErrorContent: React.FC = ({
+ error,
+ title,
+}) => {
+ return (
+ {title}}
+ body={
+
+ {error?.stack ?? error?.toString() ?? unknownErrorDescription}
+
+ }
+ layout="vertical"
+ />
+ );
+};
+
+const unknownErrorDescription = i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoryDetails.unknownErrorDescription',
+ {
+ defaultMessage: 'An unspecified error occurred.',
+ }
+);
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_flyout.tsx b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_flyout.tsx
new file mode 100644
index 0000000000000..dc2e6bf50507b
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_flyout.tsx
@@ -0,0 +1,137 @@
+/*
+ * 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 {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyout,
+ EuiFlyoutBody,
+ EuiFlyoutHeader,
+ EuiSpacer,
+ EuiTitle,
+ useGeneratedHtmlId,
+} from '@elastic/eui';
+import React, { useMemo } from 'react';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { StateFrom } from 'xstate5';
+import { SettingsStart } from '@kbn/core-ui-settings-browser';
+import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
+import type { DiscoverStart } from '@kbn/discover-plugin/public';
+import { i18n } from '@kbn/i18n';
+import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types';
+import { SharePluginStart } from '@kbn/share-plugin/public';
+import { LogCategory } from '../../types';
+import { LogCategoryPattern } from '../shared/log_category_pattern';
+import { categoryDetailsService } from '../../services/category_details_service';
+import { LogCategoryDocumentExamplesTable } from './log_category_document_examples_table';
+import { type ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+import { LogCategoryDetailsLoadingContent } from './log_category_details_loading_content';
+import { LogCategoryDetailsErrorContent } from './log_category_details_error_content';
+import { DiscoverLink } from '../discover_link';
+import { createCategoryQuery } from '../../services/categorize_logs_service/queries';
+
+export interface LogCategoriesFlyoutDependencies {
+ uiSettings: SettingsStart;
+ fieldFormats: FieldFormatsStart;
+ share: SharePluginStart;
+ columns: {
+ SummaryColumn: DiscoverStart['logColumns']['SummaryColumn'];
+ LogLevelCell: ReturnType;
+ };
+}
+
+interface LogCategoryDetailsFlyoutProps {
+ onCloseFlyout: () => void;
+ logCategory: LogCategory;
+ categoryDetailsServiceState: StateFrom;
+ dependencies: LogCategoriesFlyoutDependencies;
+ logsSource: ResolvedIndexNameLogsSourceConfiguration;
+ documentFilters?: QueryDslQueryContainer[];
+ timeRange: {
+ start: string;
+ end: string;
+ };
+}
+
+export const LogCategoryDetailsFlyout: React.FC = ({
+ onCloseFlyout,
+ logCategory,
+ categoryDetailsServiceState,
+ dependencies,
+ logsSource,
+ documentFilters,
+ timeRange,
+}) => {
+ const flyoutTitleId = useGeneratedHtmlId({
+ prefix: 'flyoutTitle',
+ });
+
+ const linkFilters = useMemo(() => {
+ return [
+ ...(documentFilters ? documentFilters : []),
+ createCategoryQuery(logsSource.messageField)(logCategory.terms),
+ ];
+ }, [documentFilters, logCategory.terms, logsSource.messageField]);
+
+ return (
+ onCloseFlyout()} aria-labelledby={flyoutTitleId}>
+
+
+
+
+
+ ,
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+ {categoryDetailsServiceState.matches({ hasCategory: 'fetchingDocuments' }) ? (
+
+ ) : categoryDetailsServiceState.matches({ hasCategory: 'error' }) ? (
+
+ ) : (
+
+ )}
+
+
+ );
+};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_loading_content.tsx b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_loading_content.tsx
new file mode 100644
index 0000000000000..fd6aa50a38221
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_details_loading_content.tsx
@@ -0,0 +1,19 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { EuiEmptyPrompt, EuiLoadingSpinner } from '@elastic/eui';
+import React from 'react';
+
+interface LogCategoryDetailsLoadingContentProps {
+ message: string;
+}
+
+export const LogCategoryDetailsLoadingContent: React.FC = ({
+ message,
+}) => {
+ return } title={{message}
} />;
+};
diff --git a/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_document_examples_table.tsx b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_document_examples_table.tsx
new file mode 100644
index 0000000000000..cbd2896463e3a
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/log_category_details/log_category_document_examples_table.tsx
@@ -0,0 +1,139 @@
+/*
+ * 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 { EuiBasicTable, EuiBasicTableColumn, EuiSpacer, EuiText } from '@elastic/eui';
+import React, { useMemo } from 'react';
+import { i18n } from '@kbn/i18n';
+import { DataGridDensity, ROWS_HEIGHT_OPTIONS } from '@kbn/unified-data-table';
+import moment from 'moment';
+import { LogCategoryDocument } from '../../services/category_details_service/types';
+import { type ResolvedIndexNameLogsSourceConfiguration } from '../../utils/logs_source';
+import type { LogCategoriesFlyoutDependencies } from './log_category_details_flyout';
+
+export type LogCategoryDocumentExamplesTableDependencies = LogCategoriesFlyoutDependencies;
+
+export interface LogCategoryDocumentExamplesTableProps {
+ dependencies: LogCategoryDocumentExamplesTableDependencies;
+ categoryDocuments: LogCategoryDocument[];
+ logsSource: ResolvedIndexNameLogsSourceConfiguration;
+}
+
+const TimestampCell = ({
+ dependencies,
+ timestamp,
+}: {
+ dependencies: LogCategoryDocumentExamplesTableDependencies;
+ timestamp?: string | number;
+}) => {
+ const dateFormat = useMemo(
+ () => dependencies.uiSettings.client.get('dateFormat'),
+ [dependencies.uiSettings.client]
+ );
+ if (!timestamp) return null;
+
+ if (dateFormat) {
+ return <>{moment(timestamp).format(dateFormat)}>;
+ } else {
+ return <>{timestamp}>;
+ }
+};
+
+export const LogCategoryDocumentExamplesTable: React.FC = ({
+ categoryDocuments,
+ dependencies,
+ logsSource,
+}) => {
+ const columns: Array> = [
+ {
+ field: 'row',
+ name: 'Timestamp',
+ width: '20%',
+ render: (row: any) => {
+ return (
+
+ );
+ },
+ },
+ {
+ field: 'row',
+ name: 'Log level',
+ width: '10%',
+ render: (row: any) => {
+ return (
+ {}}
+ closePopover={() => {}}
+ />
+ );
+ },
+ },
+ {
+ field: 'row',
+ name: 'Summary',
+ width: '70%',
+ render: (row: any) => {
+ return (
+ {}}
+ closePopover={() => {}}
+ density={DataGridDensity.COMPACT}
+ rowHeight={ROWS_HEIGHT_OPTIONS.single}
+ shouldShowFieldHandler={() => false}
+ />
+ );
+ },
+ },
+ ];
+ return (
+ <>
+
+ {i18n.translate(
+ 'xpack.observabilityLogsOverview.logCategoryDocumentExamplesTable.documentCountText',
+ {
+ defaultMessage: 'Displaying the last {documentsCount} documents.',
+ values: {
+ documentsCount: categoryDocuments.length,
+ },
+ }
+ )}
+
+
+
+ >
+ );
+};
diff --git a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
index 988656eb1571e..77535228f7af6 100644
--- a/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
+++ b/x-pack/packages/observability/logs_overview/src/components/logs_overview/logs_overview.tsx
@@ -9,6 +9,7 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { type LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
import React from 'react';
import useAsync from 'react-use/lib/useAsync';
+import { DataViewsContract } from '@kbn/data-views-plugin/public';
import { LogsSourceConfiguration, normalizeLogsSource } from '../../utils/logs_source';
import { LogCategories, LogCategoriesDependencies } from '../log_categories';
import { LogsOverviewErrorContent } from './logs_overview_error_content';
@@ -26,6 +27,7 @@ export interface LogsOverviewProps {
export type LogsOverviewDependencies = LogCategoriesDependencies & {
logsDataAccess: LogsDataAccessPluginStart;
+ dataViews: DataViewsContract;
};
export const LogsOverview: React.FC = React.memo(
@@ -36,8 +38,12 @@ export const LogsOverview: React.FC = React.memo(
timeRange,
}) => {
const normalizedLogsSource = useAsync(
- () => normalizeLogsSource({ logsDataAccess: dependencies.logsDataAccess })(logsSource),
- [dependencies.logsDataAccess, logsSource]
+ () =>
+ normalizeLogsSource({
+ logsDataAccess: dependencies.logsDataAccess,
+ dataViewsService: dependencies.dataViews,
+ })(logsSource),
+ [dependencies.dataViews, dependencies.logsDataAccess, logsSource]
);
if (normalizedLogsSource.loading) {
diff --git a/x-pack/packages/observability/logs_overview/src/components/shared/log_category_pattern.tsx b/x-pack/packages/observability/logs_overview/src/components/shared/log_category_pattern.tsx
new file mode 100644
index 0000000000000..8a8deb5918324
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/components/shared/log_category_pattern.tsx
@@ -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 { useEuiTheme } from '@elastic/eui';
+import { useMemo } from 'react';
+import { css } from '@emotion/react';
+import React from 'react';
+import { getLogCategoryTerms } from '../../utils/log_category';
+import { LogCategory } from '../../types';
+
+interface LogCategoryPatternProps {
+ logCategory: LogCategory;
+}
+
+export const LogCategoryPattern: React.FC = ({ logCategory }) => {
+ const theme = useEuiTheme();
+ const { euiTheme } = theme;
+ const termsList = useMemo(() => getLogCategoryTerms(logCategory), [logCategory]);
+
+ const commonStyle = css`
+ display: inline-block;
+ font-family: ${euiTheme.font.familyCode};
+ margin-right: ${euiTheme.size.xs};
+ `;
+
+ const termStyle = css`
+ ${commonStyle};
+ `;
+
+ const separatorStyle = css`
+ ${commonStyle};
+ color: ${euiTheme.colors.successText};
+ `;
+
+ return (
+
+ *
+ {termsList.map((term, index) => (
+
+ {term}
+ *
+
+ ))}
+
+ );
+};
diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_details_service.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_details_service.ts
new file mode 100644
index 0000000000000..958f717548600
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_details_service.ts
@@ -0,0 +1,191 @@
+/*
+ * 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 { MachineImplementationsFrom, assign, setup } from 'xstate5';
+import { LogCategory } from '../../types';
+import { getPlaceholderFor } from '../../utils/xstate5_utils';
+import {
+ CategoryDetailsServiceDependencies,
+ LogCategoryDocument,
+ LogCategoryDetailsParams,
+} from './types';
+import { getCategoryDocuments } from './category_documents';
+
+export const categoryDetailsService = setup({
+ types: {
+ input: {} as LogCategoryDetailsParams,
+ output: {} as {
+ categoryDocuments: LogCategoryDocument[] | null;
+ },
+ context: {} as {
+ parameters: LogCategoryDetailsParams;
+ error?: Error;
+ expandedRowIndex: number | null;
+ expandedCategory: LogCategory | null;
+ categoryDocuments: LogCategoryDocument[];
+ },
+ events: {} as
+ | {
+ type: 'cancel';
+ }
+ | {
+ type: 'setExpandedCategory';
+ rowIndex: number | null;
+ category: LogCategory | null;
+ },
+ },
+ actors: {
+ getCategoryDocuments: getPlaceholderFor(getCategoryDocuments),
+ },
+ actions: {
+ storeCategory: assign(
+ ({ context, event }, params: { category: LogCategory | null; rowIndex: number | null }) => ({
+ expandedCategory: params.category,
+ expandedRowIndex: params.rowIndex,
+ })
+ ),
+ storeDocuments: assign(
+ ({ context, event }, params: { categoryDocuments: LogCategoryDocument[] }) => ({
+ categoryDocuments: params.categoryDocuments,
+ })
+ ),
+ storeError: assign((_, params: { error: unknown }) => ({
+ error: params.error instanceof Error ? params.error : new Error(String(params.error)),
+ })),
+ },
+ guards: {
+ hasCategory: (_guardArgs, params: { expandedCategory: LogCategory | null }) =>
+ params.expandedCategory !== null,
+ hasDocumentExamples: (
+ _guardArgs,
+ params: { categoryDocuments: LogCategoryDocument[] | null }
+ ) => params.categoryDocuments !== null && params.categoryDocuments.length > 0,
+ },
+}).createMachine({
+ /** @xstate-layout N4IgpgJg5mDOIC5QGMCGAXMUD2AnAlgF5gAy2UsAdMtgK4B26+9UAItsrQLZiOwDEEbPTCVmAN2wBrUWkw4CxMhWp1GzNh2690sBBI4Z8wgNoAGALrmLiUAAdssfE2G2QAD0QBmMwA5KACy+AQFmob4AjABMwQBsADQgAJ6IkYEAnJkA7FmxZlERmQGxAL4liXJYeESk5FQ0DEws7Jw8fILCogYy1BhVirUqDerNWm26+vSScsb01iYRNkggDk4u9G6eCD7+QSFhftFxiSkIvgCsWZSxEVlRsbFZ52Zm515lFX0KNcr1ak2aVo6ARCERiKbSWRfapKOqqRoaFraPiTaZGUyWExRJb2RzOWabbx+QLBULhI7FE7eWL+F45GnRPIRZkfECVb6wob-RFjYH8MC4XB4Sh2AA2GAAZnguL15DDBn8EaMgSiDDMMVZLG5VvjXMstjsSftyTFKclEOdzgFKF5zukvA8zBFnl50udWez5b94SNAcjdPw0PRkGBRdZtXj1oTtsS9mTDqaEuaEBF8udKFkIr5fK6olkzOksgEPdCBt6JWB0MgABYaADKqC4YsgAGFS-g4B0wd0oXKBg2m6LW+24OHljqo-rEMzbpQos8-K7fC9CknTrF0rEbbb0oVMoWIgF3eU2e3OVQK1XaywB82IG2+x2BAKhbgReL0FLcDLPf3G3eH36J8x1xNYCSnFNmSuecXhzdJlydTcqQQLJfHSOc0PyLJN3SMxYiPEtH3PShLxret-yHe8RwEIMQzDLVx0jcDQC2GdoIXOCENXZDsyiOcAiiKJ0iiPDLi8V1CKA4jSOvKAACUwC4VBmA0QDvk7UEughHpfxqBSlJUlg1OqUcGNA3UNggrMs347IjzdaIvGQwSvECXI8k3Z43gEiJJI5BUSMrMiWH05T6FU6j+UFYUxUlaVZSksBQsMqBjIIUycRWJi9RY6dIn8KIAjsu1zkc5CAmiG1fBiaIzB8B0QmPT4iICmSNGS8KjMi2jQxArKwJyjw8pswriocqInOTLwIi3ASD1yQpswCd5WXobAIDgNxdPPCMBss3KEAAWjXRBDvTfcLsu9Jlr8r04WGAEkXGeBGL26MBOQzIt2ut4cwmirCt8W6yzhNqbwo4dH0216LOjTMIjnBdYhK1DYgdHjihtZbUIdWIXJuYGflBoLZI6iKoZe8zJwOw9KtGt1kbuTcsmQrwi0oeCQjzZ5blwt1Cek5TKN22GIIKZbAgKC45pyLyeLwtz4Kyabs1QgWAs0kXqaGhBxdcnzpaE2XXmch0MORmaBJeLwjbKMogA */
+ id: 'logCategoryDetails',
+ context: ({ input }) => ({
+ expandedCategory: null,
+ expandedRowIndex: null,
+ categoryDocuments: [],
+ parameters: input,
+ }),
+ initial: 'idle',
+ states: {
+ idle: {
+ on: {
+ setExpandedCategory: {
+ target: 'checkingCategoryState',
+ actions: [
+ {
+ type: 'storeCategory',
+ params: ({ event }) => event,
+ },
+ ],
+ },
+ },
+ },
+ checkingCategoryState: {
+ always: [
+ {
+ guard: {
+ type: 'hasCategory',
+ params: ({ event, context }) => {
+ return {
+ expandedCategory: context.expandedCategory,
+ };
+ },
+ },
+ target: '#hasCategory.fetchingDocuments',
+ },
+ { target: 'idle' },
+ ],
+ },
+ hasCategory: {
+ id: 'hasCategory',
+ initial: 'fetchingDocuments',
+ on: {
+ setExpandedCategory: {
+ target: 'checkingCategoryState',
+ actions: [
+ {
+ type: 'storeCategory',
+ params: ({ event }) => event,
+ },
+ ],
+ },
+ },
+ states: {
+ fetchingDocuments: {
+ invoke: {
+ src: 'getCategoryDocuments',
+ id: 'fetchCategoryDocumentExamples',
+ input: ({ context }) => ({
+ ...context.parameters,
+ categoryTerms: context.expandedCategory!.terms,
+ }),
+ onDone: [
+ {
+ guard: {
+ type: 'hasDocumentExamples',
+ params: ({ event }) => {
+ return event.output;
+ },
+ },
+ target: 'hasData',
+ actions: [
+ {
+ type: 'storeDocuments',
+ params: ({ event }) => {
+ return event.output;
+ },
+ },
+ ],
+ },
+ {
+ target: 'noData',
+ actions: [
+ {
+ type: 'storeDocuments',
+ params: ({ event }) => {
+ return { categoryDocuments: [] };
+ },
+ },
+ ],
+ },
+ ],
+ onError: {
+ target: 'error',
+ actions: [
+ {
+ type: 'storeError',
+ params: ({ event }) => ({ error: event.error }),
+ },
+ ],
+ },
+ },
+ },
+ hasData: {},
+ noData: {},
+ error: {},
+ },
+ },
+ },
+ output: ({ context }) => ({
+ categoryDocuments: context.categoryDocuments,
+ }),
+});
+
+export const createCategoryDetailsServiceImplementations = ({
+ search,
+}: CategoryDetailsServiceDependencies): MachineImplementationsFrom<
+ typeof categoryDetailsService
+> => ({
+ actors: {
+ getCategoryDocuments: getCategoryDocuments({ search }),
+ },
+});
diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_documents.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_documents.ts
new file mode 100644
index 0000000000000..b513fa79fc686
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/category_documents.ts
@@ -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 { ISearchGeneric } from '@kbn/search-types';
+import { fromPromise } from 'xstate5';
+import { lastValueFrom } from 'rxjs';
+import { flattenHit } from '@kbn/data-service';
+import { LogCategoryDocument, LogCategoryDocumentsParams } from './types';
+import { createGetLogCategoryDocumentsRequestParams } from './queries';
+
+export const getCategoryDocuments = ({ search }: { search: ISearchGeneric }) =>
+ fromPromise<
+ {
+ categoryDocuments: LogCategoryDocument[];
+ },
+ LogCategoryDocumentsParams
+ >(
+ async ({
+ input: {
+ index,
+ endTimestamp,
+ startTimestamp,
+ timeField,
+ messageField,
+ categoryTerms,
+ additionalFilters = [],
+ dataView,
+ },
+ signal,
+ }) => {
+ const requestParams = createGetLogCategoryDocumentsRequestParams({
+ index,
+ timeField,
+ messageField,
+ startTimestamp,
+ endTimestamp,
+ additionalFilters,
+ categoryTerms,
+ });
+
+ const { rawResponse } = await lastValueFrom(
+ search({ params: requestParams }, { abortSignal: signal })
+ );
+
+ const categoryDocuments: LogCategoryDocument[] =
+ rawResponse.hits?.hits.map((hit) => {
+ return {
+ row: {
+ raw: hit._source,
+ flattened: flattenHit(hit, dataView),
+ },
+ };
+ }) ?? [];
+
+ return {
+ categoryDocuments,
+ };
+ }
+ );
diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/index.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/index.ts
new file mode 100644
index 0000000000000..5df79dbab2cbd
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/index.ts
@@ -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 './category_details_service';
diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts
new file mode 100644
index 0000000000000..e9872afbf2669
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/queries.ts
@@ -0,0 +1,58 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import { createCategoryQuery } from '../categorize_logs_service/queries';
+
+export const createGetLogCategoryDocumentsRequestParams = ({
+ index,
+ timeField,
+ messageField,
+ startTimestamp,
+ endTimestamp,
+ additionalFilters = [],
+ categoryTerms = '',
+ documentCount = 20,
+}: {
+ startTimestamp: string;
+ endTimestamp: string;
+ index: string;
+ timeField: string;
+ messageField: string;
+ additionalFilters?: QueryDslQueryContainer[];
+ categoryTerms?: string;
+ documentCount?: number;
+}) => {
+ return {
+ index,
+ size: documentCount,
+ track_total_hits: false,
+ sort: [{ [timeField]: { order: 'desc' } }],
+ query: {
+ bool: {
+ filter: [
+ {
+ exists: {
+ field: messageField,
+ },
+ },
+ {
+ range: {
+ [timeField]: {
+ gte: startTimestamp,
+ lte: endTimestamp,
+ format: 'strict_date_time',
+ },
+ },
+ },
+ ...additionalFilters,
+ ],
+ must: createCategoryQuery(messageField)(categoryTerms),
+ },
+ },
+ };
+};
diff --git a/x-pack/packages/observability/logs_overview/src/services/category_details_service/types.ts b/x-pack/packages/observability/logs_overview/src/services/category_details_service/types.ts
new file mode 100644
index 0000000000000..72369275578e3
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/services/category_details_service/types.ts
@@ -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; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
+import { ISearchGeneric } from '@kbn/search-types';
+import { type DataView } from '@kbn/data-views-plugin/common';
+import type { DataTableRecord } from '@kbn/discover-utils';
+
+export interface LogCategoryDocument {
+ row: Pick;
+}
+
+export interface LogCategoryDetailsParams {
+ additionalFilters: QueryDslQueryContainer[];
+ endTimestamp: string;
+ index: string;
+ messageField: string;
+ startTimestamp: string;
+ timeField: string;
+ dataView: DataView;
+}
+
+export interface CategoryDetailsServiceDependencies {
+ search: ISearchGeneric;
+}
+
+export type LogCategoryDocumentsParams = LogCategoryDetailsParams & { categoryTerms: string };
diff --git a/x-pack/packages/observability/logs_overview/src/utils/log_category.ts b/x-pack/packages/observability/logs_overview/src/utils/log_category.ts
new file mode 100644
index 0000000000000..3a5e72522d78e
--- /dev/null
+++ b/x-pack/packages/observability/logs_overview/src/utils/log_category.ts
@@ -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 { LogCategory } from '../types';
+
+export const getLogCategoryTerms = (logCategory: LogCategory) => {
+ return logCategory.terms.split(' ');
+};
diff --git a/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts b/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts
index 0c8767c8702d4..15c318766be0e 100644
--- a/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts
+++ b/x-pack/packages/observability/logs_overview/src/utils/logs_source.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { type AbstractDataView } from '@kbn/data-views-plugin/common';
+import { type DataViewsContract, type DataView } from '@kbn/data-views-plugin/common';
import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public';
export type LogsSourceConfiguration =
@@ -28,33 +28,68 @@ export interface IndexNameLogsSourceConfiguration {
export interface DataViewLogsSourceConfiguration {
type: 'data_view';
- dataView: AbstractDataView;
+ dataView: DataView;
messageField?: string;
}
+export type ResolvedIndexNameLogsSourceConfiguration = IndexNameLogsSourceConfiguration & {
+ dataView: DataView;
+};
+
export const normalizeLogsSource =
- ({ logsDataAccess }: { logsDataAccess: LogsDataAccessPluginStart }) =>
- async (logsSource: LogsSourceConfiguration): Promise => {
+ ({
+ logsDataAccess,
+ dataViewsService,
+ }: {
+ logsDataAccess: LogsDataAccessPluginStart;
+ dataViewsService: DataViewsContract;
+ }) =>
+ async (
+ logsSource: LogsSourceConfiguration
+ ): Promise => {
switch (logsSource.type) {
case 'index_name':
- return logsSource;
+ return {
+ ...logsSource,
+ dataView: await getDataViewForLogSource(logsSource, dataViewsService),
+ };
case 'shared_setting':
const logSourcesFromSharedSettings =
await logsDataAccess.services.logSourcesService.getLogSources();
- return {
- type: 'index_name',
+ const sharedSettingLogsSource = {
+ type: 'index_name' as const,
indexName: logSourcesFromSharedSettings
.map((logSource) => logSource.indexPattern)
.join(','),
timestampField: logsSource.timestampField ?? '@timestamp',
messageField: logsSource.messageField ?? 'message',
};
- case 'data_view':
return {
- type: 'index_name',
+ ...sharedSettingLogsSource,
+ dataView: await getDataViewForLogSource(sharedSettingLogsSource, dataViewsService),
+ };
+ case 'data_view':
+ const dataViewLogsSource = {
+ type: 'index_name' as const,
indexName: logsSource.dataView.getIndexPattern(),
timestampField: logsSource.dataView.timeFieldName ?? '@timestamp',
messageField: logsSource.messageField ?? 'message',
};
+ return {
+ ...dataViewLogsSource,
+ dataView: logsSource.dataView,
+ };
}
};
+
+// Ad-hoc Data View
+const getDataViewForLogSource = async (
+ logSourceConfiguration: IndexNameLogsSourceConfiguration,
+ dataViewsService: DataViewsContract
+) => {
+ const dataView = await dataViewsService.create({
+ title: logSourceConfiguration.indexName,
+ timeFieldName: logSourceConfiguration.timestampField,
+ });
+ return dataView;
+};
diff --git a/x-pack/packages/observability/logs_overview/tsconfig.json b/x-pack/packages/observability/logs_overview/tsconfig.json
index 886062ae8855f..ff229bb68829e 100644
--- a/x-pack/packages/observability/logs_overview/tsconfig.json
+++ b/x-pack/packages/observability/logs_overview/tsconfig.json
@@ -35,5 +35,7 @@
"@kbn/es-query",
"@kbn/router-utils",
"@kbn/share-plugin",
+ "@kbn/field-formats-plugin",
+ "@kbn/unified-data-table",
]
}
diff --git a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc
index 10c8fe32cfe9c..a6329c585800a 100644
--- a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc
+++ b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc
@@ -11,8 +11,10 @@
"requiredPlugins": [
"charts",
"data",
+ "fieldFormats",
"dataViews",
"discoverShared",
+ "discover",
"logsDataAccess",
"observabilityShared",
"share",
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
index fc17e9b17cc82..a1ae176d5c161 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/public/plugin.ts
@@ -61,6 +61,7 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
logsDataAccess,
observabilityAIAssistant,
share,
+ fieldFormats,
} = plugins;
const logViews = this.logViews.start({
@@ -76,6 +77,12 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass {
search: data.search.search,
uiSettings: settings,
share,
+ dataViews,
+ fieldFormats,
+ columns: {
+ SummaryColumn: plugins.discover.logColumns.SummaryColumn,
+ LogLevelCell: plugins.discover.logColumns.getLogLevelCell('log.level'),
+ },
});
if (!observabilityAIAssistant) {
diff --git a/x-pack/plugins/observability_solution/logs_shared/public/types.ts b/x-pack/plugins/observability_solution/logs_shared/public/types.ts
index 4237c28c621b8..ce199cb01cd79 100644
--- a/x-pack/plugins/observability_solution/logs_shared/public/types.ts
+++ b/x-pack/plugins/observability_solution/logs_shared/public/types.ts
@@ -14,6 +14,8 @@ import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/pub
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
+import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
+import type { DiscoverStart } from '@kbn/discover-plugin/public';
import type { LogsSharedLocators } from '../common/locators';
import type { LogAIAssistantProps } from './components/log_ai_assistant/log_ai_assistant';
import type { SelfContainedLogsOverview } from './components/logs_overview';
@@ -40,10 +42,12 @@ export interface LogsSharedClientStartDeps {
data: DataPublicPluginStart;
dataViews: DataViewsPublicPluginStart;
discoverShared: DiscoverSharedPublicStart;
+ discover: DiscoverStart;
logsDataAccess: LogsDataAccessPluginStart;
observabilityAIAssistant?: ObservabilityAIAssistantPublicStart;
share: SharePluginStart;
uiActions: UiActionsStart;
+ fieldFormats: FieldFormatsStart;
}
export type LogsSharedClientCoreSetup = CoreSetup<
diff --git a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json
index 788f55c9b6fc5..f171c79afccd0 100644
--- a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json
+++ b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json
@@ -48,5 +48,6 @@
"@kbn/observability-logs-overview",
"@kbn/charts-plugin",
"@kbn/core-ui-settings-common",
+ "@kbn/field-formats-plugin",
]
}
From 246b0f778b81553847717d3fbe201456c4aef028 Mon Sep 17 00:00:00 2001
From: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Date: Thu, 10 Oct 2024 12:57:31 +0000
Subject: [PATCH 45/74] [CI] Auto-commit changed files from 'node
scripts/notice'
---
x-pack/packages/observability/logs_overview/tsconfig.json | 2 ++
1 file changed, 2 insertions(+)
diff --git a/x-pack/packages/observability/logs_overview/tsconfig.json b/x-pack/packages/observability/logs_overview/tsconfig.json
index ff229bb68829e..a1e7847935fc2 100644
--- a/x-pack/packages/observability/logs_overview/tsconfig.json
+++ b/x-pack/packages/observability/logs_overview/tsconfig.json
@@ -37,5 +37,7 @@
"@kbn/share-plugin",
"@kbn/field-formats-plugin",
"@kbn/unified-data-table",
+ "@kbn/data-service",
+ "@kbn/discover-utils",
]
}
From 6a5087e2cc784dd7e9f0d178800c40f129b548c9 Mon Sep 17 00:00:00 2001
From: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Date: Thu, 10 Oct 2024 12:58:21 +0000
Subject: [PATCH 46/74] [CI] Auto-commit changed files from 'node
scripts/lint_ts_projects --fix'
---
x-pack/plugins/observability_solution/logs_shared/tsconfig.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json
index f171c79afccd0..47ca88b558171 100644
--- a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json
+++ b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json
@@ -49,5 +49,6 @@
"@kbn/charts-plugin",
"@kbn/core-ui-settings-common",
"@kbn/field-formats-plugin",
+ "@kbn/discover-plugin",
]
}
From 0b08c2caecdaab1ae8b144178c25c544d537b353 Mon Sep 17 00:00:00 2001
From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com>
Date: Thu, 10 Oct 2024 14:17:56 +0100
Subject: [PATCH 47/74] Remove line from merge conflict
---
.../metrics/hosts/components/tabs/logs/logs_tab_content.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
index 78443c9a6ec81..68a5db6d4d484 100644
--- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
+++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/tabs/logs/logs_tab_content.tsx
@@ -85,7 +85,6 @@ export const LogsTabLogStreamContent = () => {
query={logsLinkToStreamQuery}
logView={logView}
/>
- ]
From c290819c1c1e1cb5a67d437cca7783c0e2302c8f Mon Sep 17 00:00:00 2001
From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com>
Date: Tue, 15 Oct 2024 00:30:50 +0100
Subject: [PATCH 48/74] Extract out columns / cells to new package
- This is to avoid many circular dependencies
---
package.json | 1 +
.../README.md | 3 +
.../jest.config.js | 11 +-
.../kibana.jsonc | 5 +
.../package.json | 6 +
.../logs/components}/cell_actions_popover.tsx | 6 +-
.../src/data_types/logs/components/index.ts | 10 +
.../log_level_badge_cell.test.tsx | 4 +-
.../log_level_badge_cell.tsx | 6 +-
.../service_name_badge_with_actions.tsx | 9 +-
.../components}/summary_column/content.tsx | 2 +-
.../logs/components/summary_column/index.ts | 13 +
.../components}/summary_column/resource.tsx | 5 +-
.../summary_column/summary_column.test.tsx | 50 +--
.../summary_column/summary_column.tsx | 171 ++++++++++
.../logs/components}/summary_column/utils.tsx | 59 ++--
.../logs/components/translations.tsx | 305 ++++++++++++++++++
.../tsconfig.json | 36 +++
packages/kbn-discover-utils/index.ts | 2 +-
.../logs/components/{index.ts => index.tsx} | 0
.../src/data_types/logs/constants.ts | 70 ++++
.../src/data_types/logs/index.ts | 2 +-
.../src/data_types/logs/types.ts | 7 +
.../utils/get_available_resource_fields.ts | 4 +-
packages/kbn-discover-utils/tsconfig.json | 4 +-
.../common/data_types/logs/constants.ts | 62 +---
.../data_types/logs/summary_column/index.tsx | 8 +-
.../logs/summary_column/summary_column.tsx | 173 +---------
.../accessors/get_cell_renderers.tsx | 2 +-
src/plugins/discover/public/plugin.tsx | 28 --
src/plugins/discover/public/types.ts | 6 -
src/plugins/discover/tsconfig.json | 6 +-
src/plugins/unified_doc_viewer/kibana.jsonc | 1 +
tsconfig.base.json | 2 +
.../log_category_details_flyout.tsx | 7 +-
.../observability/logs_overview/tsconfig.json | 5 +-
.../logs_shared/kibana.jsonc | 2 +-
.../public/{plugin.ts => plugin.tsx} | 17 +-
.../logs_shared/tsconfig.json | 1 +
yarn.lock | 4 +
40 files changed, 769 insertions(+), 346 deletions(-)
create mode 100644 packages/kbn-discover-contextual-components/README.md
rename src/plugins/discover/common/data_types/logs/display_options.ts => packages/kbn-discover-contextual-components/jest.config.js (75%)
create mode 100644 packages/kbn-discover-contextual-components/kibana.jsonc
create mode 100644 packages/kbn-discover-contextual-components/package.json
rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/cell_actions_popover.tsx (95%)
create mode 100644 packages/kbn-discover-contextual-components/src/data_types/logs/components/index.ts
rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell}/log_level_badge_cell.test.tsx (93%)
rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell}/log_level_badge_cell.tsx (85%)
rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/service_name_badge_with_actions.tsx (85%)
rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/summary_column/content.tsx (97%)
create mode 100644 packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/index.ts
rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/summary_column/resource.tsx (89%)
rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/summary_column/summary_column.test.tsx (86%)
create mode 100644 packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.tsx
rename {src/plugins/discover/public/components/data_types/logs => packages/kbn-discover-contextual-components/src/data_types/logs/components}/summary_column/utils.tsx (72%)
create mode 100644 packages/kbn-discover-contextual-components/src/data_types/logs/components/translations.tsx
create mode 100644 packages/kbn-discover-contextual-components/tsconfig.json
rename packages/kbn-discover-utils/src/data_types/logs/components/{index.ts => index.tsx} (100%)
create mode 100644 packages/kbn-discover-utils/src/data_types/logs/constants.ts
rename {src/plugins/discover/public => packages/kbn-discover-utils/src/data_types/logs}/utils/get_available_resource_fields.ts (87%)
rename x-pack/plugins/observability_solution/logs_shared/public/{plugin.ts => plugin.tsx} (78%)
diff --git a/package.json b/package.json
index f6412a3da925a..9f36437008219 100644
--- a/package.json
+++ b/package.json
@@ -452,6 +452,7 @@
"@kbn/default-nav-ml": "link:packages/default-nav/ml",
"@kbn/dev-tools-plugin": "link:src/plugins/dev_tools",
"@kbn/developer-examples-plugin": "link:examples/developer_examples",
+ "@kbn/discover-contextual-components": "link:packages/kbn-discover-contextual-components",
"@kbn/discover-customization-examples-plugin": "link:examples/discover_customization_examples",
"@kbn/discover-enhanced-plugin": "link:x-pack/plugins/discover_enhanced",
"@kbn/discover-plugin": "link:src/plugins/discover",
diff --git a/packages/kbn-discover-contextual-components/README.md b/packages/kbn-discover-contextual-components/README.md
new file mode 100644
index 0000000000000..ae9e2402c2a69
--- /dev/null
+++ b/packages/kbn-discover-contextual-components/README.md
@@ -0,0 +1,3 @@
+# @kbn/discover-contextual-components
+
+Houses contextual (e.g. logs) components that are used by Discover.
diff --git a/src/plugins/discover/common/data_types/logs/display_options.ts b/packages/kbn-discover-contextual-components/jest.config.js
similarity index 75%
rename from src/plugins/discover/common/data_types/logs/display_options.ts
rename to packages/kbn-discover-contextual-components/jest.config.js
index 05803ba0bde7f..37624c643d4d0 100644
--- a/src/plugins/discover/common/data_types/logs/display_options.ts
+++ b/packages/kbn-discover-contextual-components/jest.config.js
@@ -7,9 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-export interface SmartFieldGridColumnOptions {
- type: 'smart-field';
- smartField: 'content' | 'resource';
- fallbackFields: string[];
- width?: number;
-}
+module.exports = {
+ preset: '@kbn/test/jest_node',
+ rootDir: '../..',
+ roots: ['/packages/kbn-discover-contextual-components'],
+};
diff --git a/packages/kbn-discover-contextual-components/kibana.jsonc b/packages/kbn-discover-contextual-components/kibana.jsonc
new file mode 100644
index 0000000000000..8e06123264576
--- /dev/null
+++ b/packages/kbn-discover-contextual-components/kibana.jsonc
@@ -0,0 +1,5 @@
+{
+ "type": "shared-browser",
+ "id": "@kbn/discover-contextual-components",
+ "owner": "@elastic/obs-ux-logs-team"
+}
diff --git a/packages/kbn-discover-contextual-components/package.json b/packages/kbn-discover-contextual-components/package.json
new file mode 100644
index 0000000000000..ef68d35fa857f
--- /dev/null
+++ b/packages/kbn-discover-contextual-components/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "@kbn/discover-contextual-components",
+ "private": true,
+ "version": "1.0.0",
+ "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
+}
\ No newline at end of file
diff --git a/src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/cell_actions_popover.tsx
similarity index 95%
rename from src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx
rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/cell_actions_popover.tsx
index 1858bb2323637..2eaebd6e5fe6a 100644
--- a/src/plugins/discover/public/components/data_types/logs/cell_actions_popover.tsx
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/cell_actions_popover.tsx
@@ -24,7 +24,9 @@ import {
import { css } from '@emotion/react';
import { useBoolean } from '@kbn/react-hooks';
import { euiThemeVars } from '@kbn/ui-theme';
-import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
+import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
+import type { CoreStart } from '@kbn/core-lifecycle-browser';
import {
actionFilterForText,
actionFilterOutText,
@@ -158,6 +160,8 @@ export function CellActionsPopover({
export interface FieldBadgeWithActionsProps
extends Pick {
icon?: EuiBadgeProps['iconType'];
+ core?: CoreStart;
+ share?: SharePluginStart;
}
export function FieldBadgeWithActions({
diff --git a/packages/kbn-discover-contextual-components/src/data_types/logs/components/index.ts b/packages/kbn-discover-contextual-components/src/data_types/logs/components/index.ts
new file mode 100644
index 0000000000000..65832358f15b5
--- /dev/null
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/index.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+export * from './summary_column';
diff --git a/src/plugins/discover/public/components/data_types/logs/log_level_badge_cell.test.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell/log_level_badge_cell.test.tsx
similarity index 93%
rename from src/plugins/discover/public/components/data_types/logs/log_level_badge_cell.test.tsx
rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell/log_level_badge_cell.test.tsx
index 319f3738a6682..460c97dfd115b 100644
--- a/src/plugins/discover/public/components/data_types/logs/log_level_badge_cell.test.tsx
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell/log_level_badge_cell.test.tsx
@@ -7,12 +7,12 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { buildDataTableRecord, DataTableRecord } from '@kbn/discover-utils';
-import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { getLogLevelBadgeCell } from './log_level_badge_cell';
+import { dataViewMock } from '@kbn/discover-utils/src/__mocks__/data_view';
+import { DataTableRecord, buildDataTableRecord } from '@kbn/discover-utils';
const renderCell = (logLevelField: string, record: DataTableRecord) => {
const LogLevelBadgeCell = getLogLevelBadgeCell(logLevelField);
diff --git a/src/plugins/discover/public/components/data_types/logs/log_level_badge_cell.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell/log_level_badge_cell.tsx
similarity index 85%
rename from src/plugins/discover/public/components/data_types/logs/log_level_badge_cell.tsx
rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell/log_level_badge_cell.tsx
index bff3bdddee026..9b7f766911760 100644
--- a/src/plugins/discover/public/components/data_types/logs/log_level_badge_cell.tsx
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/log_level_badge_cell/log_level_badge_cell.tsx
@@ -9,8 +9,8 @@
import type { CSSObject } from '@emotion/react';
import React from 'react';
-import { LogLevelBadge } from '@kbn/discover-utils';
-import type { DataGridCellValueElementProps } from '@kbn/unified-data-table';
+import type { DataGridCellValueElementProps } from '@kbn/unified-data-table/src/types';
+import { LogLevelBadge } from '@kbn/discover-utils/src/data_types/logs/components/log_level_badge';
const dataTestSubj = 'logLevelBadgeCell';
const badgeCss: CSSObject = { marginTop: '-4px' };
@@ -32,3 +32,5 @@ export const getLogLevelBadgeCell =
/>
);
};
+
+export type LogLevelBadgeCell = ReturnType;
diff --git a/src/plugins/discover/public/components/data_types/logs/service_name_badge_with_actions.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/service_name_badge_with_actions.tsx
similarity index 85%
rename from src/plugins/discover/public/components/data_types/logs/service_name_badge_with_actions.tsx
rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/service_name_badge_with_actions.tsx
index 581c889b8e98e..87d212f8bb372 100644
--- a/src/plugins/discover/public/components/data_types/logs/service_name_badge_with_actions.tsx
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/service_name_badge_with_actions.tsx
@@ -11,17 +11,16 @@ import React from 'react';
import { getRouterLinkProps } from '@kbn/router-utils';
import { EuiLink } from '@elastic/eui';
import { OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE } from '@kbn/management-settings-ids';
-import { SharePublicStart } from '@kbn/share-plugin/public/plugin';
-import { useDiscoverServices } from '../../../hooks/use_discover_services';
+import type { SharePublicStart } from '@kbn/share-plugin/public/plugin';
import { FieldBadgeWithActions, FieldBadgeWithActionsProps } from './cell_actions_popover';
const SERVICE_ENTITY_LOCATOR = 'SERVICE_ENTITY_LOCATOR';
export function ServiceNameBadgeWithActions(props: FieldBadgeWithActionsProps) {
- const { share, core } = useDiscoverServices();
- const canViewApm = core.application.capabilities.apm?.show || false;
+ const { share, core } = props;
+ const canViewApm = core?.application.capabilities.apm?.show || false;
const isEntityCentricExperienceSettingEnabled = canViewApm
- ? core.uiSettings.get(OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE)
+ ? core?.uiSettings.get(OBSERVABILITY_ENTITY_CENTRIC_EXPERIENCE)
: false;
const derivedPropsForEntityExperience = isEntityCentricExperienceSettingEnabled
diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/content.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/content.tsx
similarity index 97%
rename from src/plugins/discover/public/components/data_types/logs/summary_column/content.tsx
rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/content.tsx
index 0da98cbf7145e..7ca3944e6aed6 100644
--- a/src/plugins/discover/public/components/data_types/logs/summary_column/content.tsx
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/content.tsx
@@ -14,7 +14,7 @@ import {
getLogDocumentOverview,
getMessageFieldWithFallbacks,
} from '@kbn/discover-utils';
-import * as constants from '../../../../../common/data_types/logs/constants';
+import * as constants from '@kbn/discover-utils/src/data_types/logs/constants';
import { formatJsonDocumentForContent } from './utils';
interface ContentProps extends DataGridCellValueElementProps {
diff --git a/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/index.ts b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/index.ts
new file mode 100644
index 0000000000000..006ec34d0a475
--- /dev/null
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the "Elastic License
+ * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+export * from './content';
+export * from './resource';
+export * from './summary_column';
+export * from './utils';
diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/resource.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/resource.tsx
similarity index 89%
rename from src/plugins/discover/public/components/data_types/logs/summary_column/resource.tsx
rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/resource.tsx
index a7955fadde622..5ea7ddda7a6b7 100644
--- a/src/plugins/discover/public/components/data_types/logs/summary_column/resource.tsx
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/resource.tsx
@@ -8,8 +8,8 @@
*/
import React from 'react';
-import { EuiBadge, EuiFlexGroup } from '@elastic/eui';
-import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
+import { CommonProps, EuiBadge, EuiFlexGroup } from '@elastic/eui';
+import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import { ResourceFieldDescriptor } from './utils';
const MAX_LIMITED_FIELDS_VISIBLE = 3;
@@ -19,6 +19,7 @@ interface ResourceProps {
/* When true, the column will render a predefined number of resources and indicates with a badge how many more we have */
limited?: boolean;
onFilter?: DocViewFilterFn;
+ css?: CommonProps['css'];
}
export const Resource = ({ fields, limited = false, onFilter, ...props }: ResourceProps) => {
diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.test.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.test.tsx
similarity index 86%
rename from src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.test.tsx
rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.test.tsx
index b8eeea613c9c6..6b337167279e3 100644
--- a/src/plugins/discover/public/components/data_types/logs/summary_column/summary_column.test.tsx
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.test.tsx
@@ -8,41 +8,41 @@
*/
import React from 'react';
-import { buildDataTableRecord, DataTableRecord } from '@kbn/discover-utils';
-import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import { render, screen } from '@testing-library/react';
import SummaryColumn, { SummaryColumnFactoryDeps, SummaryColumnProps } from './summary_column';
import { DataGridDensity, ROWS_HEIGHT_OPTIONS } from '@kbn/unified-data-table';
-import * as constants from '../../../../../common/data_types/logs/constants';
-import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
-import { discoverServiceMock } from '../../../../__mocks__/services';
+import * as constants from '@kbn/discover-utils/src/data_types/logs/constants';
+import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
+import { coreMock as corePluginMock } from '@kbn/core/public/mocks';
+import { DataTableRecord, buildDataTableRecord } from '@kbn/discover-utils';
+import { dataViewMock } from '@kbn/discover-utils/src/__mocks__/data_view';
const renderSummary = (
record: DataTableRecord,
opts: Partial = {}
) => {
render(
-
- {}}
- closePopover={() => {}}
- density={DataGridDensity.COMPACT}
- rowHeight={ROWS_HEIGHT_OPTIONS.single}
- onFilter={jest.fn()}
- shouldShowFieldHandler={() => true}
- {...opts}
- />
-
+ {}}
+ closePopover={() => {}}
+ density={DataGridDensity.COMPACT}
+ rowHeight={ROWS_HEIGHT_OPTIONS.single}
+ onFilter={jest.fn()}
+ shouldShowFieldHandler={() => true}
+ core={corePluginMock.createStart()}
+ share={sharePluginMock.createStartContract()}
+ {...opts}
+ />
);
};
diff --git a/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.tsx
new file mode 100644
index 0000000000000..d630b686255c6
--- /dev/null
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/summary_column.tsx
@@ -0,0 +1,171 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+import { DataGridDensity, type DataGridCellValueElementProps } from '@kbn/unified-data-table';
+import React from 'react';
+import { EuiButtonIcon, EuiCodeBlock, EuiFlexGroup, EuiText, EuiTitle } from '@elastic/eui';
+import { JsonCodeEditor } from '@kbn/unified-doc-viewer-plugin/public';
+import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
+import type { CoreStart } from '@kbn/core-lifecycle-browser';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
+import {
+ ShouldShowFieldInTableHandler,
+ getLogDocumentOverview,
+ getMessageFieldWithFallbacks,
+} from '@kbn/discover-utils';
+import { ROWS_HEIGHT_OPTIONS } from '@kbn/unified-data-table/src/constants';
+import { Resource } from './resource';
+import { Content } from './content';
+import { createResourceFields, formatJsonDocumentForContent } from './utils';
+import {
+ closeCellActionPopoverText,
+ contentLabel,
+ jsonLabel,
+ resourceLabel,
+} from '../translations';
+
+export interface SummaryColumnFactoryDeps {
+ density: DataGridDensity | undefined;
+ rowHeight: number | undefined;
+ shouldShowFieldHandler: ShouldShowFieldInTableHandler;
+ onFilter?: DocViewFilterFn;
+ core: CoreStart;
+ share?: SharePluginStart;
+}
+
+export type SummaryColumnProps = DataGridCellValueElementProps;
+export type AllSummaryColumnProps = SummaryColumnProps & SummaryColumnFactoryDeps;
+
+export const SummaryColumn = (props: AllSummaryColumnProps) => {
+ const { isDetails } = props;
+
+ if (isDetails) {
+ return ;
+ }
+
+ return ;
+};
+
+// eslint-disable-next-line import/no-default-export
+export default SummaryColumn;
+
+const SummaryCell = ({
+ density: maybeNullishDensity,
+ rowHeight: maybeNullishRowHeight,
+ ...props
+}: AllSummaryColumnProps) => {
+ const { onFilter, row, share, core } = props;
+
+ const density = maybeNullishDensity ?? DataGridDensity.COMPACT;
+ const isCompressed = density === DataGridDensity.COMPACT;
+
+ const rowHeight = maybeNullishRowHeight ?? ROWS_HEIGHT_OPTIONS.single;
+ const isSingleLine = rowHeight === ROWS_HEIGHT_OPTIONS.single || rowHeight === 1;
+
+ const resourceFields = createResourceFields(row, core, share);
+ const shouldRenderResource = resourceFields.length > 0;
+
+ return isSingleLine ? (
+
+ {shouldRenderResource && (
+
+ )}
+
+
+ ) : (
+ <>
+ {shouldRenderResource && (
+
+ )}
+
+ >
+ );
+};
+
+const SummaryCellPopover = (props: AllSummaryColumnProps) => {
+ const { row, dataView, fieldFormats, onFilter, closePopover, share, core } = props;
+
+ const resourceFields = createResourceFields(row, core, share);
+ const shouldRenderResource = resourceFields.length > 0;
+
+ const documentOverview = getLogDocumentOverview(row, { dataView, fieldFormats });
+ const { field, value } = getMessageFieldWithFallbacks(documentOverview);
+ const shouldRenderContent = Boolean(field && value);
+
+ const shouldRenderSource = !shouldRenderContent;
+
+ return (
+
+
+ {shouldRenderResource && (
+
+
+ {resourceLabel}
+
+
+
+ )}
+
+
+ {contentLabel}
+
+ {shouldRenderContent && (
+
+
+ {field}
+
+
+ {value}
+
+
+ )}
+ {shouldRenderSource && (
+
+
+ {jsonLabel}
+
+
+
+ )}
+
+
+ );
+};
+
+const singleLineResourceCss = {
+ flexGrow: 0,
+ lineHeight: 'normal',
+ marginTop: -1,
+};
+
+const multiLineResourceCss = { display: 'inline-flex' };
diff --git a/src/plugins/discover/public/components/data_types/logs/summary_column/utils.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/utils.tsx
similarity index 72%
rename from src/plugins/discover/public/components/data_types/logs/summary_column/utils.tsx
rename to packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/utils.tsx
index 470ec8a0f86fa..b7ba9e724a407 100644
--- a/src/plugins/discover/public/components/data_types/logs/summary_column/utils.tsx
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/summary_column/utils.tsx
@@ -7,18 +7,19 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { getFieldValue, LogDocument, ResourceFields } from '@kbn/discover-utils/src';
-import { DataTableRecord } from '@kbn/discover-utils';
import { dynamic } from '@kbn/shared-ux-utility';
import React from 'react';
import { css } from '@emotion/react';
import { AgentName } from '@kbn/elastic-agent-utils';
import { euiThemeVars } from '@kbn/ui-theme';
-import { getAvailableResourceFields } from '../../../../utils/get_available_resource_fields';
-import * as constants from '../../../../../common/data_types/logs/constants';
-import { ServiceNameBadgeWithActions } from '../service_name_badge_with_actions';
+import type { SharePluginStart } from '@kbn/share-plugin/public';
+import type { CoreStart } from '@kbn/core-lifecycle-browser';
+import * as constants from '@kbn/discover-utils/src/data_types/logs/constants';
+import { DataTableRecord, getFieldValue } from '@kbn/discover-utils';
+import { LogDocument, ResourceFields } from '@kbn/discover-utils/src';
+import { getAvailableResourceFields } from '@kbn/discover-utils/src/data_types/logs/utils/get_available_resource_fields';
import { FieldBadgeWithActions, FieldBadgeWithActionsProps } from '../cell_actions_popover';
-
+import { ServiceNameBadgeWithActions } from '../service_name_badge_with_actions';
/**
* getUnformattedResourceFields definitions
*/
@@ -58,33 +59,43 @@ const resourceCustomComponentsMap: Partial<
};
export interface ResourceFieldDescriptor {
- ResourceBadge: React.ComponentType;
+ ResourceBadge: React.ComponentType>;
Icon?: () => JSX.Element;
name: keyof ResourceFields;
value: string;
}
-export const createResourceFields = (row: DataTableRecord): ResourceFieldDescriptor[] => {
+export const createResourceFields = (
+ row: DataTableRecord,
+ core: CoreStart,
+ share?: SharePluginStart
+): ResourceFieldDescriptor[] => {
const resourceDoc = getUnformattedResourceFields(row as LogDocument);
const availableResourceFields = getAvailableResourceFields(resourceDoc);
- const resourceFields = availableResourceFields.map((name) => ({
- name,
- value: resourceDoc[name] as string,
- ResourceBadge: resourceCustomComponentsMap[name] ?? FieldBadgeWithActions,
- ...(name === constants.SERVICE_NAME_FIELD && {
- Icon: () => (
-
- ),
- }),
- }));
+ const resourceFields = availableResourceFields.map((name) => {
+ const ResourceBadgeComponent = resourceCustomComponentsMap[name] ?? FieldBadgeWithActions;
+ const resourceBadgeComponentWithDependencies = (props: FieldBadgeWithActionsProps) => (
+
+ );
+ return {
+ name,
+ value: resourceDoc[name] as string,
+ ResourceBadge: resourceBadgeComponentWithDependencies,
+ ...(name === constants.SERVICE_NAME_FIELD && {
+ Icon: () => (
+
+ ),
+ }),
+ };
+ });
return resourceFields;
};
diff --git a/packages/kbn-discover-contextual-components/src/data_types/logs/components/translations.tsx b/packages/kbn-discover-contextual-components/src/data_types/logs/components/translations.tsx
new file mode 100644
index 0000000000000..bbc39022bd503
--- /dev/null
+++ b/packages/kbn-discover-contextual-components/src/data_types/logs/components/translations.tsx
@@ -0,0 +1,305 @@
+/*
+ * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
+ * License v3.0 only", or the "Server Side Public License, v 1".
+ */
+
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { EuiCode } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+
+export const flyoutContentLabel = i18n.translate('discover.logs.flyoutDetail.label.message', {
+ defaultMessage: 'Content breakdown',
+});
+
+export const jsonLabel = i18n.translate('discover.logs.dataTable.header.popover.json', {
+ defaultMessage: 'JSON',
+});
+
+export const contentLabel = i18n.translate('discover.logs.dataTable.header.popover.content', {
+ defaultMessage: 'Content',
+});
+
+export const resourceLabel = i18n.translate('discover.logs.dataTable.header.popover.resource', {
+ defaultMessage: 'Resource',
+});
+
+export const actionsLabel = i18n.translate('discover.logs.dataTable.header.popover.actions', {
+ defaultMessage: 'Actions',
+});
+
+export const actionsLabelLowerCase = i18n.translate(
+ 'discover.logs.dataTable.header.popover.actions.lowercase',
+ {
+ defaultMessage: 'actions',
+ }
+);
+
+export const flyoutServiceLabel = i18n.translate('discover.logs.flyoutDetail.label.service', {
+ defaultMessage: 'Service',
+});
+
+export const flyoutTraceLabel = i18n.translate('discover.logs.flyoutDetail.label.trace', {
+ defaultMessage: 'Trace',
+});
+
+export const flyoutHostNameLabel = i18n.translate('discover.logs.flyoutDetail.label.hostName', {
+ defaultMessage: 'Host name',
+});
+
+export const serviceInfraAccordionTitle = i18n.translate(
+ 'discover.logs.flyoutDetail.accordion.title.serviceInfra',
+ {
+ defaultMessage: 'Service & Infrastructure',
+ }
+);
+
+export const cloudAccordionTitle = i18n.translate(
+ 'discover.logs.flyoutDetail.accordion.title.cloud',
+ {
+ defaultMessage: 'Cloud',
+ }
+);
+
+export const otherAccordionTitle = i18n.translate(
+ 'discover.logs.flyoutDetail.accordion.title.other',
+ {
+ defaultMessage: 'Other',
+ }
+);
+
+export const flyoutOrchestratorClusterNameLabel = i18n.translate(
+ 'discover.logs.flyoutDetail.label.orchestratorClusterName',
+ {
+ defaultMessage: 'Orchestrator cluster Name',
+ }
+);
+
+export const flyoutOrchestratorResourceIdLabel = i18n.translate(
+ 'discover.logs.flyoutDetail.label.orchestratorResourceId',
+ {
+ defaultMessage: 'Orchestrator resource ID',
+ }
+);
+
+export const flyoutCloudProviderLabel = i18n.translate(
+ 'discover.logs.flyoutDetail.label.cloudProvider',
+ {
+ defaultMessage: 'Cloud provider',
+ }
+);
+
+export const flyoutCloudRegionLabel = i18n.translate(
+ 'discover.logs.flyoutDetail.label.cloudRegion',
+ {
+ defaultMessage: 'Cloud region',
+ }
+);
+
+export const flyoutCloudAvailabilityZoneLabel = i18n.translate(
+ 'discover.logs.flyoutDetail.label.cloudAvailabilityZone',
+ {
+ defaultMessage: 'Cloud availability zone',
+ }
+);
+
+export const flyoutCloudProjectIdLabel = i18n.translate(
+ 'discover.logs.flyoutDetail.label.cloudProjectId',
+ {
+ defaultMessage: 'Cloud project ID',
+ }
+);
+
+export const flyoutCloudInstanceIdLabel = i18n.translate(
+ 'discover.logs.flyoutDetail.label.cloudInstanceId',
+ {
+ defaultMessage: 'Cloud instance ID',
+ }
+);
+
+export const flyoutLogPathFileLabel = i18n.translate(
+ 'discover.logs.flyoutDetail.label.logPathFile',
+ {
+ defaultMessage: 'Log path file',
+ }
+);
+
+export const flyoutNamespaceLabel = i18n.translate('discover.logs.flyoutDetail.label.namespace', {
+ defaultMessage: 'Namespace',
+});
+
+export const flyoutDatasetLabel = i18n.translate('discover.logs.flyoutDetail.label.dataset', {
+ defaultMessage: 'Dataset',
+});
+
+export const flyoutShipperLabel = i18n.translate('discover.logs.flyoutDetail.label.shipper', {
+ defaultMessage: 'Shipper',
+});
+
+export const actionFilterForText = (text: string) =>
+ i18n.translate('discover.logs.flyoutDetail.value.hover.filterFor', {
+ defaultMessage: 'Filter for this {value}',
+ values: {
+ value: text,
+ },
+ });
+
+export const actionFilterOutText = (text: string) =>
+ i18n.translate('discover.logs.flyoutDetail.value.hover.filterOut', {
+ defaultMessage: 'Filter out this {value}',
+ values: {
+ value: text,
+ },
+ });
+
+export const filterOutText = i18n.translate('discover.logs.popoverAction.filterOut', {
+ defaultMessage: 'Filter out',
+});
+
+export const filterForText = i18n.translate('discover.logs.popoverAction.filterFor', {
+ defaultMessage: 'Filter for',
+});
+
+export const flyoutHoverActionFilterForFieldPresentText = i18n.translate(
+ 'discover.logs.flyoutDetail.value.hover.filterForFieldPresent',
+ {
+ defaultMessage: 'Filter for field present',
+ }
+);
+
+export const flyoutHoverActionToggleColumnText = i18n.translate(
+ 'discover.logs.flyoutDetail.value.hover.toggleColumn',
+ {
+ defaultMessage: 'Toggle column in table',
+ }
+);
+
+export const flyoutHoverActionCopyToClipboardText = i18n.translate(
+ 'discover.logs.flyoutDetail.value.hover.copyToClipboard',
+ {
+ defaultMessage: 'Copy to clipboard',
+ }
+);
+
+export const copyValueText = i18n.translate('discover.logs.popoverAction.copyValue', {
+ defaultMessage: 'Copy value',
+});
+
+export const copyValueAriaText = (fieldName: string) =>
+ i18n.translate('discover.logs.popoverAction.copyValueAriaText', {
+ defaultMessage: 'Copy value of {fieldName}',
+ values: {
+ fieldName,
+ },
+ });
+
+export const flyoutAccordionShowMoreText = (count: number) =>
+ i18n.translate('discover.logs.flyoutDetail.section.showMore', {
+ defaultMessage: '+ {hiddenCount} more',
+ values: {
+ hiddenCount: count,
+ },
+ });
+
+export const openCellActionPopoverAriaText = i18n.translate(
+ 'discover.logs.popoverAction.openPopover',
+ {
+ defaultMessage: 'Open popover',
+ }
+);
+
+export const closeCellActionPopoverText = i18n.translate(
+ 'discover.logs.popoverAction.closePopover',
+ {
+ defaultMessage: 'Close popover',
+ }
+);
+
+export const contentHeaderTooltipParagraph1 = (
+