Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Logs Explorer] Support logs data views #176078

Merged
merged 42 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
88bdc25
feature(logs-explorer): wip logs-backed data views
Jan 31, 2024
cef8208
feature(logs-explorer): prepare explorer data view data structures
Feb 1, 2024
c112c4d
refactor(logs-explorer): wip routing for set data view selections
Feb 1, 2024
ae18df5
feature(logs-explorer): wip data views integrations
Feb 1, 2024
2743146
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 2, 2024
c2fe373
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 2, 2024
3b06e96
fix(logs-explorer): update selection only for determined cases
Feb 2, 2024
c6ad9c3
fix(logs-explorer): update control panel correctly
Feb 2, 2024
42603a3
fix(logs-explorer): update types
Feb 5, 2024
524d93d
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 5, 2024
6b3d183
fix(logs-explorer): renaming
Feb 5, 2024
0b5a328
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 5, 2024
15686c5
fix(logs-explorer): missing destructure
Feb 5, 2024
9e1315e
fix(logs-explorer): renaming
Feb 5, 2024
02a4cb2
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 5, 2024
9bbae5c
fix(logs-explorer): renaming mode to dataView
Feb 5, 2024
a193ea9
refactor(logs-explorer): renaming data view concept
Feb 5, 2024
c0d07c2
refactor(logs-explorer): fix statecharts uml
Feb 5, 2024
9e5f0ca
refactor(logs-explorer): rename handler
Feb 5, 2024
9aa6597
refactor(logs-explorer): rename component
Feb 5, 2024
e2fd413
refactor(logs-explorer): restore data view test subj
Feb 5, 2024
bebf0d1
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 6, 2024
539dcd3
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 6, 2024
66a36a1
fix(logs-explorer): avoid double state notification
Feb 6, 2024
95a854a
fix(logs-explorer): restore selector selection momde
Feb 8, 2024
a0d3eaa
refactor(logs-explorer): update selection validation and init flow
Feb 8, 2024
af509fc
refactor(logs-explorer): remove import
Feb 8, 2024
e90e1f0
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Feb 8, 2024
a9013f6
refactor(logs-explorer): update init logic
Feb 9, 2024
a138d74
Merge branch '175767-support-data-views' of github.com:tonyghiani/kib…
Feb 9, 2024
309f574
refactor(logs-explorer): update statechart representation
Feb 9, 2024
7dcefa2
refactor(logs-explorer): update statechart types
Feb 9, 2024
cf5d8e9
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 9, 2024
11332ee
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 12, 2024
ded1e99
refactor(logs-explorer): add columns to discover navigation
Feb 12, 2024
6ecc83c
refactor(logs-explorer): remove unused import
Feb 12, 2024
f8d903f
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 12, 2024
0695287
refactor(logs-explorer): minor request changes
Feb 13, 2024
f818965
refactor(logs-explorer): restrict plugins access
Feb 13, 2024
7a997a2
refactor(logs-explorer): update execution order for change/set data view
Feb 13, 2024
7f720f1
Merge branch 'main' into 175767-support-data-views
tonyghiani Feb 13, 2024
4adaf96
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Feb 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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 { DataViewDescriptor } from './data_view_descriptor';

describe('DataViewDescriptor', () => {
it('should correctly assert whether a data view has "logs" type', () => {
const id = 'test-id';

// Assert truthy cases
expect(DataViewDescriptor.create({ id, title: 'auditbeat*' }).isLogsDataType()).toBeTruthy();
expect(DataViewDescriptor.create({ id, title: 'auditbeat-*' }).isLogsDataType()).toBeTruthy();
expect(DataViewDescriptor.create({ id, title: 'logs*' }).isLogsDataType()).toBeTruthy();
expect(DataViewDescriptor.create({ id, title: 'logs-*' }).isLogsDataType()).toBeTruthy();
expect(DataViewDescriptor.create({ id, title: 'logs-*-*' }).isLogsDataType()).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'logs-system.syslog-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'logs-system.syslog-default' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-system.syslog-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({
id,
title: 'cluster1:logs-system.syslog-default',
}).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'logs-*,cluster1:logs-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'logs-*,cluster1:logs-*,' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*,cluster2:logs-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*,cluster2:logs-*' }).isLogsDataType()
).toBeTruthy();
expect(
DataViewDescriptor.create({
id,
title: '*:logs-system.syslog-*,*:logs-system.errors-*',
}).isLogsDataType()
).toBeTruthy();

