[8.x] [Observability] Split up observability-utils package (#199801) (#200886)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Observability] Split up observability-utils package
(#199801)](https://github.com/elastic/kibana/pull/199801)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Dario
Gieselaar","email":"dario.gieselaar@elastic.co"},"sourceCommit":{"committedDate":"2024-11-13T18:51:42Z","message":"[Observability]
Split up observability-utils package (#199801)\n\nSplit up
observability-utils package in browser, common, server. Also\r\nmade a
small change to `withSpan` to automatically log operation times\r\nwhen
the debug level for the logger is
enabled.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"11a752da8751f447a083f050e0c4eeb85073fa56","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","backport
missing","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-infra_services","v8.17.0","v8.18.0"],"number":199801,"url":"https://github.com/elastic/kibana/pull/199801","mergeCommit":{"message":"[Observability]
Split up observability-utils package (#199801)\n\nSplit up
observability-utils package in browser, common, server. Also\r\nmade a
small change to `withSpan` to automatically log operation times\r\nwhen
the debug level for the logger is
enabled.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"11a752da8751f447a083f050e0c4eeb85073fa56"}},"sourceBranch":"main","suggestedTargetBranches":["8.x","8.18"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199801","number":199801,"mergeCommit":{"message":"[Observability]
Split up observability-utils package (#199801)\n\nSplit up
observability-utils package in browser, common, server. Also\r\nmade a
small change to `withSpan` to automatically log operation times\r\nwhen
the debug level for the logger is
enabled.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"11a752da8751f447a083f050e0c4eeb85073fa56"}},{"branch":"8.x","label":"v8.17.0","labelRegex":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Dario Gieselaar 2024-11-20 13:12:34 +01:00 committed by GitHub
parent 71cf9c8305
commit b7d06a222e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
87 changed files with 1225 additions and 101 deletions

View file

@ -704,7 +704,9 @@
"@kbn/observability-plugin": "link:x-pack/plugins/observability_solution/observability",
"@kbn/observability-shared-plugin": "link:x-pack/plugins/observability_solution/observability_shared",
"@kbn/observability-synthetics-test-data": "link:x-pack/packages/observability/synthetics_test_data",
"@kbn/observability-utils": "link:x-pack/packages/observability/observability_utils",
"@kbn/observability-utils-browser": "link:x-pack/packages/observability/observability_utils/observability_utils_browser",
"@kbn/observability-utils-common": "link:x-pack/packages/observability/observability_utils/observability_utils_common",
"@kbn/observability-utils-server": "link:x-pack/packages/observability/observability_utils/observability_utils_server",
"@kbn/oidc-provider-plugin": "link:x-pack/test/security_api_integration/plugins/oidc_provider",
"@kbn/open-telemetry-instrumented-plugin": "link:test/common/plugins/otel_metrics",
"@kbn/openapi-common": "link:packages/kbn-openapi-common",

View file

@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import agent from 'elastic-apm-node';
import agent, { Logger } from 'elastic-apm-node';
import asyncHooks from 'async_hooks';
export interface SpanOptions {
@ -34,14 +34,48 @@ const runInNewContext = <T extends (...args: any[]) => any>(cb: T): ReturnType<T
export async function withSpan<T>(
optionsOrName: SpanOptions | string,
cb: (span?: Span) => Promise<T>
cb: (span?: Span) => Promise<T>,
logger?: Logger
): Promise<T> {
const options = parseSpanOptions(optionsOrName);
const { name, type, subtype, labels, intercept } = options;
let time: number | undefined;
if (logger?.isLevelEnabled('debug')) {
time = performance.now();
}
function logTook(failed: boolean) {
if (time) {
logger?.debug(
() =>
`Operation ${name}${failed ? ` (failed)` : ''} ${
Math.round(performance.now() - time!) / 1000
}s`
);
}
}
const withLogTook = [
<TR>(res: TR): TR | Promise<TR> => {
logTook(false);
return res;
},
(err: any): never => {
logTook(true);
throw err;
},
];
if (!agent.isStarted()) {
return cb();
const promise = cb();
// make sure tests that mock out the callback with a sync
// function don't fail.
if (typeof promise === 'object' && 'then' in promise) {
return promise.then(...withLogTook);
}
return promise;
}
let createdSpan: Span | undefined;
@ -57,7 +91,7 @@ export async function withSpan<T>(
createdSpan = agent.startSpan(name) ?? undefined;
if (!createdSpan) {
return cb();
return cb().then(...withLogTook);
}
}
@ -76,7 +110,7 @@ export async function withSpan<T>(
}
if (!span) {
return promise;
return promise.then(...withLogTook);
}
const targetedSpan = span;
@ -98,6 +132,7 @@ export async function withSpan<T>(
}
return promise
.then(...withLogTook)
.then((res) => {
if (!targetedSpan.outcome || targetedSpan.outcome === 'unknown') {
targetedSpan.outcome = 'success';

View file

@ -1332,8 +1332,12 @@
"@kbn/observability-shared-plugin/*": ["x-pack/plugins/observability_solution/observability_shared/*"],
"@kbn/observability-synthetics-test-data": ["x-pack/packages/observability/synthetics_test_data"],
"@kbn/observability-synthetics-test-data/*": ["x-pack/packages/observability/synthetics_test_data/*"],
"@kbn/observability-utils": ["x-pack/packages/observability/observability_utils"],
"@kbn/observability-utils/*": ["x-pack/packages/observability/observability_utils/*"],
"@kbn/observability-utils-browser": ["x-pack/packages/observability/observability_utils/observability_utils_browser"],
"@kbn/observability-utils-browser/*": ["x-pack/packages/observability/observability_utils/observability_utils_browser/*"],
"@kbn/observability-utils-common": ["x-pack/packages/observability/observability_utils/observability_utils_common"],
"@kbn/observability-utils-common/*": ["x-pack/packages/observability/observability_utils/observability_utils_common/*"],
"@kbn/observability-utils-server": ["x-pack/packages/observability/observability_utils/observability_utils_server"],
"@kbn/observability-utils-server/*": ["x-pack/packages/observability/observability_utils/observability_utils_server/*"],
"@kbn/oidc-provider-plugin": ["x-pack/test/security_api_integration/plugins/oidc_provider"],
"@kbn/oidc-provider-plugin/*": ["x-pack/test/security_api_integration/plugins/oidc_provider/*"],
"@kbn/open-telemetry-instrumented-plugin": ["test/common/plugins/otel_metrics"],

View file

@ -81,3 +81,5 @@ export {
isInferenceInternalError,
isInferenceRequestError,
} from './src/errors';
export { truncateList } from './src/truncate_list';

View file

@ -1,5 +0,0 @@
# @kbn/observability-utils
This package contains utilities for Observability plugins. It's a separate package to get out of dependency hell. You can put anything in here that is stateless and has no dependency on other plugins (either directly or via other packages).
The utility functions should be used via direct imports to minimize impact on bundle size and limit the risk on importing browser code to the server and vice versa.

View file

@ -18,6 +18,9 @@ export function useAbortController() {
return {
signal: controller.signal,
abort: () => {
controller.abort();
},
refresh: () => {
setController(() => new AbortController());
},

View file

@ -17,12 +17,32 @@ export type AbortableAsyncState<T> = (T extends Promise<infer TReturn>
? State<TReturn>
: State<T>) & { refresh: () => void };
export type AbortableAsyncStateOf<T extends AbortableAsyncState<any>> =
T extends AbortableAsyncState<infer TResponse> ? Awaited<TResponse> : never;
interface UseAbortableAsyncOptions<T> {
clearValueOnNext?: boolean;
unsetValueOnError?: boolean;
defaultValue?: () => T;
onError?: (error: Error) => void;
}
export type UseAbortableAsync<
TAdditionalParameters extends Record<string, any> = {},
TAdditionalOptions extends Record<string, any> = {}
> = <T>(
fn: ({}: { signal: AbortSignal } & TAdditionalParameters) => T | Promise<T>,
deps: any[],
options?: UseAbortableAsyncOptions<T> & TAdditionalOptions
) => AbortableAsyncState<T>;
export function useAbortableAsync<T>(
fn: ({}: { signal: AbortSignal }) => T | Promise<T>,
deps: any[],
options?: { clearValueOnNext?: boolean; defaultValue?: () => T }
options?: UseAbortableAsyncOptions<T>
): AbortableAsyncState<T> {
const clearValueOnNext = options?.clearValueOnNext;
const unsetValueOnError = options?.unsetValueOnError;
const controllerRef = useRef(new AbortController());
@ -43,6 +63,15 @@ export function useAbortableAsync<T>(
setError(undefined);
}
function handleError(err: Error) {
setError(err);
if (unsetValueOnError) {
setValue(undefined);
}
setLoading(false);
options?.onError?.(err);
}
try {
const response = fn({ signal: controller.signal });
if (isPromise(response)) {
@ -52,12 +81,7 @@ export function useAbortableAsync<T>(
setError(undefined);
setValue(nextValue);
})
.catch((err) => {
setValue(undefined);
if (!controller.signal.aborted) {
setError(err);
}
})
.catch(handleError)
.finally(() => setLoading(false));
} else {
setError(undefined);
@ -65,9 +89,7 @@ export function useAbortableAsync<T>(
setLoading(false);
}
} catch (err) {
setValue(undefined);
setError(err);
setLoading(false);
handleError(err);
}
return () => {

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { TimeRange } from '@kbn/data-plugin/common';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { useCallback, useEffect, useMemo, useState } from 'react';
export function useDateRange({ data }: { data: DataPublicPluginStart }): {
timeRange: TimeRange;
absoluteTimeRange: {
start: number;
end: number;
};
setTimeRange: React.Dispatch<React.SetStateAction<TimeRange>>;
} {
const timefilter = data.query.timefilter.timefilter;
const [timeRange, setTimeRange] = useState(() => timefilter.getTime());
const [absoluteTimeRange, setAbsoluteTimeRange] = useState(() => timefilter.getAbsoluteTime());
useEffect(() => {
const timeUpdateSubscription = timefilter.getTimeUpdate$().subscribe({
next: () => {
setTimeRange(() => timefilter.getTime());
setAbsoluteTimeRange(() => timefilter.getAbsoluteTime());
},
});
return () => {
timeUpdateSubscription.unsubscribe();
};
}, [timefilter]);
const setTimeRangeMemoized: React.Dispatch<React.SetStateAction<TimeRange>> = useCallback(
(nextOrCallback) => {
const val =
typeof nextOrCallback === 'function'
? nextOrCallback(timefilter.getTime())
: nextOrCallback;
timefilter.setTime(val);
},
[timefilter]
);
const asEpoch = useMemo(() => {
return {
start: new Date(absoluteTimeRange.from).getTime(),
end: new Date(absoluteTimeRange.to).getTime(),
};
}, [absoluteTimeRange]);
return {
timeRange,
absoluteTimeRange: asEpoch,
setTimeRange: setTimeRangeMemoized,
};
}

View file

@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useState, useEffect, useMemo, useCallback } from 'react';
export function useLocalStorage<T>(key: string, defaultValue: T) {
// This is necessary to fix a race condition issue.
// It guarantees that the latest value will be always returned after the value is updated
const [storageUpdate, setStorageUpdate] = useState(0);
const item = useMemo(() => {
return getFromStorage(key, defaultValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key, storageUpdate, defaultValue]);
const saveToStorage = useCallback(
(value: T) => {
if (value === undefined) {
window.localStorage.removeItem(key);
} else {
window.localStorage.setItem(key, JSON.stringify(value));
setStorageUpdate(storageUpdate + 1);
}
},
[key, storageUpdate]
);
useEffect(() => {
function onUpdate(event: StorageEvent) {
if (event.key === key) {
setStorageUpdate(storageUpdate + 1);
}
}
window.addEventListener('storage', onUpdate);
return () => {
window.removeEventListener('storage', onUpdate);
};
}, [key, setStorageUpdate, storageUpdate]);
return useMemo(() => [item, saveToStorage] as const, [item, saveToStorage]);
}
function getFromStorage<T>(keyName: string, defaultValue: T) {
const storedItem = window.localStorage.getItem(keyName);
if (storedItem !== null) {
try {
return JSON.parse(storedItem) as T;
} catch (err) {
window.localStorage.removeItem(keyName);
// eslint-disable-next-line no-console
console.log(`Unable to decode: ${keyName}`);
}
}
return defaultValue;
}

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: [
'<rootDir>/x-pack/packages/observability/observability_utils/observability_utils_browser',
],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-browser",
"id": "@kbn/observability-utils-browser",
"owner": "@elastic/observability-ui"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/observability-utils-browser",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -0,0 +1,23 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/data-plugin",
"@kbn/core-ui-settings-browser",
"@kbn/std",
]
}

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { UI_SETTINGS } from '@kbn/data-plugin/public';
import { IUiSettingsClient } from '@kbn/core-ui-settings-browser';
export function getTimeZone(uiSettings?: IUiSettingsClient) {
const kibanaTimeZone = uiSettings?.get<'Browser' | string>(UI_SETTINGS.DATEFORMAT_TZ);
if (!kibanaTimeZone || kibanaTimeZone === 'Browser') {
return 'local';
}
return kibanaTimeZone;
}

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export function getEntityKuery(entity: Record<string, unknown>) {
return Object.entries(entity)
.map(([name, value]) => {
return `(${name}:"${value}")`;
})
.join(' AND ');
}

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export function formatValueForKql(value: string) {
return `(${value.replaceAll(/((^|[^\\])):/g, '\\:')})`;
}

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
export function entityQuery(entity: Record<string, string>): QueryDslQueryContainer[] {
return [
{
bool: {
filter: Object.entries(entity).map(([field, value]) => {
return {
term: {
[field]: value,
},
};
}),
},
},
];
}

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import numeral from '@elastic/numeral';
export function formatInteger(num: number) {
return numeral(num).format('0a');
}

View file

@ -7,6 +7,6 @@
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/x-pack/packages/observability/observability_utils'],
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/packages/observability/observability_utils/observability_utils_common'],
};

View file

@ -1,5 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/observability-utils",
"id": "@kbn/observability-utils-common",
"owner": "@elastic/observability-ui"
}

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export interface DocumentAnalysis {
total: number;
sampled: number;
fields: Array<{
name: string;
types: string[];
cardinality: number | null;
values: Array<string | number | boolean>;
empty: boolean;
}>;
}
export interface TruncatedDocumentAnalysis {
fields: string[];
total: number;
sampled: number;
}

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
function addCapturingGroupsToRegex(regex: string): string {
// Match all parts of the regex that are not special characters
// We treat constant parts as sequences of characters that are not part of regex syntax
return regex.replaceAll(/((?:\.\*\?)|(?:\.\+\?)|(?:\+\?))/g, (...args) => {
return `(${args[1]})`;
});
}
export function highlightPatternFromRegex(pattern: string, str: string): string {
// First, add non-capturing groups to the regex around constant parts
const updatedPattern = addCapturingGroupsToRegex(pattern);
const regex = new RegExp(updatedPattern, 'ds');
const matches = str.match(regex) as
| (RegExpMatchArray & { indices: Array<[number, number]> })
| null;
const slices: string[] = [];
matches?.forEach((_, index) => {
if (index === 0) {
return;
}
const [, prevEnd] = index > 1 ? matches?.indices[index - 1] : [undefined, undefined];
const [start, end] = matches?.indices[index];
const literalSlice = prevEnd !== undefined ? str.slice(prevEnd, start) : undefined;
if (literalSlice) {
slices.push(`<em>${literalSlice}</em>`);
}
const slice = str.slice(start, end);
slices.push(slice);
if (index === matches.length - 1) {
slices.push(str.slice(end));
}
});
return slices.join('');
}

View file

@ -0,0 +1,78 @@
/*
* 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 { castArray, sortBy, uniq } from 'lodash';
import type { DocumentAnalysis } from './document_analysis';
export function mergeSampleDocumentsWithFieldCaps({
total,
samples,
fieldCaps,
}: {
total: number;
samples: Array<Record<string, unknown | unknown[]>>;
fieldCaps: Array<{ name: string; esTypes?: string[] }>;
}): DocumentAnalysis {
const nonEmptyFields = new Set<string>();
const fieldValues = new Map<string, Array<string | number | boolean>>();
for (const document of samples) {
Object.keys(document).forEach((field) => {
if (!nonEmptyFields.has(field)) {
nonEmptyFields.add(field);
}
const values = castArray(document[field]);
const currentFieldValues = fieldValues.get(field) ?? [];
values.forEach((value) => {
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
currentFieldValues.push(value);
}
});
fieldValues.set(field, currentFieldValues);
});
}
const fields = fieldCaps.flatMap((spec) => {
const values = fieldValues.get(spec.name);
const countByValues = new Map<string | number | boolean, number>();
values?.forEach((value) => {
const currentCount = countByValues.get(value) ?? 0;
countByValues.set(value, currentCount + 1);
});
const sortedValues = sortBy(
Array.from(countByValues.entries()).map(([value, count]) => {
return {
value,
count,
};
}),
'count',
'desc'
);
return {
name: spec.name,
types: spec.esTypes ?? [],
empty: !nonEmptyFields.has(spec.name),
cardinality: countByValues.size || null,
values: uniq(sortedValues.flatMap(({ value }) => value)),
};
});
return {
total,
sampled: samples.length,
fields,
};
}

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { partition, shuffle } from 'lodash';
import { truncateList } from '@kbn/inference-common';
import type { DocumentAnalysis, TruncatedDocumentAnalysis } from './document_analysis';
export function sortAndTruncateAnalyzedFields(
analysis: DocumentAnalysis
): TruncatedDocumentAnalysis {
const { fields, ...meta } = analysis;
const [nonEmptyFields, emptyFields] = partition(analysis.fields, (field) => !field.empty);
const sortedFields = [...shuffle(nonEmptyFields), ...shuffle(emptyFields)];
return {
...meta,
fields: truncateList(
sortedFields.map((field) => {
let label = `${field.name}:${field.types.join(',')}`;
if (field.empty) {
return `${name} (empty)`;
}
label += ` - ${field.cardinality} distinct values`;
if (field.name === '@timestamp' || field.name === 'event.ingested') {
return `${label}`;
}
const shortValues = field.values.filter((value) => {
return String(value).length <= 1024;
});
if (shortValues.length) {
return `${label} (${truncateList(
shortValues.map((value) => '`' + value + '`'),
field.types.includes('text') || field.types.includes('match_only_text') ? 2 : 10
).join(', ')})`;
}
return label;
}),
500
).sort(),
};
}

View file

@ -0,0 +1,48 @@
/*
* 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 { ShortIdTable } from './short_id_table';
describe('shortIdTable', () => {
it('generates at least 10k unique ids consistently', () => {
const ids = new Set();
const table = new ShortIdTable();
let i = 10_000;
while (i--) {
const id = table.take(String(i));
ids.add(id);
}
expect(ids.size).toBe(10_000);
});
it('returns the original id based on the generated id', () => {
const table = new ShortIdTable();
const idsByOriginal = new Map<string, string>();
let i = 100;
while (i--) {
const id = table.take(String(i));
idsByOriginal.set(String(i), id);
}
expect(idsByOriginal.size).toBe(100);
expect(() => {
Array.from(idsByOriginal.entries()).forEach(([originalId, shortId]) => {
const returnedOriginalId = table.lookup(shortId);
if (returnedOriginalId !== originalId) {
throw Error(
`Expected shortId ${shortId} to return ${originalId}, but ${returnedOriginalId} was returned instead`
);
}
});
}).not.toThrow();
});
});

View file

@ -0,0 +1,56 @@
/*
* 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.
*/
const ALPHABET = 'abcdefghijklmnopqrstuvwxyz';
function generateShortId(size: number): string {
let id = '';
let i = size;
while (i--) {
const index = Math.floor(Math.random() * ALPHABET.length);
id += ALPHABET[index];
}
return id;
}
const MAX_ATTEMPTS_AT_LENGTH = 100;
export class ShortIdTable {
private byShortId: Map<string, string> = new Map();
private byOriginalId: Map<string, string> = new Map();
constructor() {}
take(originalId: string) {
if (this.byOriginalId.has(originalId)) {
return this.byOriginalId.get(originalId)!;
}
let uniqueId: string | undefined;
let attemptsAtLength = 0;
let length = 4;
while (!uniqueId) {
const nextId = generateShortId(length);
attemptsAtLength++;
if (!this.byShortId.has(nextId)) {
uniqueId = nextId;
} else if (attemptsAtLength >= MAX_ATTEMPTS_AT_LENGTH) {
attemptsAtLength = 0;
length++;
}
}
this.byShortId.set(uniqueId, originalId);
this.byOriginalId.set(originalId, uniqueId);
return uniqueId;
}
lookup(shortId: string) {
return this.byShortId.get(shortId);
}
}

View file

@ -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.
*/
export const P_VALUE_SIGNIFICANCE_HIGH = 1e-6;
export const P_VALUE_SIGNIFICANCE_MEDIUM = 0.001;
export function pValueToLabel(pValue: number): 'high' | 'medium' | 'low' {
if (pValue <= P_VALUE_SIGNIFICANCE_HIGH) {
return 'high';
} else if (pValue <= P_VALUE_SIGNIFICANCE_MEDIUM) {
return 'medium';
} else {
return 'low';
}
}

View file

@ -11,7 +11,6 @@ export function unflattenObject(source: Record<string, any>, target: Record<stri
// eslint-disable-next-line guard-for-in
for (const key in source) {
const val = source[key as keyof typeof source];
if (Array.isArray(val)) {
const unflattenedArray = val.map((item) => {
if (item && typeof item === 'object' && !Array.isArray(item)) {

View file

@ -1,6 +1,6 @@
{
"name": "@kbn/observability-utils",
"name": "@kbn/observability-utils-common",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}
}

View file

@ -1,5 +1,5 @@
{
"extends": "../../../../tsconfig.base.json",
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
@ -16,11 +16,8 @@
"target/**/*"
],
"kbn_references": [
"@kbn/std",
"@kbn/core",
"@kbn/es-types",
"@kbn/apm-utils",
"@kbn/es-query",
"@kbn/safer-lodash-set",
"@kbn/inference-common",
]
}

View file

@ -0,0 +1,84 @@
/*
* 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 { mapValues } from 'lodash';
import { mergeSampleDocumentsWithFieldCaps } from '@kbn/observability-utils-common/llm/log_analysis/merge_sample_documents_with_field_caps';
import { DocumentAnalysis } from '@kbn/observability-utils-common/llm/log_analysis/document_analysis';
import type { ObservabilityElasticsearchClient } from '../es/client/create_observability_es_client';
import { kqlQuery } from '../es/queries/kql_query';
import { rangeQuery } from '../es/queries/range_query';
export async function analyzeDocuments({
esClient,
kuery,
start,
end,
index,
}: {
esClient: ObservabilityElasticsearchClient;
kuery: string;
start: number;
end: number;
index: string | string[];
}): Promise<DocumentAnalysis> {
const [fieldCaps, hits] = await Promise.all([
esClient.fieldCaps('get_field_caps_for_document_analysis', {
index,
fields: '*',
index_filter: {
bool: {
filter: rangeQuery(start, end),
},
},
}),
esClient
.search('get_document_samples', {
index,
size: 1000,
track_total_hits: true,
query: {
bool: {
must: [...kqlQuery(kuery), ...rangeQuery(start, end)],
should: [
{
function_score: {
functions: [
{
random_score: {},
},
],
},
},
],
},
},
sort: {
_score: {
order: 'desc',
},
},
_source: false,
fields: ['*' as const],
})
.then((response) => ({
hits: response.hits.hits.map((hit) =>
mapValues(hit.fields!, (value) => (value.length === 1 ? value[0] : value))
),
total: response.hits.total,
})),
]);
const analysis = mergeSampleDocumentsWithFieldCaps({
samples: hits.hits,
total: hits.total.value,
fieldCaps: Object.entries(fieldCaps.fields).map(([name, specs]) => {
return { name, esTypes: Object.keys(specs) };
}),
});
return analysis;
}

View file

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { compact, uniq } from 'lodash';
import { ObservabilityElasticsearchClient } from '../es/client/create_observability_es_client';
import { excludeFrozenQuery } from '../es/queries/exclude_frozen_query';
import { kqlQuery } from '../es/queries/kql_query';
export async function getDataStreamsForEntity({
esClient,
kuery,
index,
}: {
esClient: ObservabilityElasticsearchClient;
kuery: string;
index: string | string[];
}) {
const response = await esClient.search('get_data_streams_for_entity', {
track_total_hits: false,
index,
size: 0,
terminate_after: 1,
timeout: '1ms',
aggs: {
indices: {
terms: {
field: '_index',
size: 10000,
},
},
},
query: {
bool: {
filter: [...excludeFrozenQuery(), ...kqlQuery(kuery)],
},
},
});
const allIndices =
response.aggregations?.indices.buckets.map((bucket) => bucket.key as string) ?? [];
if (!allIndices.length) {
return {
dataStreams: [],
};
}
const resolveIndexResponse = await esClient.client.indices.resolveIndex({
name: allIndices,
});
const dataStreams = uniq(
compact(await resolveIndexResponse.indices.flatMap((idx) => idx.data_stream))
);
return {
dataStreams,
};
}

View file

@ -0,0 +1,57 @@
/*
* 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 { RulesClient } from '@kbn/alerting-plugin/server';
import { AlertsClient } from '@kbn/rule-registry-plugin/server';
import {
ALERT_GROUP_FIELD,
ALERT_GROUP_VALUE,
ALERT_STATUS,
ALERT_STATUS_ACTIVE,
ALERT_TIME_RANGE,
} from '@kbn/rule-data-utils';
import { kqlQuery } from '../../es/queries/kql_query';
import { rangeQuery } from '../../es/queries/range_query';
export async function getAlertsForEntity({
start,
end,
entity,
alertsClient,
rulesClient,
size,
}: {
start: number;
end: number;
entity: Record<string, unknown>;
alertsClient: AlertsClient;
rulesClient: RulesClient;
size: number;
}) {
const alertsKuery = Object.entries(entity)
.map(([field, value]) => {
return `(${[
`(${ALERT_GROUP_FIELD}:"${field}" AND ${ALERT_GROUP_VALUE}:"${value}")`,
`(${field}:"${value}")`,
].join(' OR ')})`;
})
.join(' AND ');
const openAlerts = await alertsClient.find({
size,
query: {
bool: {
filter: [
...kqlQuery(alertsKuery),
...rangeQuery(start, end, ALERT_TIME_RANGE),
{ term: { [ALERT_STATUS]: ALERT_STATUS_ACTIVE } },
],
},
},
});
return openAlerts;
}

View file

@ -0,0 +1,16 @@
/*
* 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 getAnomaliesForEntity({
start,
end,
entity,
}: {
start: number;
end: number;
entity: Record<string, unknown>;
}) {}

View file

@ -0,0 +1,80 @@
/*
* 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 { ObservabilityElasticsearchClient } from '../../es/client/create_observability_es_client';
import { kqlQuery } from '../../es/queries/kql_query';
export async function getSlosForEntity({
start,
end,
entity,
esClient,
sloSummaryIndices,
size,
spaceId,
}: {
start: number;
end: number;
entity: Record<string, unknown>;
esClient: ObservabilityElasticsearchClient;
sloSummaryIndices: string | string[];
size: number;
spaceId: string;
}) {
const slosKuery = Object.entries(entity)
.map(([field, value]) => {
return `(slo.groupings.${field}:"${value}")`;
})
.join(' AND ');
const sloSummaryResponse = await esClient.search('get_slo_summaries_for_entity', {
index: sloSummaryIndices,
size,
track_total_hits: false,
query: {
bool: {
filter: [
...kqlQuery(slosKuery),
{
range: {
'slo.createdAt': {
lte: end,
},
},
},
{
range: {
summaryUpdatedAt: {
gte: start,
},
},
},
{
term: {
spaceId,
},
},
],
},
},
});
return {
...sloSummaryResponse,
hits: {
...sloSummaryResponse.hits,
hits: sloSummaryResponse.hits.hits.map((hit) => {
return {
...hit,
_source: hit._source as Record<string, any> & {
status: 'VIOLATED' | 'DEGRADED' | 'HEALTHY' | 'NO_DATA';
},
};
}),
},
};
}

View file

@ -5,11 +5,18 @@
* 2.0.
*/
import type {
EsqlQueryRequest,
FieldCapsRequest,
FieldCapsResponse,
MsearchRequest,
SearchResponse,
} from '@elastic/elasticsearch/lib/api/types';
import { withSpan } from '@kbn/apm-utils';
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
import type { ESQLSearchResponse, ESSearchRequest, InferSearchResponseOf } from '@kbn/es-types';
import { withSpan } from '@kbn/apm-utils';
import type { EsqlQueryRequest } from '@elastic/elasticsearch/lib/api/types';
import { esqlResultToPlainObjects } from '../utils/esql_result_to_plain_objects';
import { Required } from 'utility-types';
import { esqlResultToPlainObjects } from '../esql_result_to_plain_objects';
type SearchRequest = ESSearchRequest & {
index: string | string[];
@ -39,7 +46,17 @@ export interface ObservabilityElasticsearchClient {
search<TDocument = unknown, TSearchRequest extends SearchRequest = SearchRequest>(
operationName: string,
parameters: TSearchRequest
): Promise<InferSearchResponseOf<TDocument, TSearchRequest>>;
): Promise<InferSearchResponseOf<TDocument, TSearchRequest, { restTotalHitsAsInt: false }>>;
msearch<TDocument = unknown>(
operationName: string,
parameters: MsearchRequest
): Promise<{
responses: Array<SearchResponse<TDocument>>;
}>;
fieldCaps(
operationName: string,
request: Required<FieldCapsRequest, 'index_filter' | 'fields' | 'index'>
): Promise<FieldCapsResponse>;
esql<TOutput = unknown, TQueryParams extends EsqlOutputParameters = EsqlOutputParameters>(
operationName: string,
parameters: TQueryParams
@ -60,8 +77,38 @@ export function createObservabilityEsClient({
logger: Logger;
plugin: string;
}): ObservabilityElasticsearchClient {
// wraps the ES calls in a named APM span for better analysis
// (otherwise it would just eg be a _search span)
const callWithLogger = <T>(
operationName: string,
request: Record<string, any>,
callback: () => Promise<T>
) => {
logger.debug(() => `Request (${operationName}):\n${JSON.stringify(request)}`);
return withSpan(
{
name: operationName,
labels: {
plugin,
},
},
callback,
logger
).then((response) => {
logger.trace(() => `Response (${operationName}):\n${JSON.stringify(response, null, 2)}`);
return response;
});
};
return {
client,
fieldCaps(operationName, parameters) {
return callWithLogger(operationName, parameters, () => {
return client.fieldCaps({
...parameters,
});
});
},
esql<TOutput = unknown, TSearchRequest extends EsqlParameters = EsqlParameters>(
operationName: string,
{ parseOutput = true, format = 'json', columnar = false, ...parameters }: TSearchRequest
@ -93,24 +140,17 @@ export function createObservabilityEsClient({
operationName: string,
parameters: SearchRequest
) {
logger.trace(() => `Request (${operationName}):\n${JSON.stringify(parameters, null, 2)}`);
// wraps the search operation in a named APM span for better analysis
// (otherwise it would just be a _search span)
return withSpan(
{
name: operationName,
labels: {
plugin,
},
},
() => {
return client.search<TDocument>(parameters) as unknown as Promise<
InferSearchResponseOf<TDocument, TSearchRequest>
>;
}
).then((response) => {
logger.trace(() => `Response (${operationName}):\n${JSON.stringify(response, null, 2)}`);
return response;
return callWithLogger(operationName, parameters, () => {
return client.search<TDocument>(parameters) as unknown as Promise<
InferSearchResponseOf<TDocument, TSearchRequest, { restTotalHitsAsInt: false }>
>;
});
},
msearch<TDocument = unknown>(operationName: string, parameters: MsearchRequest) {
return callWithLogger(operationName, parameters, () => {
return client.msearch<TDocument>(parameters) as unknown as Promise<{
responses: Array<SearchResponse<TDocument>>;
}>;
});
},
};

View file

@ -6,7 +6,7 @@
*/
import type { ESQLSearchResponse } from '@kbn/es-types';
import { unflattenObject } from '../../object/unflatten_object';
import { unflattenObject } from '@kbn/observability-utils-common/object/unflatten_object';
export function esqlResultToPlainObjects<TDocument = unknown>(
result: ESQLSearchResponse

View file

@ -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 type { estypes } from '@elastic/elasticsearch';
export function excludeFrozenQuery(): estypes.QueryDslQueryContainer[] {
return [
{
bool: {
must_not: [
{
term: {
_tier: 'data_frozen',
},
},
],
},
},
];
}

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { estypes } from '@elastic/elasticsearch';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
export function kqlQuery(kql?: string): estypes.QueryDslQueryContainer[] {
if (!kql) {
return [];
}
const ast = fromKueryExpression(kql);
return [toElasticsearchQuery(ast)];
}

View file

@ -0,0 +1,25 @@
/*
* 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 { estypes } from '@elastic/elasticsearch';
export function rangeQuery(
start?: number,
end?: number,
field = '@timestamp'
): estypes.QueryDslQueryContainer[] {
return [
{
range: {
[field]: {
gte: start,
lte: end,
format: 'epoch_millis',
},
},
},
];
}

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
interface TermQueryOpts {
queryEmptyString: boolean;
}
export function termQuery<T extends string>(
field: T,
value: string | boolean | number | undefined | null,
opts: TermQueryOpts = { queryEmptyString: true }
): QueryDslQueryContainer[] {
if (value === null || value === undefined || (!opts.queryEmptyString && value === '')) {
return [];
}
return [{ term: { [field]: value } }];
}

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../../..',
roots: ['<rootDir>/x-pack/packages/observability/observability_utils/observability_utils_server'],
};

View file

@ -0,0 +1,5 @@
{
"type": "shared-server",
"id": "@kbn/observability-utils-server",
"owner": "@elastic/observability-ui"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/observability-utils-server",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -0,0 +1,28 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/core",
"@kbn/es-types",
"@kbn/apm-utils",
"@kbn/es-query",
"@kbn/observability-utils-common",
"@kbn/alerting-plugin",
"@kbn/rule-registry-plugin",
"@kbn/rule-data-utils",
]
}

View file

@ -23,7 +23,7 @@ import { ValuesType } from 'utility-types';
import type { APMError, Metric, Span, Transaction, Event } from '@kbn/apm-types/es_schemas_ui';
import type { InspectResponse } from '@kbn/observability-plugin/typings/common';
import type { DataTier } from '@kbn/observability-shared-plugin/common';
import { excludeTiersQuery } from '@kbn/observability-utils/es/queries/exclude_tiers_query';
import { excludeTiersQuery } from '@kbn/observability-utils-common/es/queries/exclude_tiers_query';
import { withApmSpan } from '../../../../utils';
import type { ApmDataSource } from '../../../../../common/data_source';
import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort';

View file

@ -6,7 +6,7 @@
*/
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { DataTier } from '@kbn/observability-shared-plugin/common';
import { excludeTiersQuery } from '@kbn/observability-utils/es/queries/exclude_tiers_query';
import { excludeTiersQuery } from '@kbn/observability-utils-common/es/queries/exclude_tiers_query';
export function getDataTierFilterCombined({
filter,

View file

@ -8,8 +8,8 @@
import type { DedotObject } from '@kbn/utility-types';
import * as APM_EVENT_FIELDS_MAP from '@kbn/apm-types/es_fields';
import type { ValuesType } from 'utility-types';
import { unflattenObject } from '@kbn/observability-utils/object/unflatten_object';
import { mergePlainObjects } from '@kbn/observability-utils/object/merge_plain_objects';
import { unflattenObject } from '@kbn/observability-utils-common/object/unflatten_object';
import { mergePlainObjects } from '@kbn/observability-utils-common/object/merge_plain_objects';
import { castArray, isArray } from 'lodash';
import { AgentName } from '@kbn/elastic-agent-utils';
import { EventOutcome } from '@kbn/apm-types/src/es_schemas/raw/fields';

View file

@ -20,8 +20,8 @@
"@kbn/apm-utils",
"@kbn/core-http-server",
"@kbn/security-plugin-types-server",
"@kbn/observability-utils",
"@kbn/utility-types",
"@kbn/elastic-agent-utils"
"@kbn/elastic-agent-utils",
"@kbn/observability-utils-common"
]
}

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
import { type EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client';
import { type InfraMetricsClient } from '../../lib/helpers/get_infra_metrics_client';
import { getDataStreamTypes } from './get_data_stream_types';

View file

@ -8,7 +8,7 @@
import { type EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client';
import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common';
import { EntityDataStreamType } from '@kbn/observability-shared-plugin/common';
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
import { castArray } from 'lodash';
import { type InfraMetricsClient } from '../../lib/helpers/get_infra_metrics_client';
import { getHasMetricsData } from './get_has_metrics_data';

View file

@ -8,7 +8,7 @@
import { ENTITY_LATEST, entitiesAliasPattern } from '@kbn/entities-schema';
import { type EntityClient } from '@kbn/entityManager-plugin/server/lib/entity_client';
import { ENTITY_TYPE, SOURCE_DATA_STREAM_TYPE } from '@kbn/observability-shared-plugin/common';
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
const ENTITIES_LATEST_ALIAS = entitiesAliasPattern({
type: '*',

View file

@ -8,7 +8,7 @@
import { schema } from '@kbn/config-schema';
import { METRICS_APP_ID } from '@kbn/deeplinks-observability/constants';
import { entityCentricExperience } from '@kbn/observability-plugin/common';
import { createObservabilityEsClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import { createObservabilityEsClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
import { ENTITY_TYPES } from '@kbn/observability-shared-plugin/common';
import { getInfraMetricsClient } from '../../lib/helpers/get_infra_metrics_client';
import { InfraBackendLibs } from '../../lib/infra_types';

View file

@ -114,9 +114,9 @@
"@kbn/management-settings-ids",
"@kbn/core-ui-settings-common",
"@kbn/entityManager-plugin",
"@kbn/observability-utils",
"@kbn/entities-schema",
"@kbn/zod"
"@kbn/zod",
"@kbn/observability-utils-server"
],
"exclude": ["target/**/*"]
}

View file

@ -8,7 +8,7 @@ import { EuiSpacer } from '@elastic/eui';
import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common';
import React from 'react';
import useEffectOnce from 'react-use/lib/useEffectOnce';
import { flattenObject } from '@kbn/observability-utils/object/flatten_object';
import { flattenObject } from '@kbn/observability-utils-common/object/flatten_object';
import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async';
import { useKibana } from '../../hooks/use_kibana';
import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { useAbortableAsync } from '@kbn/observability-utils/hooks/use_abortable_async';
import { useAbortableAsync } from '@kbn/observability-utils-browser/hooks/use_abortable_async';
import { useState } from 'react';
import { useKibana } from './use_kibana';

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useAbortableAsync } from '@kbn/observability-utils/hooks/use_abortable_async';
import { useAbortableAsync } from '@kbn/observability-utils-browser/hooks/use_abortable_async';
import { i18n } from '@kbn/i18n';
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser';
import { useKibana } from './use_kibana';

View file

@ -6,7 +6,7 @@
*/
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
import {
ENTITIES_LATEST_ALIAS,
type EntityGroup,

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common';
import type { EntityInstance } from '@kbn/entities-schema';
import { ENTITIES_LATEST_ALIAS } from '../../../common/entities';

View file

@ -5,19 +5,19 @@
* 2.0.
*/
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import {
ENTITY_LAST_SEEN,
ENTITY_TYPE,
ENTITY_DISPLAY_NAME,
} from '@kbn/observability-shared-plugin/common';
import type { QueryDslQueryContainer, ScalarValue } from '@elastic/elasticsearch/lib/api/types';
import type { EntityInstance } from '@kbn/entities-schema';
import {
ENTITY_DISPLAY_NAME,
ENTITY_LAST_SEEN,
ENTITY_TYPE,
} from '@kbn/observability-shared-plugin/common';
import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
import {
ENTITIES_LATEST_ALIAS,
InventoryEntity,
MAX_NUMBER_OF_ENTITIES,
type EntityColumnIds,
InventoryEntity,
} from '../../../common/entities';
import { getBuiltinEntityDefinitionIdESQLWhereClause } from './query_helper';

View file

@ -6,19 +6,19 @@
*/
import { INVENTORY_APP_ID } from '@kbn/deeplinks-observability/constants';
import { jsonRt } from '@kbn/io-ts-utils';
import { createObservabilityEsClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common';
import { joinByKey } from '@kbn/observability-utils-common/array/join_by_key';
import { createObservabilityEsClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
import * as t from 'io-ts';
import { orderBy } from 'lodash';
import { joinByKey } from '@kbn/observability-utils/array/join_by_key';
import { entityColumnIdsRt, InventoryEntity } from '../../../common/entities';
import { createInventoryServerRoute } from '../create_inventory_server_route';
import { getEntityTypes } from './get_entity_types';
import { getLatestEntities } from './get_latest_entities';
import { InventoryEntity, entityColumnIdsRt } from '../../../common/entities';
import { createAlertsClient } from '../../lib/create_alerts_client.ts/create_alerts_client';
import { getLatestEntitiesAlerts } from './get_latest_entities_alerts';
import { getIdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type';
import { createInventoryServerRoute } from '../create_inventory_server_route';
import { getEntityGroupsBy } from './get_entity_groups';
import { getEntityTypes } from './get_entity_types';
import { getIdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type';
import { getLatestEntities } from './get_latest_entities';
import { getLatestEntitiesAlerts } from './get_latest_entities_alerts';
export const getEntityTypesRoute = createInventoryServerRoute({
endpoint: 'GET /internal/inventory/entities/types',

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { Logger } from '@kbn/core/server';
import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
import { getBuiltinEntityDefinitionIdESQLWhereClause } from '../entities/query_helper';
import { ENTITIES_LATEST_ALIAS } from '../../../common/entities';

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { createObservabilityEsClient } from '@kbn/observability-utils/es/client/create_observability_es_client';
import { createObservabilityEsClient } from '@kbn/observability-utils-server/es/client/create_observability_es_client';
import { INVENTORY_APP_ID } from '@kbn/deeplinks-observability/constants';
import { createInventoryServerRoute } from '../create_inventory_server_route';
import { getHasData } from './get_has_data';

View file

@ -20,7 +20,6 @@
"@kbn/server-route-repository",
"@kbn/shared-ux-link-redirect-app",
"@kbn/typed-react-router-config",
"@kbn/observability-utils",
"@kbn/kibana-react-plugin",
"@kbn/i18n",
"@kbn/deeplinks-observability",
@ -57,6 +56,9 @@
"@kbn/deeplinks-analytics",
"@kbn/controls-plugin",
"@kbn/securitysolution-io-ts-types",
"@kbn/react-hooks"
"@kbn/react-hooks",
"@kbn/observability-utils-common",
"@kbn/observability-utils-browser",
"@kbn/observability-utils-server"
]
}

View file

@ -8,7 +8,7 @@ import { EuiLoadingSpinner, EuiFlexItem } from '@elastic/eui';
import { css } from '@emotion/css';
import { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public';
import type { GlobalWidgetParameters } from '@kbn/investigate-plugin/public';
import { useAbortableAsync } from '@kbn/observability-utils/hooks/use_abortable_async';
import { useAbortableAsync } from '@kbn/observability-utils-browser/hooks/use_abortable_async';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { v4 } from 'uuid';
import { ErrorMessage } from '../../components/error_message';

View file

@ -10,7 +10,7 @@ import type { ESQLSearchResponse } from '@kbn/es-types';
import { i18n } from '@kbn/i18n';
import { type GlobalWidgetParameters } from '@kbn/investigate-plugin/public';
import type { Suggestion } from '@kbn/lens-plugin/public';
import { useAbortableAsync } from '@kbn/observability-utils/hooks/use_abortable_async';
import { useAbortableAsync } from '@kbn/observability-utils-browser/hooks/use_abortable_async';
import React, { useMemo } from 'react';
import { ErrorMessage } from '../../components/error_message';
import { useKibana } from '../../hooks/use_kibana';

View file

@ -11,7 +11,7 @@ import type { ESQLColumn, ESQLRow } from '@kbn/es-types';
import { GlobalWidgetParameters } from '@kbn/investigate-plugin/public';
import { Item } from '@kbn/investigation-shared';
import type { Suggestion } from '@kbn/lens-plugin/public';
import { useAbortableAsync } from '@kbn/observability-utils/hooks/use_abortable_async';
import { useAbortableAsync } from '@kbn/observability-utils-browser/hooks/use_abortable_async';
import React, { useEffect, useMemo, useState } from 'react';
import { ErrorMessage } from '../../../../components/error_message';
import { SuggestVisualizationList } from '../../../../components/suggest_visualization_list';

View file

@ -10,7 +10,7 @@ import moment from 'moment';
import { Chart, Axis, AreaSeries, Position, ScaleType, Settings } from '@elastic/charts';
import { useActiveCursor } from '@kbn/charts-plugin/public';
import { EuiSkeletonText } from '@elastic/eui';
import { getBrushData } from '@kbn/observability-utils/chart/utils';
import { getBrushData } from '@kbn/observability-utils-browser/chart/utils';
import { Group } from '@kbn/observability-alerting-rule-utils';
import { ALERT_GROUP } from '@kbn/rule-data-utils';
import { SERVICE_NAME } from '@kbn/observability-shared-plugin/common';

View file

@ -7,7 +7,7 @@
import pLimit from 'p-limit';
import { estypes } from '@elastic/elasticsearch';
import { castArray, sortBy, uniq, partition, shuffle } from 'lodash';
import { truncateList } from '@kbn/inference-plugin/common/utils/truncate_list';
import { truncateList } from '@kbn/inference-common';
import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types';
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import { rangeQuery, excludeFrozenQuery } from './queries';

View file

@ -62,13 +62,13 @@
"@kbn/licensing-plugin",
"@kbn/rule-data-utils",
"@kbn/entities-schema",
"@kbn/inference-plugin",
"@kbn/core-elasticsearch-server",
"@kbn/calculate-auto",
"@kbn/ml-random-sampler-utils",
"@kbn/charts-plugin",
"@kbn/observability-utils",
"@kbn/observability-alerting-rule-utils",
"@kbn/observability-utils-browser",
"@kbn/usage-collection-plugin",
"@kbn/inference-common",
],
}

View file

@ -6447,7 +6447,15 @@
version "0.0.0"
uid ""
"@kbn/observability-utils@link:x-pack/packages/observability/observability_utils":
"@kbn/observability-utils-browser@link:x-pack/packages/observability/observability_utils/observability_utils_browser":
version "0.0.0"
uid ""
"@kbn/observability-utils-common@link:x-pack/packages/observability/observability_utils/observability_utils_common":
version "0.0.0"
uid ""
"@kbn/observability-utils-server@link:x-pack/packages/observability/observability_utils/observability_utils_server":
version "0.0.0"
uid ""