// Assert falsy cases
expect(DataViewDescriptor.create({ id, title: 'auditbeats*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'auditbeats-*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'logss*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'logss-*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'metrics*' }).isLogsDataType()).toBeFalsy();
expect(DataViewDescriptor.create({ id, title: 'metrics-*' }).isLogsDataType()).toBeFalsy();
expect(
DataViewDescriptor.create({
id,
title: '*:metrics-system.syslog-*,logs-system.errors-*',
}).isLogsDataType()
).toBeFalsy();
expect(
DataViewDescriptor.create({ id, title: 'cluster1:logs-*,clust,er2:logs-*' }).isLogsDataType()
).toBeFalsy();
expect(
DataViewDescriptor.create({
id,
title: 'cluster1:logs-*, cluster2:logs-*',
}).isLogsDataType()
).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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 { DataViewListItem } from '@kbn/data-views-plugin/common';
import { DataViewSpecWithId } from '../../dataset_selection';
import { DataViewDescriptorType } from '../types';
import { buildIndexPatternRegExp } from '../utils';

type Allowlist = Array<string | RegExp>;

const LOGS_ALLOWLIST: Allowlist = [
buildIndexPatternRegExp(['logs', 'auditbeat', 'filebeat', 'winbeat']),
// Add more strings or regex patterns as needed
];

export class DataViewDescriptor {
id: DataViewDescriptorType['id'];
dataType: DataViewDescriptorType['dataType'];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this property will be useful to differentiate between different data views and group them by its value.

kibanaSpaces: DataViewDescriptorType['kibanaSpaces'];
name: DataViewDescriptorType['name'];
title: DataViewDescriptorType['title'];
type: DataViewDescriptorType['type'];

private constructor(dataViewDescriptor: DataViewDescriptorType) {
this.id = dataViewDescriptor.id;
this.dataType = dataViewDescriptor.dataType;
this.kibanaSpaces = dataViewDescriptor.kibanaSpaces;
this.name = dataViewDescriptor.name;
this.title = dataViewDescriptor.title;
this.type = dataViewDescriptor.type;
}

getFullTitle() {
return this.name;
}

toDataviewSpec(): DataViewSpecWithId {
return {
id: this.id,
name: this.name,
title: this.title,
};
}

toPlain() {
return {
id: this.id,
dataType: this.dataType,
name: this.name,
title: this.title,
};
}

public static create({ id, namespaces, title, type, name }: DataViewListItem) {
const nameWithFallbackTitle = name ?? title;
const dataType = DataViewDescriptor.#extractDataType(title);
const kibanaSpaces = namespaces;

return new DataViewDescriptor({
id,
dataType,
kibanaSpaces,
name: nameWithFallbackTitle,
title,
type,
});
}

static #extractDataType(title: string): DataViewDescriptorType['dataType'] {
if (isAllowed(title, LOGS_ALLOWLIST)) {
return 'logs';
}

return 'unknown';
}

public isLogsDataType() {
return this.dataType === 'logs';
}

public isUnknownDataType() {
return this.dataType === 'unknown';
}
}

function isAllowed(value: string, allowList: Allowlist) {
for (const allowedItem of allowList) {
if (typeof allowedItem === 'string') {
return value === allowedItem;
}
if (allowedItem instanceof RegExp) {
return allowedItem.test(value);
}
}

// If no match is found in the allowList, return false
return false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 * as rt from 'io-ts';

const dataTypeRT = rt.keyof({
logs: null,
unknown: null,
});

export const dataViewDescriptorRT = rt.exact(
rt.intersection([
rt.type({
id: rt.string,
name: rt.string,
title: rt.string,
dataType: dataTypeRT,
}),
rt.partial({
kibanaSpaces: rt.array(rt.string),
type: rt.string,
}),
])
);

export type DataViewDescriptorType = rt.TypeOf<typeof dataViewDescriptorRT>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export const buildIndexPatternRegExp = (basePatterns: string[]) => {
Copy link
Contributor Author

@tonyghiani tonyghiani Feb 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: Starting from a single base pattern such as logs, metrics, etc.., this function allows to create a regular expression to test an index pattern for:

  • word boundaries and subsequent paths (logs-, logs-system.syslog-default, etc...)
  • local and remote clusters (logs-, remote_cluster:logs-*, etc...)
  • multiple patterns, supporting above features (logs-*,remote_cluster:logs-*, etc...)

// Create the base patterns union with strict boundaries
const basePatternGroup = `\\b(${basePatterns.join('|')})\\b[^,\\s]+`;
// Apply base patterns union for local and remote clusters
const localAndRemotePatternGroup = `((${basePatternGroup})|([^:,\\s]+:${basePatternGroup}))`;
// Handle trailing comma and multiple pattern concatenation
return new RegExp(`^${localAndRemotePatternGroup}(,${localAndRemotePatternGroup})*(,$|$)`, 'i');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really loved the explanatory way of creating the regex 💯

};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 { DataViewDescriptor } from '../data_views/models/data_view_descriptor';
import { DatasetSelectionStrategy, DataViewSelectionPayload } from './types';

export class DataViewSelection implements DatasetSelectionStrategy {
selectionType: 'dataView';
selection: {
dataView: DataViewDescriptor;
};

private constructor(dataViewDescriptor: DataViewDescriptor) {
this.selectionType = 'dataView';
this.selection = {
dataView: dataViewDescriptor,
};
}

toDataviewSpec() {
return this.selection.dataView.toDataviewSpec();
}

toPlainSelection() {
return {
selectionType: this.selectionType,
selection: {
dataView: this.selection.dataView.toPlain(),
},
};
}

public static fromSelection(selection: DataViewSelectionPayload) {
const { dataView } = selection;

const dataViewDescriptor = DataViewDescriptor.create(dataView);
return DataViewSelection.create(dataViewDescriptor);
}

public static create(dataViewDescriptor: DataViewDescriptor) {
return new DataViewSelection(dataViewDescriptor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { AllDatasetSelection } from './all_dataset_selection';
import { DataViewSelection } from './data_view_selection';
import { SingleDatasetSelection } from './single_dataset_selection';
import { DatasetSelectionPlain } from './types';
import { UnresolvedDatasetSelection } from './unresolved_dataset_selection';
Expand All @@ -15,6 +16,8 @@ export const hydrateDatasetSelection = (datasetSelection: DatasetSelectionPlain)
return AllDatasetSelection.create();
} else if (datasetSelection.selectionType === 'single') {
return SingleDatasetSelection.fromSelection(datasetSelection.selection);
} else if (datasetSelection.selectionType === 'dataView') {
return DataViewSelection.fromSelection(datasetSelection.selection);
} else {
return UnresolvedDatasetSelection.fromSelection(datasetSelection.selection);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
* 2.0.
*/

import { DataViewListItem } from '@kbn/data-views-plugin/common';
import { AllDatasetSelection } from './all_dataset_selection';
import { DataViewSelection } from './data_view_selection';
import { SingleDatasetSelection } from './single_dataset_selection';
import { UnresolvedDatasetSelection } from './unresolved_dataset_selection';

export type DatasetSelection =
| AllDatasetSelection
| SingleDatasetSelection
| UnresolvedDatasetSelection;
export type DatasetSelectionChange = (datasetSelection: DatasetSelection) => void;
export type DataViewSelection = (dataView: DataViewListItem) => void;
export type SelectionChange = (selection: DatasetSelection | DataViewSelection) => void;

export const isDatasetSelection = (input: any): input is DatasetSelection => {
return (
Expand All @@ -25,7 +24,17 @@ export const isDatasetSelection = (input: any): input is DatasetSelection => {
);
};

export const isUnresolvedDatasetSelection = (input: any): input is UnresolvedDatasetSelection => {
return input instanceof UnresolvedDatasetSelection;
};

export const isDataViewSelection = (input: any): input is DataViewSelection => {
return input instanceof DataViewSelection;
};

export * from './all_dataset_selection';
export * from './data_view_selection';
export * from './single_dataset_selection';
export * from './single_dataset_selection';
export * from './unresolved_dataset_selection';
export * from './errors';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class SingleDatasetSelection implements DatasetSelectionStrategy {
const integration = name && version ? { name, title, version } : undefined;
const datasetInstance = Dataset.create(dataset, integration);

return new SingleDatasetSelection(datasetInstance);
return SingleDatasetSelection.create(datasetInstance);
}

public static create(dataset: Dataset) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { DataViewSpec } from '@kbn/data-views-plugin/common';
import * as rt from 'io-ts';
import { datasetRT } from '../datasets';
import { dataViewDescriptorRT } from '../data_views/types';

export const allDatasetSelectionPlainRT = rt.type({
selectionType: rt.literal('all'),
Expand All @@ -33,6 +34,10 @@ const singleDatasetSelectionPayloadRT = rt.intersection([
}),
]);

const dataViewSelectionPayloadRT = rt.type({
dataView: dataViewDescriptorRT,
});

const unresolvedDatasetSelectionPayloadRT = rt.intersection([
integrationNameRT,
rt.type({
Expand All @@ -45,18 +50,25 @@ export const singleDatasetSelectionPlainRT = rt.type({
selection: singleDatasetSelectionPayloadRT,
});

export const dataViewSelectionPlainRT = rt.type({
selectionType: rt.literal('dataView'),
selection: dataViewSelectionPayloadRT,
});

export const unresolvedDatasetSelectionPlainRT = rt.type({
selectionType: rt.literal('unresolved'),
selection: unresolvedDatasetSelectionPayloadRT,
});

export const datasetSelectionPlainRT = rt.union([
allDatasetSelectionPlainRT,
dataViewSelectionPlainRT,
singleDatasetSelectionPlainRT,
unresolvedDatasetSelectionPlainRT,
]);

export type SingleDatasetSelectionPayload = rt.TypeOf<typeof singleDatasetSelectionPayloadRT>;
export type DataViewSelectionPayload = rt.TypeOf<typeof dataViewSelectionPayloadRT>;
export type UnresolvedDatasetSelectionPayload = rt.TypeOf<
typeof unresolvedDatasetSelectionPayloadRT
>;
Expand Down
Loading