mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Backports the following commits to 7.x: - [Logs UI] Add customizable columns to source configuration (#34916)
This commit is contained in:
parent
9a765673a2
commit
f0ecf26b28
68 changed files with 3677 additions and 1926 deletions
|
@ -20,4 +20,33 @@ export const sharedFragments = {
|
|||
updatedAt
|
||||
}
|
||||
`,
|
||||
InfraLogEntryFields: gql`
|
||||
fragment InfraLogEntryFields on InfraLogEntry {
|
||||
gid
|
||||
key {
|
||||
time
|
||||
tiebreaker
|
||||
}
|
||||
columns {
|
||||
... on InfraLogEntryTimestampColumn {
|
||||
timestamp
|
||||
}
|
||||
... on InfraLogEntryMessageColumn {
|
||||
message {
|
||||
... on InfraLogMessageFieldSegment {
|
||||
field
|
||||
value
|
||||
}
|
||||
... on InfraLogMessageConstantSegment {
|
||||
constant
|
||||
}
|
||||
}
|
||||
}
|
||||
... on InfraLogEntryFieldColumn {
|
||||
field
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
|
|
@ -53,6 +53,8 @@ export interface InfraSourceConfiguration {
|
|||
logAlias: string;
|
||||
/** The field mapping to use for this source */
|
||||
fields: InfraSourceFields;
|
||||
/** The columns to use for log display */
|
||||
logColumns: InfraSourceLogColumn[];
|
||||
}
|
||||
/** A mapping of semantic fields to their document counterparts */
|
||||
export interface InfraSourceFields {
|
||||
|
@ -69,6 +71,35 @@ export interface InfraSourceFields {
|
|||
/** The field to use as a timestamp for metrics and logs */
|
||||
timestamp: string;
|
||||
}
|
||||
/** The built-in timestamp log column */
|
||||
export interface InfraSourceTimestampLogColumn {
|
||||
timestampColumn: InfraSourceTimestampLogColumnAttributes;
|
||||
}
|
||||
|
||||
export interface InfraSourceTimestampLogColumnAttributes {
|
||||
/** A unique id for the column */
|
||||
id: string;
|
||||
}
|
||||
/** The built-in message log column */
|
||||
export interface InfraSourceMessageLogColumn {
|
||||
messageColumn: InfraSourceMessageLogColumnAttributes;
|
||||
}
|
||||
|
||||
export interface InfraSourceMessageLogColumnAttributes {
|
||||
/** A unique id for the column */
|
||||
id: string;
|
||||
}
|
||||
/** A log column containing a field value */
|
||||
export interface InfraSourceFieldLogColumn {
|
||||
fieldColumn: InfraSourceFieldLogColumnAttributes;
|
||||
}
|
||||
|
||||
export interface InfraSourceFieldLogColumnAttributes {
|
||||
/** A unique id for the column */
|
||||
id: string;
|
||||
/** The field name this column refers to */
|
||||
field: string;
|
||||
}
|
||||
/** The status of an infrastructure data source */
|
||||
export interface InfraSourceStatus {
|
||||
/** Whether the configured metric alias exists */
|
||||
|
@ -143,6 +174,16 @@ export interface InfraLogEntry {
|
|||
gid: string;
|
||||
/** The source id */
|
||||
source: string;
|
||||
/** The columns used for rendering the log entry */
|
||||
columns: InfraLogEntryColumn[];
|
||||
}
|
||||
/** A special built-in column that contains the log entry's timestamp */
|
||||
export interface InfraLogEntryTimestampColumn {
|
||||
/** The timestamp */
|
||||
timestamp: number;
|
||||
}
|
||||
/** A special built-in column that contains the log entry's constructed message */
|
||||
export interface InfraLogEntryMessageColumn {
|
||||
/** A list of the formatted log entry segments */
|
||||
message: InfraLogMessageSegment[];
|
||||
}
|
||||
|
@ -155,11 +196,18 @@ export interface InfraLogMessageFieldSegment {
|
|||
/** A list of highlighted substrings of the value */
|
||||
highlights: string[];
|
||||
}
|
||||
/** A segment of the log entry message that was derived from a field */
|
||||
/** A segment of the log entry message that was derived from a string literal */
|
||||
export interface InfraLogMessageConstantSegment {
|
||||
/** The segment's message */
|
||||
constant: string;
|
||||
}
|
||||
/** A column that contains the value of a field of the log entry */
|
||||
export interface InfraLogEntryFieldColumn {
|
||||
/** The field name of the column */
|
||||
field: string;
|
||||
/** The value of the field in the log entry */
|
||||
value: string;
|
||||
}
|
||||
/** A consecutive sequence of log summary buckets */
|
||||
export interface InfraLogSummaryInterval {
|
||||
/** The millisecond timestamp corresponding to the start of the interval covered by the summary */
|
||||
|
@ -246,20 +294,15 @@ export interface InfraDataPoint {
|
|||
|
||||
export interface Mutation {
|
||||
/** Create a new source of infrastructure data */
|
||||
createSource: CreateSourceResult;
|
||||
/** Modify an existing source using the given sequence of update operations */
|
||||
createSource: UpdateSourceResult;
|
||||
/** Modify an existing source */
|
||||
updateSource: UpdateSourceResult;
|
||||
/** Delete a source of infrastructure data */
|
||||
deleteSource: DeleteSourceResult;
|
||||
}
|
||||
/** The result of a successful source creation */
|
||||
export interface CreateSourceResult {
|
||||
/** The source that was created */
|
||||
source: InfraSource;
|
||||
}
|
||||
/** The result of a sequence of source update operations */
|
||||
/** The result of a successful source update */
|
||||
export interface UpdateSourceResult {
|
||||
/** The source after the operations were performed */
|
||||
/** The source that was updated */
|
||||
source: InfraSource;
|
||||
}
|
||||
/** The result of a source deletion operations */
|
||||
|
@ -298,10 +341,10 @@ export interface InfraSnapshotMetricInput {
|
|||
/** The type of metric */
|
||||
type: InfraSnapshotMetricType;
|
||||
}
|
||||
/** The source to be created */
|
||||
export interface CreateSourceInput {
|
||||
/** The properties to update the source with */
|
||||
export interface UpdateSourceInput {
|
||||
/** The name of the data source */
|
||||
name: string;
|
||||
name?: string | null;
|
||||
/** A description of the data source */
|
||||
description?: string | null;
|
||||
/** The alias to read metric data from */
|
||||
|
@ -309,10 +352,12 @@ export interface CreateSourceInput {
|
|||
/** The alias to read log data from */
|
||||
logAlias?: string | null;
|
||||
/** The field mapping to use for this source */
|
||||
fields?: CreateSourceFieldsInput | null;
|
||||
fields?: UpdateSourceFieldsInput | null;
|
||||
/** The log columns to display for this source */
|
||||
logColumns?: UpdateSourceLogColumnInput[] | null;
|
||||
}
|
||||
/** The mapping of semantic fields of the source to be created */
|
||||
export interface CreateSourceFieldsInput {
|
||||
export interface UpdateSourceFieldsInput {
|
||||
/** The field to identify a container by */
|
||||
container?: string | null;
|
||||
/** The fields to identify a host by */
|
||||
|
@ -324,46 +369,28 @@ export interface CreateSourceFieldsInput {
|
|||
/** The field to use as a timestamp for metrics and logs */
|
||||
timestamp?: string | null;
|
||||
}
|
||||
/** The update operations to be performed */
|
||||
export interface UpdateSourceInput {
|
||||
/** The name update operation to be performed */
|
||||
setName?: UpdateSourceNameInput | null;
|
||||
/** The description update operation to be performed */
|
||||
setDescription?: UpdateSourceDescriptionInput | null;
|
||||
/** The alias update operation to be performed */
|
||||
setAliases?: UpdateSourceAliasInput | null;
|
||||
/** The field update operation to be performed */
|
||||
setFields?: UpdateSourceFieldsInput | null;
|
||||
/** One of the log column types to display for this source */
|
||||
export interface UpdateSourceLogColumnInput {
|
||||
/** A custom field log column */
|
||||
fieldColumn?: UpdateSourceFieldLogColumnInput | null;
|
||||
/** A built-in message log column */
|
||||
messageColumn?: UpdateSourceMessageLogColumnInput | null;
|
||||
/** A built-in timestamp log column */
|
||||
timestampColumn?: UpdateSourceTimestampLogColumnInput | null;
|
||||
}
|
||||
/** A name update operation */
|
||||
export interface UpdateSourceNameInput {
|
||||
/** The new name to be set */
|
||||
name: string;
|
||||
|
||||
export interface UpdateSourceFieldLogColumnInput {
|
||||
id: string;
|
||||
|
||||
field: string;
|
||||
}
|
||||
/** A description update operation */
|
||||
export interface UpdateSourceDescriptionInput {
|
||||
/** The new description to be set */
|
||||
description: string;
|
||||
|
||||
export interface UpdateSourceMessageLogColumnInput {
|
||||
id: string;
|
||||
}
|
||||
/** An alias update operation */
|
||||
export interface UpdateSourceAliasInput {
|
||||
/** The new log index pattern or alias to bet set */
|
||||
logAlias?: string | null;
|
||||
/** The new metric index pattern or alias to bet set */
|
||||
metricAlias?: string | null;
|
||||
}
|
||||
/** A field update operations */
|
||||
export interface UpdateSourceFieldsInput {
|
||||
/** The new container field to be set */
|
||||
container?: string | null;
|
||||
/** The new host field to be set */
|
||||
host?: string | null;
|
||||
/** The new pod field to be set */
|
||||
pod?: string | null;
|
||||
/** The new tiebreaker field to be set */
|
||||
tiebreaker?: string | null;
|
||||
/** The new timestamp field to be set */
|
||||
timestamp?: string | null;
|
||||
|
||||
export interface UpdateSourceTimestampLogColumnInput {
|
||||
id: string;
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
|
@ -442,13 +469,13 @@ export interface CreateSourceMutationArgs {
|
|||
/** The id of the source */
|
||||
id: string;
|
||||
|
||||
source: CreateSourceInput;
|
||||
sourceProperties: UpdateSourceInput;
|
||||
}
|
||||
export interface UpdateSourceMutationArgs {
|
||||
/** The id of the source */
|
||||
id: string;
|
||||
/** A sequence of update operations */
|
||||
changes: UpdateSourceInput[];
|
||||
/** The properties to update the source with */
|
||||
sourceProperties: UpdateSourceInput;
|
||||
}
|
||||
export interface DeleteSourceMutationArgs {
|
||||
/** The id of the source */
|
||||
|
@ -515,6 +542,18 @@ export enum InfraMetric {
|
|||
// Unions
|
||||
// ====================================================
|
||||
|
||||
/** All known log column types */
|
||||
export type InfraSourceLogColumn =
|
||||
| InfraSourceTimestampLogColumn
|
||||
| InfraSourceMessageLogColumn
|
||||
| InfraSourceFieldLogColumn;
|
||||
|
||||
/** A column of a log entry */
|
||||
export type InfraLogEntryColumn =
|
||||
| InfraLogEntryTimestampColumn
|
||||
| InfraLogEntryMessageColumn
|
||||
| InfraLogEntryFieldColumn;
|
||||
|
||||
/** A segment of the log entry message */
|
||||
export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment;
|
||||
|
||||
|
@ -708,7 +747,7 @@ export namespace MetricsQuery {
|
|||
export namespace CreateSourceConfigurationMutation {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
sourceConfiguration: CreateSourceInput;
|
||||
sourceProperties: UpdateSourceInput;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
|
@ -718,7 +757,7 @@ export namespace CreateSourceConfigurationMutation {
|
|||
};
|
||||
|
||||
export type CreateSource = {
|
||||
__typename?: 'CreateSourceResult';
|
||||
__typename?: 'UpdateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
@ -763,7 +802,7 @@ export namespace SourceQuery {
|
|||
export namespace UpdateSourceMutation {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
changes: UpdateSourceInput[];
|
||||
sourceProperties: UpdateSourceInput;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
|
@ -891,41 +930,7 @@ export namespace LogEntries {
|
|||
|
||||
export type End = InfraTimeKeyFields.Fragment;
|
||||
|
||||
export type Entries = {
|
||||
__typename?: 'InfraLogEntry';
|
||||
|
||||
gid: string;
|
||||
|
||||
key: Key;
|
||||
|
||||
message: Message[];
|
||||
};
|
||||
|
||||
export type Key = {
|
||||
__typename?: 'InfraTimeKey';
|
||||
|
||||
time: number;
|
||||
|
||||
tiebreaker: number;
|
||||
};
|
||||
|
||||
export type Message =
|
||||
| InfraLogMessageFieldSegmentInlineFragment
|
||||
| InfraLogMessageConstantSegmentInlineFragment;
|
||||
|
||||
export type InfraLogMessageFieldSegmentInlineFragment = {
|
||||
__typename?: 'InfraLogMessageFieldSegment';
|
||||
|
||||
field: string;
|
||||
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type InfraLogMessageConstantSegmentInlineFragment = {
|
||||
__typename?: 'InfraLogMessageConstantSegment';
|
||||
|
||||
constant: string;
|
||||
};
|
||||
export type Entries = InfraLogEntryFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace SourceConfigurationFields {
|
||||
|
@ -941,6 +946,8 @@ export namespace SourceConfigurationFields {
|
|||
metricAlias: string;
|
||||
|
||||
fields: Fields;
|
||||
|
||||
logColumns: LogColumns[];
|
||||
};
|
||||
|
||||
export type Fields = {
|
||||
|
@ -958,6 +965,49 @@ export namespace SourceConfigurationFields {
|
|||
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
export type LogColumns =
|
||||
| InfraSourceTimestampLogColumnInlineFragment
|
||||
| InfraSourceMessageLogColumnInlineFragment
|
||||
| InfraSourceFieldLogColumnInlineFragment;
|
||||
|
||||
export type InfraSourceTimestampLogColumnInlineFragment = {
|
||||
__typename?: 'InfraSourceTimestampLogColumn';
|
||||
|
||||
timestampColumn: TimestampColumn;
|
||||
};
|
||||
|
||||
export type TimestampColumn = {
|
||||
__typename?: 'InfraSourceTimestampLogColumnAttributes';
|
||||
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type InfraSourceMessageLogColumnInlineFragment = {
|
||||
__typename?: 'InfraSourceMessageLogColumn';
|
||||
|
||||
messageColumn: MessageColumn;
|
||||
};
|
||||
|
||||
export type MessageColumn = {
|
||||
__typename?: 'InfraSourceMessageLogColumnAttributes';
|
||||
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type InfraSourceFieldLogColumnInlineFragment = {
|
||||
__typename?: 'InfraSourceFieldLogColumn';
|
||||
|
||||
fieldColumn: FieldColumn;
|
||||
};
|
||||
|
||||
export type FieldColumn = {
|
||||
__typename?: 'InfraSourceFieldLogColumnAttributes';
|
||||
|
||||
id: string;
|
||||
|
||||
field: string;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace SourceStatusFields {
|
||||
|
@ -1005,3 +1055,66 @@ export namespace InfraSourceFields {
|
|||
updatedAt?: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace InfraLogEntryFields {
|
||||
export type Fragment = {
|
||||
__typename?: 'InfraLogEntry';
|
||||
|
||||
gid: string;
|
||||
|
||||
key: Key;
|
||||
|
||||
columns: Columns[];
|
||||
};
|
||||
|
||||
export type Key = {
|
||||
__typename?: 'InfraTimeKey';
|
||||
|
||||
time: number;
|
||||
|
||||
tiebreaker: number;
|
||||
};
|
||||
|
||||
export type Columns =
|
||||
| InfraLogEntryTimestampColumnInlineFragment
|
||||
| InfraLogEntryMessageColumnInlineFragment
|
||||
| InfraLogEntryFieldColumnInlineFragment;
|
||||
|
||||
export type InfraLogEntryTimestampColumnInlineFragment = {
|
||||
__typename?: 'InfraLogEntryTimestampColumn';
|
||||
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
export type InfraLogEntryMessageColumnInlineFragment = {
|
||||
__typename?: 'InfraLogEntryMessageColumn';
|
||||
|
||||
message: Message[];
|
||||
};
|
||||
|
||||
export type Message =
|
||||
| InfraLogMessageFieldSegmentInlineFragment
|
||||
| InfraLogMessageConstantSegmentInlineFragment;
|
||||
|
||||
export type InfraLogMessageFieldSegmentInlineFragment = {
|
||||
__typename?: 'InfraLogMessageFieldSegment';
|
||||
|
||||
field: string;
|
||||
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type InfraLogMessageConstantSegmentInlineFragment = {
|
||||
__typename?: 'InfraLogMessageConstantSegment';
|
||||
|
||||
constant: string;
|
||||
};
|
||||
|
||||
export type InfraLogEntryFieldColumnInlineFragment = {
|
||||
__typename?: 'InfraLogEntryFieldColumn';
|
||||
|
||||
field: string;
|
||||
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,4 +5,3 @@
|
|||
*/
|
||||
|
||||
export * from './log_entry';
|
||||
export * from './log_entry_list';
|
||||
|
|
|
@ -5,12 +5,9 @@
|
|||
*/
|
||||
|
||||
import { TimeKey } from '../time';
|
||||
import { InfraLogEntry } from '../graphql/types';
|
||||
|
||||
export interface LogEntry {
|
||||
gid: string;
|
||||
origin: LogEntryOrigin;
|
||||
fields: LogEntryFields;
|
||||
}
|
||||
export type LogEntry = InfraLogEntry;
|
||||
|
||||
export interface LogEntryOrigin {
|
||||
id: string;
|
||||
|
@ -18,15 +15,7 @@ export interface LogEntryOrigin {
|
|||
type: string;
|
||||
}
|
||||
|
||||
export interface LogEntryFields extends LogEntryTime {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type LogEntryTime = TimeKey;
|
||||
// export interface LogEntryTime {
|
||||
// tiebreaker: number;
|
||||
// time: number;
|
||||
// }
|
||||
|
||||
export interface LogEntryFieldsMapping {
|
||||
message: string;
|
||||
|
@ -34,14 +23,6 @@ export interface LogEntryFieldsMapping {
|
|||
time: string;
|
||||
}
|
||||
|
||||
export function getLogEntryKey(entry: LogEntry) {
|
||||
return {
|
||||
gid: entry.gid,
|
||||
tiebreaker: entry.fields.tiebreaker,
|
||||
time: entry.fields.time,
|
||||
};
|
||||
}
|
||||
|
||||
export function isEqual(time1: LogEntryTime, time2: LogEntryTime) {
|
||||
return time1.time === time2.time && time1.tiebreaker === time2.tiebreaker;
|
||||
}
|
||||
|
|
|
@ -1,43 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
getLogEntryKey,
|
||||
isEqual,
|
||||
isLess,
|
||||
isLessOrEqual,
|
||||
LogEntry,
|
||||
LogEntryTime,
|
||||
} from './log_entry';
|
||||
|
||||
export type LogEntryList = LogEntry[];
|
||||
|
||||
export function getIndexNearLogEntry(logEntries: LogEntryList, key: LogEntryTime, highest = false) {
|
||||
let minIndex = 0;
|
||||
let maxIndex = logEntries.length;
|
||||
let currentIndex: number;
|
||||
let currentKey: LogEntryTime;
|
||||
|
||||
while (minIndex < maxIndex) {
|
||||
currentIndex = (minIndex + maxIndex) >>> 1; // eslint-disable-line no-bitwise
|
||||
currentKey = getLogEntryKey(logEntries[currentIndex]);
|
||||
|
||||
if ((highest ? isLessOrEqual : isLess)(currentKey, key)) {
|
||||
minIndex = currentIndex + 1;
|
||||
} else {
|
||||
maxIndex = currentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return maxIndex;
|
||||
}
|
||||
|
||||
export function getIndexOfLogEntry(logEntries: LogEntry[], key: LogEntryTime) {
|
||||
const index = getIndexNearLogEntry(logEntries, key);
|
||||
const logEntry = logEntries[index];
|
||||
|
||||
return logEntry && isEqual(key, getLogEntryKey(logEntry)) ? index : null;
|
||||
}
|
|
@ -1,202 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InfraSourceConfiguration } from './graphql/types';
|
||||
import { convertChangeToUpdater } from './source_configuration';
|
||||
|
||||
const initialConfiguration: InfraSourceConfiguration = {
|
||||
name: 'INITIAL_NAME',
|
||||
description: 'INITIAL_DESCRIPTION',
|
||||
logAlias: 'INITIAL_LOG_ALIAS',
|
||||
metricAlias: 'INITIAL_METRIC_ALIAS',
|
||||
fields: {
|
||||
container: 'INITIAL_CONTAINER_FIELD',
|
||||
host: 'INITIAL_HOST_FIELD',
|
||||
message: ['INITIAL_MESSAGE_FIELD'],
|
||||
pod: 'INITIAL_POD_FIELD',
|
||||
tiebreaker: 'INITIAL_TIEBREAKER_FIELD',
|
||||
timestamp: 'INITIAL_TIMESTAMP_FIELD',
|
||||
},
|
||||
};
|
||||
|
||||
describe('infrastructure source configuration', () => {
|
||||
describe('convertChangeToUpdater function', () => {
|
||||
it('creates a no-op updater for an empty change', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual(initialConfiguration);
|
||||
});
|
||||
|
||||
describe('setName operation', () => {
|
||||
it('creates a name updater', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setName: {
|
||||
name: 'CHANGED_NAME',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
name: 'CHANGED_NAME',
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a name updater even for an empty string', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setName: {
|
||||
name: '',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
name: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setDescription operation', () => {
|
||||
it('creates a description updater', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setDescription: {
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
description: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a description updater even for an empty string', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setDescription: {
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
description: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAliases operation', () => {
|
||||
it('creates a partial alias updater for a partial `setAliases` operation', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setAliases: {
|
||||
logAlias: 'CHANGED_LOG_ALIAS',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
logAlias: 'CHANGED_LOG_ALIAS',
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a complete alias updater for a complete `setAliases` operation', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setAliases: {
|
||||
logAlias: 'CHANGED_LOG_ALIAS',
|
||||
metricAlias: 'CHANGED_METRIC_ALIAS',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
logAlias: 'CHANGED_LOG_ALIAS',
|
||||
metricAlias: 'CHANGED_METRIC_ALIAS',
|
||||
});
|
||||
});
|
||||
|
||||
it('creates an alias updater even for empty strings', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setAliases: {
|
||||
logAlias: '',
|
||||
metricAlias: '',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
logAlias: '',
|
||||
metricAlias: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setFields operation', () => {
|
||||
it('creates a partial field updater for a partial `setFields` operation', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setFields: {
|
||||
host: 'CHANGED_HOST',
|
||||
timestamp: 'CHANGED_TIMESTAMP',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
fields: {
|
||||
...initialConfiguration.fields,
|
||||
host: 'CHANGED_HOST',
|
||||
timestamp: 'CHANGED_TIMESTAMP',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a complete field updater for a complete `setFields` operation', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setFields: {
|
||||
container: 'CHANGED_CONTAINER',
|
||||
host: 'CHANGED_HOST',
|
||||
pod: 'CHANGED_POD',
|
||||
tiebreaker: 'CHANGED_TIEBREAKER',
|
||||
timestamp: 'CHANGED_TIMESTAMP',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
fields: {
|
||||
...initialConfiguration.fields,
|
||||
container: 'CHANGED_CONTAINER',
|
||||
host: 'CHANGED_HOST',
|
||||
pod: 'CHANGED_POD',
|
||||
tiebreaker: 'CHANGED_TIEBREAKER',
|
||||
timestamp: 'CHANGED_TIMESTAMP',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a field updater even for empty strings', () => {
|
||||
const updateConfiguration = convertChangeToUpdater({
|
||||
setFields: {
|
||||
container: '',
|
||||
host: '',
|
||||
pod: '',
|
||||
tiebreaker: '',
|
||||
timestamp: '',
|
||||
},
|
||||
});
|
||||
|
||||
expect(updateConfiguration(initialConfiguration)).toEqual({
|
||||
...initialConfiguration,
|
||||
fields: {
|
||||
...initialConfiguration.fields,
|
||||
container: '',
|
||||
host: '',
|
||||
pod: '',
|
||||
tiebreaker: '',
|
||||
timestamp: '',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,47 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InfraSourceConfiguration, UpdateSourceInput } from './graphql/types';
|
||||
|
||||
export const convertChangeToUpdater = (change: UpdateSourceInput) => <
|
||||
C extends InfraSourceConfiguration
|
||||
>(
|
||||
configuration: C
|
||||
): C => {
|
||||
const updaters: Array<(c: C) => C> = [
|
||||
c => (change.setName ? { ...c, name: change.setName.name } : c),
|
||||
c => (change.setDescription ? { ...c, description: change.setDescription.description } : c),
|
||||
c =>
|
||||
change.setAliases
|
||||
? {
|
||||
...c,
|
||||
metricAlias: defaultTo(c.metricAlias, change.setAliases.metricAlias),
|
||||
logAlias: defaultTo(c.logAlias, change.setAliases.logAlias),
|
||||
}
|
||||
: c,
|
||||
c =>
|
||||
change.setFields
|
||||
? {
|
||||
...c,
|
||||
fields: {
|
||||
container: defaultTo(c.fields.container, change.setFields.container),
|
||||
host: defaultTo(c.fields.host, change.setFields.host),
|
||||
message: c.fields.message,
|
||||
pod: defaultTo(c.fields.pod, change.setFields.pod),
|
||||
tiebreaker: defaultTo(c.fields.tiebreaker, change.setFields.tiebreaker),
|
||||
timestamp: defaultTo(c.fields.timestamp, change.setFields.timestamp),
|
||||
},
|
||||
}
|
||||
: c,
|
||||
];
|
||||
return updaters.reduce(
|
||||
(updatedConfiguration, updater) => updater(updatedConfiguration),
|
||||
configuration
|
||||
);
|
||||
};
|
||||
|
||||
const defaultTo = <T>(defaultValue: T, maybeValue: T | undefined | null): T =>
|
||||
typeof maybeValue === 'undefined' || maybeValue === null ? defaultValue : maybeValue;
|
|
@ -5,27 +5,19 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useKibanaUiSetting } from '../utils/use_kibana_ui_setting';
|
||||
|
||||
interface FormattedTimeProps {
|
||||
time: number; // Unix time (milliseconds)
|
||||
fallbackFormat?: string;
|
||||
}
|
||||
|
||||
const getFormattedTime = (
|
||||
time: FormattedTimeProps['time'],
|
||||
time: number,
|
||||
userFormat: string | undefined,
|
||||
fallbackFormat: string = 'Y-MM-DD HH:mm:ss.SSS'
|
||||
) => {
|
||||
return userFormat ? moment(time).format(userFormat) : moment(time).format(fallbackFormat);
|
||||
};
|
||||
|
||||
export const FormattedTime: React.FunctionComponent<FormattedTimeProps> = ({
|
||||
time,
|
||||
fallbackFormat,
|
||||
}) => {
|
||||
export const useFormattedTime = (time: number, fallbackFormat?: string) => {
|
||||
const [dateFormat] = useKibanaUiSetting('dateFormat');
|
||||
const formattedTime = useMemo(() => getFormattedTime(time, dateFormat, fallbackFormat), [
|
||||
getFormattedTime,
|
||||
|
@ -34,5 +26,5 @@ export const FormattedTime: React.FunctionComponent<FormattedTimeProps> = ({
|
|||
fallbackFormat,
|
||||
]);
|
||||
|
||||
return <span>{formattedTime}</span>;
|
||||
return formattedTime;
|
||||
};
|
||||
|
|
|
@ -6,29 +6,28 @@
|
|||
|
||||
import { bisector } from 'd3-array';
|
||||
|
||||
import { getLogEntryKey, LogEntry } from '../../../../common/log_entry';
|
||||
import { SearchResult } from '../../../../common/log_search_result';
|
||||
import { compareToTimeKey, TimeKey } from '../../../../common/time';
|
||||
import { LogEntry } from '../../../utils/log_entry';
|
||||
|
||||
export type StreamItem = LogEntryStreamItem;
|
||||
|
||||
export interface LogEntryStreamItem {
|
||||
kind: 'logEntry';
|
||||
logEntry: LogEntry;
|
||||
searchResult: SearchResult | undefined;
|
||||
}
|
||||
|
||||
export function getStreamItemTimeKey(item: StreamItem) {
|
||||
switch (item.kind) {
|
||||
case 'logEntry':
|
||||
return getLogEntryKey(item.logEntry);
|
||||
return item.logEntry.key;
|
||||
}
|
||||
}
|
||||
|
||||
export function getStreamItemId(item: StreamItem) {
|
||||
const { time, tiebreaker, gid } = getStreamItemTimeKey(item);
|
||||
|
||||
return `${time}:${tiebreaker}:${gid}`;
|
||||
switch (item.kind) {
|
||||
case 'logEntry':
|
||||
return `${item.logEntry.key.time}:${item.logEntry.key.tiebreaker}:${item.logEntry.gid}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseStreamItemId(id: string) {
|
||||
|
|
|
@ -5,44 +5,48 @@
|
|||
*/
|
||||
|
||||
import { darken, transparentize } from 'polished';
|
||||
import * as React from 'react';
|
||||
import { css } from 'styled-components';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { css } from '../../../../../../common/eui_styled_components';
|
||||
import { TextScale } from '../../../../common/log_text_scale';
|
||||
import { tintOrShade } from '../../../utils/styles';
|
||||
import { useFormattedTime } from '../../formatted_time';
|
||||
import { LogTextStreamItemField } from './item_field';
|
||||
|
||||
interface LogTextStreamItemDateFieldProps {
|
||||
children: React.ReactNode;
|
||||
dataTestSubj?: string;
|
||||
hasHighlights: boolean;
|
||||
isHighlighted: boolean;
|
||||
isHovered: boolean;
|
||||
scale: TextScale;
|
||||
isHighlighted: boolean;
|
||||
time: number;
|
||||
}
|
||||
|
||||
export class LogTextStreamItemDateField extends React.PureComponent<
|
||||
LogTextStreamItemDateFieldProps,
|
||||
{}
|
||||
> {
|
||||
public render() {
|
||||
const { children, hasHighlights, isHovered, isHighlighted, scale } = this.props;
|
||||
export const LogTextStreamItemDateField = memo<LogTextStreamItemDateFieldProps>(
|
||||
({ dataTestSubj, hasHighlights, isHighlighted, isHovered, scale, time }) => {
|
||||
const formattedTime = useFormattedTime(time);
|
||||
|
||||
return (
|
||||
<LogTextStreamItemDateFieldWrapper
|
||||
data-test-subj={dataTestSubj}
|
||||
hasHighlights={hasHighlights}
|
||||
isHovered={isHovered}
|
||||
isHighlighted={isHighlighted}
|
||||
scale={scale}
|
||||
>
|
||||
{children}
|
||||
{formattedTime}
|
||||
</LogTextStreamItemDateFieldWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const highlightedFieldStyle = css`
|
||||
background-color: ${props =>
|
||||
tintOrShade(props.theme.eui.euiTextColor, props.theme.eui.euiColorSecondary, 0.15)};
|
||||
tintOrShade(
|
||||
props.theme.eui.euiTextColor as any,
|
||||
props.theme.eui.euiColorSecondary as any,
|
||||
0.15
|
||||
)};
|
||||
border-color: ${props => props.theme.eui.euiColorSecondary};
|
||||
`;
|
||||
|
||||
|
@ -67,7 +71,6 @@ const LogTextStreamItemDateFieldWrapper = LogTextStreamItemField.extend.attrs<{
|
|||
border-right: solid 2px ${props => props.theme.eui.euiColorLightShade};
|
||||
color: ${props => props.theme.eui.euiColorDarkShade};
|
||||
white-space: pre;
|
||||
padding: 0 ${props => props.theme.eui.paddingSizes.l};
|
||||
|
||||
${props => (props.hasHighlights ? highlightedFieldStyle : '')};
|
||||
${props => (props.isHovered || props.isHighlighted ? hoveredFieldStyle : '')};
|
||||
|
|
|
@ -18,5 +18,5 @@ export const LogTextStreamItemField = euiStyled.div.attrs<{
|
|||
[switchProp.default]: props.theme.eui.euiFontSize,
|
||||
})};
|
||||
line-height: ${props => props.theme.eui.euiLineHeight};
|
||||
padding: 2px ${props => props.theme.eui.euiSize} 2px 0;
|
||||
padding: 2px ${props => props.theme.eui.paddingSizes.m};
|
||||
`;
|
||||
|
|
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { darken, transparentize } from 'polished';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { css } from '../../../../../../common/eui_styled_components';
|
||||
import { TextScale } from '../../../../common/log_text_scale';
|
||||
import { LogTextStreamItemField } from './item_field';
|
||||
|
||||
interface LogTextStreamItemFieldFieldProps {
|
||||
dataTestSubj?: string;
|
||||
encodedValue: string;
|
||||
isHighlighted: boolean;
|
||||
isHovered: boolean;
|
||||
scale: TextScale;
|
||||
}
|
||||
|
||||
export const LogTextStreamItemFieldField: React.FunctionComponent<
|
||||
LogTextStreamItemFieldFieldProps
|
||||
> = ({ dataTestSubj, encodedValue, isHighlighted, isHovered, scale }) => {
|
||||
const value = useMemo(() => JSON.parse(encodedValue), [encodedValue]);
|
||||
|
||||
return (
|
||||
<LogTextStreamItemFieldFieldWrapper
|
||||
data-test-subj={dataTestSubj}
|
||||
isHighlighted={isHighlighted}
|
||||
isHovered={isHovered}
|
||||
scale={scale}
|
||||
>
|
||||
{value}
|
||||
</LogTextStreamItemFieldFieldWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const hoveredFieldStyle = css`
|
||||
background-color: ${props =>
|
||||
props.theme.darkMode
|
||||
? transparentize(0.9, darken(0.05, props.theme.eui.euiColorHighlight))
|
||||
: darken(0.05, props.theme.eui.euiColorHighlight)};
|
||||
`;
|
||||
|
||||
const LogTextStreamItemFieldFieldWrapper = LogTextStreamItemField.extend.attrs<{
|
||||
isHighlighted: boolean;
|
||||
isHovered: boolean;
|
||||
}>({})`
|
||||
flex: 1 0 0%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
background-color: ${props => props.theme.eui.euiColorEmptyShade};
|
||||
|
||||
${props => (props.isHovered || props.isHighlighted ? hoveredFieldStyle : '')};
|
||||
`;
|
|
@ -5,70 +5,72 @@
|
|||
*/
|
||||
|
||||
import { darken, transparentize } from 'polished';
|
||||
import * as React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import euiStyled, { css } from '../../../../../../common/eui_styled_components';
|
||||
// import euiStyled, { css } from '../../../../../../common/eui_styled_components';
|
||||
import { css } from '../../../../../../common/eui_styled_components';
|
||||
import { TextScale } from '../../../../common/log_text_scale';
|
||||
import { tintOrShade } from '../../../utils/styles';
|
||||
import { LogTextStreamItemField } from './item_field';
|
||||
import {
|
||||
isConstantSegment,
|
||||
isFieldSegment,
|
||||
LogEntryMessageSegment,
|
||||
} from '../../../utils/log_entry';
|
||||
|
||||
interface LogTextStreamItemMessageFieldProps {
|
||||
children: string;
|
||||
highlights: string[];
|
||||
dataTestSubj?: string;
|
||||
segments: LogEntryMessageSegment[];
|
||||
isHovered: boolean;
|
||||
isWrapped: boolean;
|
||||
scale: TextScale;
|
||||
isHighlighted: boolean;
|
||||
}
|
||||
|
||||
export class LogTextStreamItemMessageField extends React.PureComponent<
|
||||
LogTextStreamItemMessageFieldProps,
|
||||
{}
|
||||
> {
|
||||
public render() {
|
||||
const { children, highlights, isHovered, isHighlighted, isWrapped, scale } = this.props;
|
||||
export const LogTextStreamItemMessageField: React.FunctionComponent<
|
||||
LogTextStreamItemMessageFieldProps
|
||||
> = ({ dataTestSubj, isHighlighted, isHovered, isWrapped, scale, segments }) => {
|
||||
const message = useMemo(() => segments.map(formatMessageSegment).join(''), [segments]);
|
||||
|
||||
const hasHighlights = highlights.length > 0;
|
||||
const content = hasHighlights ? renderHighlightFragments(children, highlights) : children;
|
||||
return (
|
||||
<LogTextStreamItemMessageFieldWrapper
|
||||
hasHighlights={hasHighlights}
|
||||
isHovered={isHovered}
|
||||
isHighlighted={isHighlighted}
|
||||
isWrapped={isWrapped}
|
||||
scale={scale}
|
||||
>
|
||||
{content}
|
||||
</LogTextStreamItemMessageFieldWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const renderHighlightFragments = (text: string, highlights: string[]): React.ReactNode[] => {
|
||||
const renderedHighlights = highlights.reduce(
|
||||
({ lastFragmentEnd, renderedFragments }, highlight) => {
|
||||
const fragmentStart = text.indexOf(highlight, lastFragmentEnd);
|
||||
return {
|
||||
lastFragmentEnd: fragmentStart + highlight.length,
|
||||
renderedFragments: [
|
||||
...renderedFragments,
|
||||
text.slice(lastFragmentEnd, fragmentStart),
|
||||
<HighlightSpan key={fragmentStart}>{highlight}</HighlightSpan>,
|
||||
],
|
||||
};
|
||||
},
|
||||
{
|
||||
lastFragmentEnd: 0,
|
||||
renderedFragments: [],
|
||||
} as {
|
||||
lastFragmentEnd: number;
|
||||
renderedFragments: React.ReactNode[];
|
||||
}
|
||||
return (
|
||||
<LogTextStreamItemMessageFieldWrapper
|
||||
data-test-subj={dataTestSubj}
|
||||
hasHighlights={false}
|
||||
isHighlighted={isHighlighted}
|
||||
isHovered={isHovered}
|
||||
isWrapped={isWrapped}
|
||||
scale={scale}
|
||||
>
|
||||
{message}
|
||||
</LogTextStreamItemMessageFieldWrapper>
|
||||
);
|
||||
|
||||
return [...renderedHighlights.renderedFragments, text.slice(renderedHighlights.lastFragmentEnd)];
|
||||
};
|
||||
|
||||
// const renderHighlightFragments = (text: string, highlights: string[]): React.ReactNode[] => {
|
||||
// const renderedHighlights = highlights.reduce(
|
||||
// ({ lastFragmentEnd, renderedFragments }, highlight) => {
|
||||
// const fragmentStart = text.indexOf(highlight, lastFragmentEnd);
|
||||
// return {
|
||||
// lastFragmentEnd: fragmentStart + highlight.length,
|
||||
// renderedFragments: [
|
||||
// ...renderedFragments,
|
||||
// text.slice(lastFragmentEnd, fragmentStart),
|
||||
// <HighlightSpan key={fragmentStart}>{highlight}</HighlightSpan>,
|
||||
// ],
|
||||
// };
|
||||
// },
|
||||
// {
|
||||
// lastFragmentEnd: 0,
|
||||
// renderedFragments: [],
|
||||
// } as {
|
||||
// lastFragmentEnd: number;
|
||||
// renderedFragments: React.ReactNode[];
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// return [...renderedHighlights.renderedFragments, text.slice(renderedHighlights.lastFragmentEnd)];
|
||||
// };
|
||||
|
||||
const highlightedFieldStyle = css`
|
||||
background-color: ${props =>
|
||||
tintOrShade(
|
||||
|
@ -102,18 +104,29 @@ const LogTextStreamItemMessageFieldWrapper = LogTextStreamItemField.extend.attrs
|
|||
isHighlighted: boolean;
|
||||
isWrapped?: boolean;
|
||||
}>({})`
|
||||
flex-grow: 1;
|
||||
flex: 5 0 0%
|
||||
text-overflow: ellipsis;
|
||||
background-color: ${props => props.theme.eui.euiColorEmptyShade};
|
||||
padding-left: 0;
|
||||
|
||||
${props => (props.hasHighlights ? highlightedFieldStyle : '')};
|
||||
${props => (props.isHovered || props.isHighlighted ? hoveredFieldStyle : '')};
|
||||
${props => (props.isWrapped ? wrappedFieldStyle : unwrappedFieldStyle)};
|
||||
`;
|
||||
|
||||
const HighlightSpan = euiStyled.span`
|
||||
display: inline-block;
|
||||
background-color: ${props => props.theme.eui.euiColorSecondary};
|
||||
color: ${props => props.theme.eui.euiColorGhost};
|
||||
font-weight: ${props => props.theme.eui.euiFontWeightMedium};
|
||||
`;
|
||||
// const HighlightSpan = euiStyled.span`
|
||||
// display: inline-block;
|
||||
// background-color: ${props => props.theme.eui.euiColorSecondary};
|
||||
// color: ${props => props.theme.eui.euiColorGhost};
|
||||
// font-weight: ${props => props.theme.eui.euiFontWeightMedium};
|
||||
// `;
|
||||
|
||||
const formatMessageSegment = (messageSegment: LogEntryMessageSegment): string => {
|
||||
if (isFieldSegment(messageSegment)) {
|
||||
return messageSegment.value;
|
||||
} else if (isConstantSegment(messageSegment)) {
|
||||
return messageSegment.constant;
|
||||
}
|
||||
|
||||
return 'failed to format message';
|
||||
};
|
||||
|
|
|
@ -26,7 +26,6 @@ export const LogTextStreamItemView = React.forwardRef<Element, StreamItemProps>(
|
|||
<LogTextStreamLogEntryItemView
|
||||
boundingBoxRef={ref}
|
||||
logEntry={item.logEntry}
|
||||
searchResult={item.searchResult}
|
||||
scale={scale}
|
||||
wrap={wrap}
|
||||
openFlyoutWithItem={openFlyoutWithItem}
|
||||
|
|
|
@ -5,116 +5,125 @@
|
|||
*/
|
||||
|
||||
import { darken, transparentize } from 'polished';
|
||||
import * as React from 'react';
|
||||
import React, { useState, useCallback, Fragment } from 'react';
|
||||
|
||||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
import { InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import { injectI18n, InjectedIntl } from '@kbn/i18n/react';
|
||||
import euiStyled from '../../../../../../common/eui_styled_components';
|
||||
import { LogEntry } from '../../../../common/log_entry';
|
||||
import { SearchResult } from '../../../../common/log_search_result';
|
||||
import {
|
||||
LogEntry,
|
||||
isFieldColumn,
|
||||
isMessageColumn,
|
||||
isTimestampColumn,
|
||||
} from '../../../utils/log_entry';
|
||||
import { TextScale } from '../../../../common/log_text_scale';
|
||||
import { FormattedTime } from '../../formatted_time';
|
||||
import { LogTextStreamItemDateField } from './item_date_field';
|
||||
import { LogTextStreamItemFieldField } from './item_field_field';
|
||||
import { LogTextStreamItemMessageField } from './item_message_field';
|
||||
|
||||
interface LogTextStreamLogEntryItemViewProps {
|
||||
boundingBoxRef?: React.Ref<Element>;
|
||||
isHighlighted: boolean;
|
||||
intl: InjectedIntl;
|
||||
logEntry: LogEntry;
|
||||
searchResult?: SearchResult;
|
||||
openFlyoutWithItem: (id: string) => void;
|
||||
scale: TextScale;
|
||||
wrap: boolean;
|
||||
openFlyoutWithItem: (id: string) => void;
|
||||
intl: InjectedIntl;
|
||||
isHighlighted: boolean;
|
||||
}
|
||||
|
||||
interface LogTextStreamLogEntryItemViewState {
|
||||
isHovered: boolean;
|
||||
}
|
||||
|
||||
export const LogTextStreamLogEntryItemView = injectI18n(
|
||||
class extends React.PureComponent<
|
||||
LogTextStreamLogEntryItemViewProps,
|
||||
LogTextStreamLogEntryItemViewState
|
||||
> {
|
||||
public readonly state = {
|
||||
isHovered: false,
|
||||
};
|
||||
({
|
||||
boundingBoxRef,
|
||||
isHighlighted,
|
||||
intl,
|
||||
logEntry,
|
||||
openFlyoutWithItem,
|
||||
scale,
|
||||
wrap,
|
||||
}: LogTextStreamLogEntryItemViewProps) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
public handleMouseEnter: React.MouseEventHandler<HTMLDivElement> = () => {
|
||||
this.setState({
|
||||
isHovered: true,
|
||||
});
|
||||
};
|
||||
const setItemIsHovered = useCallback(() => {
|
||||
setIsHovered(true);
|
||||
}, []);
|
||||
|
||||
public handleMouseLeave: React.MouseEventHandler<HTMLDivElement> = () => {
|
||||
this.setState({
|
||||
isHovered: false,
|
||||
});
|
||||
};
|
||||
const setItemIsNotHovered = useCallback(() => {
|
||||
setIsHovered(false);
|
||||
}, []);
|
||||
|
||||
public handleClick: React.MouseEventHandler<HTMLButtonElement> = () => {
|
||||
this.props.openFlyoutWithItem(this.props.logEntry.gid);
|
||||
};
|
||||
const openFlyout = useCallback(() => openFlyoutWithItem(logEntry.gid), [
|
||||
openFlyoutWithItem,
|
||||
logEntry.gid,
|
||||
]);
|
||||
|
||||
public render() {
|
||||
const {
|
||||
intl,
|
||||
boundingBoxRef,
|
||||
logEntry,
|
||||
scale,
|
||||
searchResult,
|
||||
wrap,
|
||||
isHighlighted,
|
||||
} = this.props;
|
||||
const { isHovered } = this.state;
|
||||
const viewDetailsLabel = intl.formatMessage({
|
||||
id: 'xpack.infra.logEntryItemView.viewDetailsToolTip',
|
||||
defaultMessage: 'View Details',
|
||||
});
|
||||
|
||||
return (
|
||||
<LogTextStreamLogEntryItemDiv
|
||||
innerRef={
|
||||
/* Workaround for missing RefObject support in styled-components */
|
||||
boundingBoxRef as any
|
||||
}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
<LogTextStreamItemDateField
|
||||
hasHighlights={!!searchResult}
|
||||
isHovered={isHovered}
|
||||
isHighlighted={isHighlighted}
|
||||
scale={scale}
|
||||
>
|
||||
<FormattedTime time={logEntry.fields.time} />
|
||||
</LogTextStreamItemDateField>
|
||||
<LogTextStreamIconDiv isHovered={isHovered} isHighlighted={isHighlighted}>
|
||||
{isHovered ? (
|
||||
<EuiToolTip content={viewDetailsLabel}>
|
||||
<EuiButtonIcon
|
||||
onClick={this.handleClick}
|
||||
iconType="expand"
|
||||
aria-label={viewDetailsLabel}
|
||||
return (
|
||||
<LogTextStreamLogEntryItemDiv
|
||||
data-test-subj="streamEntry logTextStreamEntry"
|
||||
innerRef={
|
||||
/* Workaround for missing RefObject support in styled-components */
|
||||
boundingBoxRef as any
|
||||
}
|
||||
onMouseEnter={setItemIsHovered}
|
||||
onMouseLeave={setItemIsNotHovered}
|
||||
>
|
||||
{logEntry.columns.map((column, columnIndex) => {
|
||||
if (isTimestampColumn(column)) {
|
||||
return (
|
||||
<LogTextStreamItemDateField
|
||||
dataTestSubj="logColumn timestampLogColumn"
|
||||
hasHighlights={false}
|
||||
isHighlighted={isHighlighted}
|
||||
isHovered={isHovered}
|
||||
key={`${columnIndex}`}
|
||||
scale={scale}
|
||||
time={column.timestamp}
|
||||
/>
|
||||
);
|
||||
} else if (isMessageColumn(column)) {
|
||||
const viewDetailsLabel = intl.formatMessage({
|
||||
id: 'xpack.infra.logEntryItemView.viewDetailsToolTip',
|
||||
defaultMessage: 'View Details',
|
||||
});
|
||||
return (
|
||||
<Fragment key={`${columnIndex}`}>
|
||||
<LogTextStreamIconDiv isHighlighted={isHighlighted} isHovered={isHovered}>
|
||||
{isHovered ? (
|
||||
<EuiToolTip content={viewDetailsLabel}>
|
||||
<EuiButtonIcon
|
||||
onClick={openFlyout}
|
||||
iconType="expand"
|
||||
aria-label={viewDetailsLabel}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<EmptyIcon />
|
||||
)}
|
||||
</LogTextStreamIconDiv>
|
||||
<LogTextStreamItemMessageField
|
||||
dataTestSubj="logColumn messageLogColumn"
|
||||
isHighlighted={isHighlighted}
|
||||
isHovered={isHovered}
|
||||
isWrapped={wrap}
|
||||
scale={scale}
|
||||
segments={column.message}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
) : (
|
||||
<EmptyIcon />
|
||||
)}
|
||||
</LogTextStreamIconDiv>
|
||||
<LogTextStreamItemMessageField
|
||||
highlights={searchResult ? searchResult.matches.message || [] : []}
|
||||
isHovered={isHovered}
|
||||
isHighlighted={isHighlighted}
|
||||
isWrapped={wrap}
|
||||
scale={scale}
|
||||
>
|
||||
{logEntry.fields.message}
|
||||
</LogTextStreamItemMessageField>
|
||||
</LogTextStreamLogEntryItemDiv>
|
||||
);
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
} else if (isFieldColumn(column)) {
|
||||
return (
|
||||
<LogTextStreamItemFieldField
|
||||
dataTestSubj={`logColumn fieldLogColumn fieldLogColumn:${column.field}`}
|
||||
encodedValue={column.value}
|
||||
isHighlighted={isHighlighted}
|
||||
isHovered={isHovered}
|
||||
key={`${columnIndex}`}
|
||||
scale={scale}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</LogTextStreamLogEntryItemDiv>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
*/
|
||||
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import * as React from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import euiStyled from '../../../../../../common/eui_styled_components';
|
||||
import { TextScale } from '../../../../common/log_text_scale';
|
||||
import { TimeKey } from '../../../../common/time';
|
||||
import { callWithoutRepeats } from '../../../utils/handlers';
|
||||
import { AutoSizer } from '../../auto_sizer';
|
||||
import { NoData } from '../../empty_states';
|
||||
import { InfraLoadingPanel } from '../../loading';
|
||||
import { getStreamItemBeforeTimeKey, getStreamItemId, parseStreamItemId, StreamItem } from './item';
|
||||
|
@ -19,8 +21,6 @@ import { MeasurableItemView } from './measurable_item_view';
|
|||
import { VerticalScrollPanel } from './vertical_scroll_panel';
|
||||
|
||||
interface ScrollableLogTextStreamViewProps {
|
||||
height: number;
|
||||
width: number;
|
||||
items: StreamItem[];
|
||||
scale: TextScale;
|
||||
wrap: boolean;
|
||||
|
@ -92,8 +92,6 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent<
|
|||
public render() {
|
||||
const {
|
||||
items,
|
||||
height,
|
||||
width,
|
||||
scale,
|
||||
wrap,
|
||||
isReloading,
|
||||
|
@ -107,90 +105,95 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent<
|
|||
} = this.props;
|
||||
const { targetId } = this.state;
|
||||
const hasItems = items.length > 0;
|
||||
if (isReloading && !hasItems) {
|
||||
return (
|
||||
<InfraLoadingPanel
|
||||
height={height}
|
||||
width={width}
|
||||
text={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.scrollableLogTextStreamView.loadingEntriesLabel"
|
||||
defaultMessage="Loading entries"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else if (!hasItems) {
|
||||
return (
|
||||
<NoData
|
||||
titleText={intl.formatMessage({
|
||||
id: 'xpack.infra.logs.emptyView.noLogMessageTitle',
|
||||
defaultMessage: 'There are no log messages to display.',
|
||||
})}
|
||||
bodyText={intl.formatMessage({
|
||||
id: 'xpack.infra.logs.emptyView.noLogMessageDescription',
|
||||
defaultMessage: 'Try adjusting your filter.',
|
||||
})}
|
||||
refetchText={intl.formatMessage({
|
||||
id: 'xpack.infra.logs.emptyView.checkForNewDataButtonLabel',
|
||||
defaultMessage: 'Check for new data',
|
||||
})}
|
||||
onRefetch={this.handleReload}
|
||||
testString="logsNoDataPrompt"
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<VerticalScrollPanel
|
||||
height={height}
|
||||
width={width}
|
||||
onVisibleChildrenChange={this.handleVisibleChildrenChange}
|
||||
target={targetId}
|
||||
hideScrollbar={true}
|
||||
data-test-subj={'logStream'}
|
||||
>
|
||||
{registerChild => (
|
||||
<>
|
||||
<LogTextStreamLoadingItemView
|
||||
alignment="bottom"
|
||||
isLoading={isLoadingMore}
|
||||
hasMore={hasMoreBeforeStart}
|
||||
isStreaming={false}
|
||||
lastStreamingUpdate={null}
|
||||
|
||||
return (
|
||||
<ScrollableLogTextStreamViewWrapper>
|
||||
{isReloading && !hasItems ? (
|
||||
<InfraLoadingPanel
|
||||
width="100%"
|
||||
height="100%"
|
||||
text={
|
||||
<FormattedMessage
|
||||
id="xpack.infra.logs.scrollableLogTextStreamView.loadingEntriesLabel"
|
||||
defaultMessage="Loading entries"
|
||||
/>
|
||||
{items.map(item => (
|
||||
<MeasurableItemView
|
||||
register={registerChild}
|
||||
registrationKey={getStreamItemId(item)}
|
||||
key={getStreamItemId(item)}
|
||||
}
|
||||
/>
|
||||
) : !hasItems ? (
|
||||
<NoData
|
||||
titleText={intl.formatMessage({
|
||||
id: 'xpack.infra.logs.emptyView.noLogMessageTitle',
|
||||
defaultMessage: 'There are no log messages to display.',
|
||||
})}
|
||||
bodyText={intl.formatMessage({
|
||||
id: 'xpack.infra.logs.emptyView.noLogMessageDescription',
|
||||
defaultMessage: 'Try adjusting your filter.',
|
||||
})}
|
||||
refetchText={intl.formatMessage({
|
||||
id: 'xpack.infra.logs.emptyView.checkForNewDataButtonLabel',
|
||||
defaultMessage: 'Check for new data',
|
||||
})}
|
||||
onRefetch={this.handleReload}
|
||||
testString="logsNoDataPrompt"
|
||||
/>
|
||||
) : (
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => (
|
||||
<ScrollPanelSizeProbe innerRef={measureRef}>
|
||||
<VerticalScrollPanel
|
||||
height={height}
|
||||
width={width}
|
||||
onVisibleChildrenChange={this.handleVisibleChildrenChange}
|
||||
target={targetId}
|
||||
hideScrollbar={true}
|
||||
data-test-subj={'logStream'}
|
||||
>
|
||||
{measureRef => (
|
||||
<LogTextStreamItemView
|
||||
openFlyoutWithItem={this.handleOpenFlyout}
|
||||
ref={measureRef}
|
||||
item={item}
|
||||
scale={scale}
|
||||
wrap={wrap}
|
||||
isHighlighted={
|
||||
highlightedItem ? item.logEntry.gid === highlightedItem : false
|
||||
}
|
||||
/>
|
||||
{registerChild => (
|
||||
<>
|
||||
<LogTextStreamLoadingItemView
|
||||
alignment="bottom"
|
||||
isLoading={isLoadingMore}
|
||||
hasMore={hasMoreBeforeStart}
|
||||
isStreaming={false}
|
||||
lastStreamingUpdate={null}
|
||||
/>
|
||||
{items.map(item => (
|
||||
<MeasurableItemView
|
||||
register={registerChild}
|
||||
registrationKey={getStreamItemId(item)}
|
||||
key={getStreamItemId(item)}
|
||||
>
|
||||
{itemMeasureRef => (
|
||||
<LogTextStreamItemView
|
||||
openFlyoutWithItem={this.handleOpenFlyout}
|
||||
ref={itemMeasureRef}
|
||||
item={item}
|
||||
scale={scale}
|
||||
wrap={wrap}
|
||||
isHighlighted={
|
||||
highlightedItem ? item.logEntry.gid === highlightedItem : false
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</MeasurableItemView>
|
||||
))}
|
||||
<LogTextStreamLoadingItemView
|
||||
alignment="top"
|
||||
isLoading={isStreaming || isLoadingMore}
|
||||
hasMore={hasMoreAfterEnd}
|
||||
isStreaming={isStreaming}
|
||||
lastStreamingUpdate={isStreaming ? lastLoadedTime : null}
|
||||
onLoadMore={this.handleLoadNewerItems}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</MeasurableItemView>
|
||||
))}
|
||||
<LogTextStreamLoadingItemView
|
||||
alignment="top"
|
||||
isLoading={isStreaming || isLoadingMore}
|
||||
hasMore={hasMoreAfterEnd}
|
||||
isStreaming={isStreaming}
|
||||
lastStreamingUpdate={isStreaming ? lastLoadedTime : null}
|
||||
onLoadMore={this.handleLoadNewerItems}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</VerticalScrollPanel>
|
||||
);
|
||||
}
|
||||
</VerticalScrollPanel>
|
||||
</ScrollPanelSizeProbe>
|
||||
)}
|
||||
</AutoSizer>
|
||||
)}
|
||||
</ScrollableLogTextStreamViewWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
private handleOpenFlyout = (id: string) => {
|
||||
|
@ -242,3 +245,16 @@ class ScrollableLogTextStreamViewClass extends React.PureComponent<
|
|||
}
|
||||
|
||||
export const ScrollableLogTextStreamView = injectI18n(ScrollableLogTextStreamViewClass);
|
||||
|
||||
const ScrollableLogTextStreamViewWrapper = euiStyled.div`
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1 1 0%;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
`;
|
||||
|
||||
const ScrollPanelSizeProbe = euiStyled.div`
|
||||
overflow: hidden;
|
||||
flex: 1 1 0%;
|
||||
`;
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiBadge, EuiButton, EuiPopover, EuiPopoverTitle, EuiSelectable } from '@elastic/eui';
|
||||
import { Option } from '@elastic/eui/src/components/selectable/types';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { LogColumnConfiguration } from '../../utils/source_configuration';
|
||||
import { euiStyled } from '../../../../../common/eui_styled_components';
|
||||
|
||||
interface SelectableColumnOption {
|
||||
optionProps: Option;
|
||||
columnConfiguration: LogColumnConfiguration;
|
||||
}
|
||||
|
||||
export const AddLogColumnButtonAndPopover: React.FunctionComponent<{
|
||||
addLogColumn: (logColumnConfiguration: LogColumnConfiguration) => void;
|
||||
availableFields: string[];
|
||||
isDisabled?: boolean;
|
||||
}> = ({ addLogColumn, availableFields, isDisabled }) => {
|
||||
const [isOpen, openPopover, closePopover] = usePopoverVisibilityState(false);
|
||||
|
||||
const availableColumnOptions = useMemo<SelectableColumnOption[]>(
|
||||
() => [
|
||||
{
|
||||
optionProps: {
|
||||
append: <BuiltinBadge />,
|
||||
'data-test-subj': 'addTimestampLogColumn',
|
||||
// this key works around EuiSelectable using a lowercased label as
|
||||
// key, which leads to conflicts with field names
|
||||
key: 'timestamp',
|
||||
label: 'Timestamp',
|
||||
},
|
||||
columnConfiguration: {
|
||||
timestampColumn: {
|
||||
id: uuidv4(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
optionProps: {
|
||||
'data-test-subj': 'addMessageLogColumn',
|
||||
append: <BuiltinBadge />,
|
||||
// this key works around EuiSelectable using a lowercased label as
|
||||
// key, which leads to conflicts with field names
|
||||
key: 'message',
|
||||
label: 'Message',
|
||||
},
|
||||
columnConfiguration: {
|
||||
messageColumn: {
|
||||
id: uuidv4(),
|
||||
},
|
||||
},
|
||||
},
|
||||
...availableFields.map<SelectableColumnOption>(field => ({
|
||||
optionProps: {
|
||||
'data-test-subj': `addFieldLogColumn addFieldLogColumn:${field}`,
|
||||
// this key works around EuiSelectable using a lowercased label as
|
||||
// key, which leads to conflicts with fields that only differ in the
|
||||
// case (e.g. the metricbeat mongodb module)
|
||||
key: `field-${field}`,
|
||||
label: field,
|
||||
},
|
||||
columnConfiguration: {
|
||||
fieldColumn: {
|
||||
id: uuidv4(),
|
||||
field,
|
||||
},
|
||||
},
|
||||
})),
|
||||
],
|
||||
[availableFields]
|
||||
);
|
||||
|
||||
const availableOptions = useMemo<Option[]>(
|
||||
() => availableColumnOptions.map(availableColumnOption => availableColumnOption.optionProps),
|
||||
[availableColumnOptions]
|
||||
);
|
||||
|
||||
const handleColumnSelection = useCallback(
|
||||
(selectedOptions: Option[]) => {
|
||||
closePopover();
|
||||
|
||||
const selectedOptionIndex = selectedOptions.findIndex(
|
||||
selectedOption => selectedOption.checked === 'on'
|
||||
);
|
||||
const selectedOption = availableColumnOptions[selectedOptionIndex];
|
||||
|
||||
addLogColumn(selectedOption.columnConfiguration);
|
||||
},
|
||||
[addLogColumn, availableColumnOptions]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
anchorPosition="downRight"
|
||||
button={
|
||||
<EuiButton
|
||||
data-test-subj="addLogColumnButton"
|
||||
isDisabled={isDisabled}
|
||||
iconType="plusInCircle"
|
||||
onClick={openPopover}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.addLogColumnButtonLabel"
|
||||
defaultMessage="Add Column"
|
||||
/>
|
||||
</EuiButton>
|
||||
}
|
||||
closePopover={closePopover}
|
||||
id="addLogColumn"
|
||||
isOpen={isOpen}
|
||||
ownFocus
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiSelectable
|
||||
height={600}
|
||||
listProps={selectableListProps}
|
||||
onChange={handleColumnSelection}
|
||||
options={availableOptions}
|
||||
searchable
|
||||
searchProps={searchProps}
|
||||
singleSelection
|
||||
>
|
||||
{(list, search) => (
|
||||
<SelectableContent data-test-subj="addLogColumnPopover">
|
||||
<EuiPopoverTitle>{search}</EuiPopoverTitle>
|
||||
{list}
|
||||
</SelectableContent>
|
||||
)}
|
||||
</EuiSelectable>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
||||
const searchProps = {
|
||||
'data-test-subj': 'fieldSearchInput',
|
||||
};
|
||||
|
||||
const selectableListProps = {
|
||||
showIcons: false,
|
||||
};
|
||||
|
||||
const usePopoverVisibilityState = (initialState: boolean) => {
|
||||
const [isOpen, setIsOpen] = useState(initialState);
|
||||
|
||||
const closePopover = useCallback(() => setIsOpen(false), []);
|
||||
const openPopover = useCallback(() => setIsOpen(true), []);
|
||||
|
||||
return useMemo<[typeof isOpen, typeof openPopover, typeof closePopover]>(
|
||||
() => [isOpen, openPopover, closePopover],
|
||||
[isOpen, openPopover, closePopover]
|
||||
);
|
||||
};
|
||||
|
||||
const BuiltinBadge: React.FunctionComponent = () => (
|
||||
<EuiBadge>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.builtInColumnBadgeLabel"
|
||||
defaultMessage="Built-in"
|
||||
/>
|
||||
</EuiBadge>
|
||||
);
|
||||
|
||||
const SelectableContent = euiStyled.div`
|
||||
width: 400px;
|
||||
`;
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import { EuiCode, EuiFieldText, EuiForm, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { InputFieldProps } from './source_configuration_form_state';
|
||||
import { InputFieldProps } from './input_fields';
|
||||
|
||||
interface FieldsConfigurationPanelProps {
|
||||
containerFieldProps: InputFieldProps;
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { createInputFieldProps, validateInputFieldNotEmpty } from './input_fields';
|
||||
|
||||
interface FormState {
|
||||
name: string;
|
||||
description: string;
|
||||
metricAlias: string;
|
||||
logAlias: string;
|
||||
containerField: string;
|
||||
hostField: string;
|
||||
messageField: string[];
|
||||
podField: string;
|
||||
tiebreakerField: string;
|
||||
timestampField: string;
|
||||
}
|
||||
|
||||
type FormStateChanges = Partial<FormState>;
|
||||
|
||||
export const useIndicesConfigurationFormState = ({
|
||||
initialFormState = defaultFormState,
|
||||
}: {
|
||||
initialFormState?: FormState;
|
||||
}) => {
|
||||
const [formStateChanges, setFormStateChanges] = useState<FormStateChanges>({});
|
||||
|
||||
const resetForm = useCallback(() => setFormStateChanges({}), []);
|
||||
|
||||
const formState = useMemo(
|
||||
() => ({
|
||||
...initialFormState,
|
||||
...formStateChanges,
|
||||
}),
|
||||
[initialFormState, formStateChanges]
|
||||
);
|
||||
|
||||
const nameFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.name),
|
||||
name: 'name',
|
||||
onChange: name => setFormStateChanges(changes => ({ ...changes, name })),
|
||||
value: formState.name,
|
||||
}),
|
||||
[formState.name]
|
||||
);
|
||||
const logAliasFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.logAlias),
|
||||
name: 'logAlias',
|
||||
onChange: logAlias => setFormStateChanges(changes => ({ ...changes, logAlias })),
|
||||
value: formState.logAlias,
|
||||
}),
|
||||
[formState.logAlias]
|
||||
);
|
||||
const metricAliasFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.metricAlias),
|
||||
name: 'metricAlias',
|
||||
onChange: metricAlias => setFormStateChanges(changes => ({ ...changes, metricAlias })),
|
||||
value: formState.metricAlias,
|
||||
}),
|
||||
[formState.metricAlias]
|
||||
);
|
||||
const containerFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.containerField),
|
||||
name: `containerField`,
|
||||
onChange: containerField =>
|
||||
setFormStateChanges(changes => ({ ...changes, containerField })),
|
||||
value: formState.containerField,
|
||||
}),
|
||||
[formState.containerField]
|
||||
);
|
||||
const hostFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.hostField),
|
||||
name: `hostField`,
|
||||
onChange: hostField => setFormStateChanges(changes => ({ ...changes, hostField })),
|
||||
value: formState.hostField,
|
||||
}),
|
||||
[formState.hostField]
|
||||
);
|
||||
const podFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.podField),
|
||||
name: `podField`,
|
||||
onChange: podField => setFormStateChanges(changes => ({ ...changes, podField })),
|
||||
value: formState.podField,
|
||||
}),
|
||||
[formState.podField]
|
||||
);
|
||||
const tiebreakerFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.tiebreakerField),
|
||||
name: `tiebreakerField`,
|
||||
onChange: tiebreakerField =>
|
||||
setFormStateChanges(changes => ({ ...changes, tiebreakerField })),
|
||||
value: formState.tiebreakerField,
|
||||
}),
|
||||
[formState.tiebreakerField]
|
||||
);
|
||||
const timestampFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.timestampField),
|
||||
name: `timestampField`,
|
||||
onChange: timestampField =>
|
||||
setFormStateChanges(changes => ({ ...changes, timestampField })),
|
||||
value: formState.timestampField,
|
||||
}),
|
||||
[formState.timestampField]
|
||||
);
|
||||
|
||||
const fieldProps = useMemo(
|
||||
() => ({
|
||||
name: nameFieldProps,
|
||||
logAlias: logAliasFieldProps,
|
||||
metricAlias: metricAliasFieldProps,
|
||||
containerField: containerFieldFieldProps,
|
||||
hostField: hostFieldFieldProps,
|
||||
podField: podFieldFieldProps,
|
||||
tiebreakerField: tiebreakerFieldFieldProps,
|
||||
timestampField: timestampFieldFieldProps,
|
||||
}),
|
||||
[
|
||||
nameFieldProps,
|
||||
logAliasFieldProps,
|
||||
metricAliasFieldProps,
|
||||
containerFieldFieldProps,
|
||||
hostFieldFieldProps,
|
||||
podFieldFieldProps,
|
||||
tiebreakerFieldFieldProps,
|
||||
timestampFieldFieldProps,
|
||||
]
|
||||
);
|
||||
|
||||
const errors = useMemo(
|
||||
() =>
|
||||
Object.values(fieldProps).reduce<ReactNode[]>(
|
||||
(accumulatedErrors, { error }) => [...accumulatedErrors, ...error],
|
||||
[]
|
||||
),
|
||||
[fieldProps]
|
||||
);
|
||||
|
||||
const isFormValid = useMemo(() => errors.length <= 0, [errors]);
|
||||
|
||||
const isFormDirty = useMemo(() => Object.keys(formStateChanges).length > 0, [formStateChanges]);
|
||||
|
||||
return {
|
||||
errors,
|
||||
fieldProps,
|
||||
formState,
|
||||
formStateChanges,
|
||||
isFormDirty,
|
||||
isFormValid,
|
||||
resetForm,
|
||||
};
|
||||
};
|
||||
|
||||
const defaultFormState: FormState = {
|
||||
name: '',
|
||||
description: '',
|
||||
logAlias: '',
|
||||
metricAlias: '',
|
||||
containerField: '',
|
||||
hostField: '',
|
||||
messageField: [],
|
||||
podField: '',
|
||||
tiebreakerField: '',
|
||||
timestampField: '',
|
||||
};
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import { EuiCode, EuiFieldText, EuiForm, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { InputFieldProps } from './source_configuration_form_state';
|
||||
import { InputFieldProps } from './input_fields';
|
||||
|
||||
interface IndicesConfigurationPanelProps {
|
||||
isLoading: boolean;
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export interface InputFieldProps<
|
||||
Value extends string = string,
|
||||
FieldElement extends HTMLInputElement = HTMLInputElement
|
||||
> {
|
||||
error: React.ReactNode[];
|
||||
isInvalid: boolean;
|
||||
name: string;
|
||||
onChange?: React.ChangeEventHandler<FieldElement>;
|
||||
value?: Value;
|
||||
}
|
||||
|
||||
export type FieldErrorMessage = string | JSX.Element;
|
||||
|
||||
export const createInputFieldProps = <
|
||||
Value extends string = string,
|
||||
FieldElement extends HTMLInputElement = HTMLInputElement
|
||||
>({
|
||||
errors,
|
||||
name,
|
||||
onChange,
|
||||
value,
|
||||
}: {
|
||||
errors: FieldErrorMessage[];
|
||||
name: string;
|
||||
onChange: (newValue: string) => void;
|
||||
value: Value;
|
||||
}): InputFieldProps<Value, FieldElement> => ({
|
||||
error: errors,
|
||||
isInvalid: errors.length > 0,
|
||||
name,
|
||||
onChange: (evt: React.ChangeEvent<FieldElement>) => onChange(evt.currentTarget.value),
|
||||
value,
|
||||
});
|
||||
|
||||
export const validateInputFieldNotEmpty = (value: string) =>
|
||||
value === ''
|
||||
? [
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.fieldEmptyErrorMessage"
|
||||
defaultMessage="The field must not be empty"
|
||||
/>,
|
||||
]
|
||||
: [];
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
LogColumnConfiguration,
|
||||
isTimestampLogColumnConfiguration,
|
||||
isMessageLogColumnConfiguration,
|
||||
TimestampLogColumnConfiguration,
|
||||
MessageLogColumnConfiguration,
|
||||
FieldLogColumnConfiguration,
|
||||
} from '../../utils/source_configuration';
|
||||
|
||||
export interface TimestampLogColumnConfigurationProps {
|
||||
logColumnConfiguration: TimestampLogColumnConfiguration['timestampColumn'];
|
||||
remove: () => void;
|
||||
type: 'timestamp';
|
||||
}
|
||||
|
||||
export interface MessageLogColumnConfigurationProps {
|
||||
logColumnConfiguration: MessageLogColumnConfiguration['messageColumn'];
|
||||
remove: () => void;
|
||||
type: 'message';
|
||||
}
|
||||
|
||||
export interface FieldLogColumnConfigurationProps {
|
||||
logColumnConfiguration: FieldLogColumnConfiguration['fieldColumn'];
|
||||
remove: () => void;
|
||||
type: 'field';
|
||||
}
|
||||
|
||||
export type LogColumnConfigurationProps =
|
||||
| TimestampLogColumnConfigurationProps
|
||||
| MessageLogColumnConfigurationProps
|
||||
| FieldLogColumnConfigurationProps;
|
||||
|
||||
interface FormState {
|
||||
logColumns: LogColumnConfiguration[];
|
||||
}
|
||||
|
||||
type FormStateChanges = Partial<FormState>;
|
||||
|
||||
export const useLogColumnsConfigurationFormState = ({
|
||||
initialFormState = defaultFormState,
|
||||
}: {
|
||||
initialFormState?: FormState;
|
||||
}) => {
|
||||
const [formStateChanges, setFormStateChanges] = useState<FormStateChanges>({});
|
||||
|
||||
const resetForm = useCallback(() => setFormStateChanges({}), []);
|
||||
|
||||
const formState = useMemo(
|
||||
() => ({
|
||||
...initialFormState,
|
||||
...formStateChanges,
|
||||
}),
|
||||
[initialFormState, formStateChanges]
|
||||
);
|
||||
|
||||
const logColumnConfigurationProps = useMemo<LogColumnConfigurationProps[]>(
|
||||
() =>
|
||||
formState.logColumns.map(
|
||||
(logColumn): LogColumnConfigurationProps => {
|
||||
const remove = () =>
|
||||
setFormStateChanges(changes => ({
|
||||
...changes,
|
||||
logColumns: formState.logColumns.filter(item => item !== logColumn),
|
||||
}));
|
||||
|
||||
if (isTimestampLogColumnConfiguration(logColumn)) {
|
||||
return {
|
||||
logColumnConfiguration: logColumn.timestampColumn,
|
||||
remove,
|
||||
type: 'timestamp',
|
||||
};
|
||||
} else if (isMessageLogColumnConfiguration(logColumn)) {
|
||||
return {
|
||||
logColumnConfiguration: logColumn.messageColumn,
|
||||
remove,
|
||||
type: 'message',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
logColumnConfiguration: logColumn.fieldColumn,
|
||||
remove,
|
||||
type: 'field',
|
||||
};
|
||||
}
|
||||
}
|
||||
),
|
||||
[formState.logColumns]
|
||||
);
|
||||
|
||||
const addLogColumn = useCallback(
|
||||
(logColumnConfiguration: LogColumnConfiguration) =>
|
||||
setFormStateChanges(changes => ({
|
||||
...changes,
|
||||
logColumns: [...formState.logColumns, logColumnConfiguration],
|
||||
})),
|
||||
[formState.logColumns]
|
||||
);
|
||||
|
||||
const errors = useMemo(
|
||||
() =>
|
||||
logColumnConfigurationProps.length <= 0
|
||||
? [
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.logColumnListEmptyErrorMessage"
|
||||
defaultMessage="The log column list must not be empty."
|
||||
/>,
|
||||
]
|
||||
: [],
|
||||
[logColumnConfigurationProps]
|
||||
);
|
||||
|
||||
const isFormValid = useMemo(() => (errors.length <= 0 ? true : false), [errors]);
|
||||
|
||||
const isFormDirty = useMemo(() => Object.keys(formStateChanges).length > 0, [formStateChanges]);
|
||||
|
||||
return {
|
||||
addLogColumn,
|
||||
errors,
|
||||
logColumnConfigurationProps,
|
||||
formState,
|
||||
formStateChanges,
|
||||
isFormDirty,
|
||||
isFormValid,
|
||||
resetForm,
|
||||
};
|
||||
};
|
||||
|
||||
const defaultFormState: FormState = {
|
||||
logColumns: [],
|
||||
};
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiEmptyPrompt,
|
||||
EuiForm,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { AddLogColumnButtonAndPopover } from './add_log_column_popover';
|
||||
import {
|
||||
FieldLogColumnConfigurationProps,
|
||||
LogColumnConfigurationProps,
|
||||
} from './log_columns_configuration_form_state';
|
||||
import { LogColumnConfiguration } from '../../utils/source_configuration';
|
||||
|
||||
interface LogColumnsConfigurationPanelProps {
|
||||
availableFields: string[];
|
||||
isLoading: boolean;
|
||||
logColumnConfiguration: LogColumnConfigurationProps[];
|
||||
addLogColumn: (logColumn: LogColumnConfiguration) => void;
|
||||
}
|
||||
|
||||
export const LogColumnsConfigurationPanel: React.FunctionComponent<
|
||||
LogColumnsConfigurationPanelProps
|
||||
> = ({ addLogColumn, availableFields, isLoading, logColumnConfiguration }) => (
|
||||
<EuiForm>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="s" data-test-subj="sourceConfigurationLogColumnsSectionTitle">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.logColumnsSectionTitle"
|
||||
defaultMessage="Columns"
|
||||
/>
|
||||
</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<AddLogColumnButtonAndPopover
|
||||
addLogColumn={addLogColumn}
|
||||
availableFields={availableFields}
|
||||
isDisabled={isLoading}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{logColumnConfiguration.length > 0 ? (
|
||||
logColumnConfiguration.map((column, index) => (
|
||||
<LogColumnConfigurationPanel
|
||||
logColumnConfigurationProps={column}
|
||||
key={`logColumnConfigurationPanel-${index}`}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<LogColumnConfigurationEmptyPrompt />
|
||||
)}
|
||||
</EuiForm>
|
||||
);
|
||||
|
||||
interface LogColumnConfigurationPanelProps {
|
||||
logColumnConfigurationProps: LogColumnConfigurationProps;
|
||||
}
|
||||
|
||||
const LogColumnConfigurationPanel: React.FunctionComponent<LogColumnConfigurationPanelProps> = ({
|
||||
logColumnConfigurationProps,
|
||||
}) => (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
{logColumnConfigurationProps.type === 'timestamp' ? (
|
||||
<TimestampLogColumnConfigurationPanel
|
||||
logColumnConfigurationProps={logColumnConfigurationProps}
|
||||
/>
|
||||
) : logColumnConfigurationProps.type === 'message' ? (
|
||||
<MessageLogColumnConfigurationPanel
|
||||
logColumnConfigurationProps={logColumnConfigurationProps}
|
||||
/>
|
||||
) : (
|
||||
<FieldLogColumnConfigurationPanel logColumnConfigurationProps={logColumnConfigurationProps} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const TimestampLogColumnConfigurationPanel: React.FunctionComponent<
|
||||
LogColumnConfigurationPanelProps
|
||||
> = ({ logColumnConfigurationProps }) => (
|
||||
<ExplainedLogColumnConfigurationPanel
|
||||
fieldName="Timestamp"
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
tagName="span"
|
||||
id="xpack.infra.sourceConfiguration.timestampLogColumnDescription"
|
||||
defaultMessage="This built-in field shows the log entry's time as determined by the {timestampSetting} field setting."
|
||||
values={{
|
||||
timestampSetting: <code>timestamp</code>,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
removeColumn={logColumnConfigurationProps.remove}
|
||||
/>
|
||||
);
|
||||
|
||||
const MessageLogColumnConfigurationPanel: React.FunctionComponent<
|
||||
LogColumnConfigurationPanelProps
|
||||
> = ({ logColumnConfigurationProps }) => (
|
||||
<ExplainedLogColumnConfigurationPanel
|
||||
fieldName="Message"
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
tagName="span"
|
||||
id="xpack.infra.sourceConfiguration.messageLogColumnDescription"
|
||||
defaultMessage="This built-in field shows the log entry message as derived from the document fields."
|
||||
/>
|
||||
}
|
||||
removeColumn={logColumnConfigurationProps.remove}
|
||||
/>
|
||||
);
|
||||
|
||||
const FieldLogColumnConfigurationPanel: React.FunctionComponent<{
|
||||
logColumnConfigurationProps: FieldLogColumnConfigurationProps;
|
||||
}> = ({
|
||||
logColumnConfigurationProps: {
|
||||
logColumnConfiguration: { field },
|
||||
remove,
|
||||
},
|
||||
}) => (
|
||||
<EuiPanel data-test-subj={`logColumnPanel fieldLogColumnPanel fieldLogColumnPanel:${field}`}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={1}>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.fieldLogColumnTitle"
|
||||
defaultMessage="Field"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<code>{field}</code>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<RemoveLogColumnButton onClick={remove} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
||||
const ExplainedLogColumnConfigurationPanel: React.FunctionComponent<{
|
||||
fieldName: React.ReactNode;
|
||||
helpText: React.ReactNode;
|
||||
removeColumn: () => void;
|
||||
}> = ({ fieldName, helpText, removeColumn }) => (
|
||||
<EuiPanel
|
||||
data-test-subj={`logColumnPanel builtInLogColumnPanel builtInLogColumnPanel:${fieldName}`}
|
||||
>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={1}>{fieldName}</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
<EuiText size="s" color="subdued">
|
||||
{helpText}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<RemoveLogColumnButton onClick={removeColumn} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
||||
const RemoveLogColumnButton = injectI18n<{
|
||||
onClick?: () => void;
|
||||
}>(({ intl, onClick }) => {
|
||||
const removeColumnLabel = intl.formatMessage({
|
||||
id: 'xpack.infra.sourceConfiguration.removeLogColumnButtonLabel',
|
||||
defaultMessage: 'Remove this column',
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
aria-label={removeColumnLabel}
|
||||
color="danger"
|
||||
data-test-subj="removeLogColumnButton"
|
||||
iconType="trash"
|
||||
onClick={onClick}
|
||||
title={removeColumnLabel}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const LogColumnConfigurationEmptyPrompt: React.FunctionComponent = () => (
|
||||
<EuiEmptyPrompt
|
||||
iconType="list"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.noLogColumnsTitle"
|
||||
defaultMessage="No columns"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.noLogColumnsDescription"
|
||||
defaultMessage="Add a column to this list using the button above."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
);
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { InputFieldProps } from './source_configuration_form_state';
|
||||
import { InputFieldProps } from './input_fields';
|
||||
|
||||
interface NameConfigurationPanelProps {
|
||||
isLoading: boolean;
|
||||
|
@ -22,7 +22,7 @@ export const NameConfigurationPanel = ({
|
|||
nameFieldProps,
|
||||
}: NameConfigurationPanelProps) => (
|
||||
<EuiForm>
|
||||
<EuiTitle size="s">
|
||||
<EuiTitle size="s" data-test-subj="sourceConfigurationNameSectionTitle">
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.nameSectionTitle"
|
||||
|
|
|
@ -7,215 +7,240 @@
|
|||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutFooter,
|
||||
EuiFlyoutHeader,
|
||||
EuiTabbedContent,
|
||||
EuiTabbedContentTab,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage, injectI18n, InjectedIntl } from '@kbn/i18n/react';
|
||||
import React, { useCallback, useContext, useMemo } from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { Source } from '../../containers/source';
|
||||
import { FieldsConfigurationPanel } from './fields_configuration_panel';
|
||||
import { IndicesConfigurationPanel } from './indices_configuration_panel';
|
||||
import { NameConfigurationPanel } from './name_configuration_panel';
|
||||
import { LogColumnsConfigurationPanel } from './log_columns_configuration_panel';
|
||||
import { SourceConfigurationFlyoutState } from './source_configuration_flyout_state';
|
||||
import { useSourceConfigurationFormState } from './source_configuration_form_state';
|
||||
|
||||
const noop = () => undefined;
|
||||
|
||||
interface SourceConfigurationFlyoutProps {
|
||||
intl: InjectedIntl;
|
||||
shouldAllowEdit: boolean;
|
||||
}
|
||||
|
||||
export const SourceConfigurationFlyout: React.FunctionComponent<
|
||||
SourceConfigurationFlyoutProps
|
||||
> = props => {
|
||||
const { shouldAllowEdit } = props;
|
||||
const { isVisible, hide } = useContext(SourceConfigurationFlyoutState.Context);
|
||||
export const SourceConfigurationFlyout = injectI18n(
|
||||
({ intl, shouldAllowEdit }: SourceConfigurationFlyoutProps) => {
|
||||
const { isVisible, hide } = useContext(SourceConfigurationFlyoutState.Context);
|
||||
|
||||
const {
|
||||
createSourceConfiguration,
|
||||
source,
|
||||
sourceExists,
|
||||
isLoading,
|
||||
updateSourceConfiguration,
|
||||
} = useContext(Source.Context);
|
||||
const {
|
||||
createSourceConfiguration,
|
||||
source,
|
||||
sourceExists,
|
||||
isLoading,
|
||||
updateSourceConfiguration,
|
||||
} = useContext(Source.Context);
|
||||
const availableFields = useMemo(
|
||||
() => (source && source.status ? source.status.indexFields.map(field => field.name) : []),
|
||||
[source]
|
||||
);
|
||||
|
||||
const configuration = source && source.configuration;
|
||||
const initialFormState = useMemo(
|
||||
() =>
|
||||
configuration
|
||||
? {
|
||||
name: configuration.name,
|
||||
description: configuration.description,
|
||||
fields: {
|
||||
container: configuration.fields.container,
|
||||
host: configuration.fields.host,
|
||||
message: configuration.fields.message,
|
||||
pod: configuration.fields.pod,
|
||||
tiebreaker: configuration.fields.tiebreaker,
|
||||
timestamp: configuration.fields.timestamp,
|
||||
},
|
||||
logAlias: configuration.logAlias,
|
||||
metricAlias: configuration.metricAlias,
|
||||
}
|
||||
: defaultFormState,
|
||||
[configuration]
|
||||
);
|
||||
const {
|
||||
addLogColumn,
|
||||
indicesConfigurationProps,
|
||||
logColumnConfigurationProps,
|
||||
errors,
|
||||
resetForm,
|
||||
isFormDirty,
|
||||
isFormValid,
|
||||
formState,
|
||||
formStateChanges,
|
||||
} = useSourceConfigurationFormState(source && source.configuration);
|
||||
|
||||
const {
|
||||
fieldProps,
|
||||
formState,
|
||||
isFormDirty,
|
||||
isFormValid,
|
||||
resetForm,
|
||||
updates,
|
||||
} = useSourceConfigurationFormState({
|
||||
initialFormState,
|
||||
});
|
||||
const persistUpdates = useCallback(
|
||||
async () => {
|
||||
if (sourceExists) {
|
||||
await updateSourceConfiguration(formStateChanges);
|
||||
} else {
|
||||
await createSourceConfiguration(formState);
|
||||
}
|
||||
resetForm();
|
||||
},
|
||||
[
|
||||
sourceExists,
|
||||
updateSourceConfiguration,
|
||||
createSourceConfiguration,
|
||||
resetForm,
|
||||
formState,
|
||||
formStateChanges,
|
||||
]
|
||||
);
|
||||
|
||||
const persistUpdates = useCallback(
|
||||
async () => {
|
||||
if (sourceExists) {
|
||||
await updateSourceConfiguration(updates);
|
||||
} else {
|
||||
await createSourceConfiguration(formState);
|
||||
}
|
||||
resetForm();
|
||||
},
|
||||
[sourceExists, updateSourceConfiguration, createSourceConfiguration, resetForm, formState]
|
||||
);
|
||||
if (!isVisible || !source || !source.configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isVisible || !configuration) {
|
||||
return null;
|
||||
}
|
||||
const tabs: EuiTabbedContentTab[] = [
|
||||
{
|
||||
id: 'indicesAndFieldsTab',
|
||||
name: intl.formatMessage({
|
||||
id: 'xpack.infra.sourceConfiguration.sourceConfigurationIndicesTabTitle',
|
||||
defaultMessage: 'Indices and fields',
|
||||
}),
|
||||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<NameConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
nameFieldProps={indicesConfigurationProps.name}
|
||||
readOnly={!shouldAllowEdit}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<IndicesConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
logAliasFieldProps={indicesConfigurationProps.logAlias}
|
||||
metricAliasFieldProps={indicesConfigurationProps.metricAlias}
|
||||
readOnly={!shouldAllowEdit}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<FieldsConfigurationPanel
|
||||
containerFieldProps={indicesConfigurationProps.containerField}
|
||||
hostFieldProps={indicesConfigurationProps.hostField}
|
||||
isLoading={isLoading}
|
||||
podFieldProps={indicesConfigurationProps.podField}
|
||||
readOnly={!shouldAllowEdit}
|
||||
tiebreakerFieldProps={indicesConfigurationProps.tiebreakerField}
|
||||
timestampFieldProps={indicesConfigurationProps.timestampField}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'logsTab',
|
||||
name: intl.formatMessage({
|
||||
id: 'xpack.infra.sourceConfiguration.sourceConfigurationLogColumnsTabTitle',
|
||||
defaultMessage: 'Log Columns',
|
||||
}),
|
||||
content: (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<LogColumnsConfigurationPanel
|
||||
addLogColumn={addLogColumn}
|
||||
availableFields={availableFields}
|
||||
isLoading={isLoading}
|
||||
logColumnConfiguration={logColumnConfigurationProps}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<EuiFlyout
|
||||
aria-labelledby="sourceConfigurationTitle"
|
||||
data-test-subj="sourceConfigurationFlyout"
|
||||
hideCloseButton
|
||||
onClose={noop}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle>
|
||||
<h2 id="sourceConfigurationTitle">
|
||||
{shouldAllowEdit ? (
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.sourceConfigurationTitle"
|
||||
defaultMessage="Configure source"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.sourceConfigurationReadonlyTitle"
|
||||
defaultMessage="View source configuration"
|
||||
/>
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<NameConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
readOnly={!shouldAllowEdit}
|
||||
nameFieldProps={fieldProps.name}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<IndicesConfigurationPanel
|
||||
isLoading={isLoading}
|
||||
readOnly={!shouldAllowEdit}
|
||||
logAliasFieldProps={fieldProps.logAlias}
|
||||
metricAliasFieldProps={fieldProps.metricAlias}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<FieldsConfigurationPanel
|
||||
containerFieldProps={fieldProps.containerField}
|
||||
hostFieldProps={fieldProps.hostField}
|
||||
isLoading={isLoading}
|
||||
readOnly={!shouldAllowEdit}
|
||||
podFieldProps={fieldProps.podField}
|
||||
tiebreakerFieldProps={fieldProps.tiebreakerField}
|
||||
timestampFieldProps={fieldProps.timestampField}
|
||||
/>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
{!isFormDirty ? (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="closeFlyoutButton"
|
||||
iconType="cross"
|
||||
isDisabled={isLoading}
|
||||
onClick={() => hide()}
|
||||
>
|
||||
return (
|
||||
<EuiFlyout
|
||||
aria-labelledby="sourceConfigurationTitle"
|
||||
data-test-subj="sourceConfigurationFlyout"
|
||||
hideCloseButton
|
||||
onClose={noop}
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
<EuiTitle>
|
||||
<h2 id="sourceConfigurationTitle">
|
||||
{shouldAllowEdit ? (
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.closeButtonLabel"
|
||||
defaultMessage="Close"
|
||||
id="xpack.infra.sourceConfiguration.sourceConfigurationTitle"
|
||||
defaultMessage="Configure source"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="discardAndCloseFlyoutButton"
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
isDisabled={isLoading}
|
||||
onClick={() => {
|
||||
resetForm();
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.discardAndCloseButtonLabel"
|
||||
defaultMessage="Discard and Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem />
|
||||
{shouldAllowEdit && (
|
||||
<EuiFlexItem grow={false}>
|
||||
{isLoading ? (
|
||||
<EuiButton color="primary" isLoading fill>
|
||||
Loading
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiButton
|
||||
data-test-subj="updateSourceConfigurationButton"
|
||||
color="primary"
|
||||
isDisabled={!isFormDirty || !isFormValid}
|
||||
fill
|
||||
onClick={persistUpdates}
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.sourceConfigurationReadonlyTitle"
|
||||
defaultMessage="View source configuration"
|
||||
/>
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiTabbedContent tabs={tabs} />
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
{errors.length > 0 ? (
|
||||
<>
|
||||
<EuiCallOut color="danger">
|
||||
<ul>
|
||||
{errors.map((error, errorIndex) => (
|
||||
<li key={errorIndex}>{error}</li>
|
||||
))}
|
||||
</ul>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
) : null}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
{!isFormDirty ? (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="closeFlyoutButton"
|
||||
iconType="cross"
|
||||
isDisabled={isLoading}
|
||||
onClick={() => hide()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.updateSourceConfigurationButtonLabel"
|
||||
defaultMessage="Update Source"
|
||||
id="xpack.infra.sourceConfiguration.closeButtonLabel"
|
||||
defaultMessage="Close"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="discardAndCloseFlyoutButton"
|
||||
color="danger"
|
||||
iconType="cross"
|
||||
isDisabled={isLoading}
|
||||
onClick={() => {
|
||||
resetForm();
|
||||
hide();
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.discardAndCloseButtonLabel"
|
||||
defaultMessage="Discard and Close"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
||||
|
||||
const defaultFormState = {
|
||||
name: '',
|
||||
description: '',
|
||||
fields: {
|
||||
container: '',
|
||||
host: '',
|
||||
message: [],
|
||||
pod: '',
|
||||
tiebreaker: '',
|
||||
timestamp: '',
|
||||
},
|
||||
logAlias: '',
|
||||
metricAlias: '',
|
||||
};
|
||||
<EuiFlexItem />
|
||||
{shouldAllowEdit && (
|
||||
<EuiFlexItem grow={false}>
|
||||
{isLoading ? (
|
||||
<EuiButton color="primary" isLoading fill>
|
||||
Loading
|
||||
</EuiButton>
|
||||
) : (
|
||||
<EuiButton
|
||||
data-test-subj="updateSourceConfigurationButton"
|
||||
color="primary"
|
||||
isDisabled={!isFormDirty || !isFormValid}
|
||||
fill
|
||||
onClick={persistUpdates}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.updateSourceConfigurationButtonLabel"
|
||||
defaultMessage="Update Source"
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlyout>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -4,251 +4,117 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import mergeAll from 'lodash/fp/mergeAll';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { convertChangeToUpdater } from '../../../common/source_configuration';
|
||||
import { UpdateSourceInput } from '../../graphql/types';
|
||||
import { useIndicesConfigurationFormState } from './indices_configuration_form_state';
|
||||
import { useLogColumnsConfigurationFormState } from './log_columns_configuration_form_state';
|
||||
import { SourceConfiguration } from '../../utils/source_configuration';
|
||||
|
||||
export interface InputFieldProps<
|
||||
Value extends string = string,
|
||||
FieldElement extends HTMLInputElement = HTMLInputElement
|
||||
> {
|
||||
error: React.ReactNode[];
|
||||
isInvalid: boolean;
|
||||
name: string;
|
||||
onChange?: React.ChangeEventHandler<FieldElement>;
|
||||
value?: Value;
|
||||
}
|
||||
export const useSourceConfigurationFormState = (configuration?: SourceConfiguration) => {
|
||||
const indicesConfigurationFormState = useIndicesConfigurationFormState({
|
||||
initialFormState: useMemo(
|
||||
() =>
|
||||
configuration
|
||||
? {
|
||||
name: configuration.name,
|
||||
description: configuration.description,
|
||||
logAlias: configuration.logAlias,
|
||||
metricAlias: configuration.metricAlias,
|
||||
containerField: configuration.fields.container,
|
||||
hostField: configuration.fields.host,
|
||||
messageField: configuration.fields.message,
|
||||
podField: configuration.fields.pod,
|
||||
tiebreakerField: configuration.fields.tiebreaker,
|
||||
timestampField: configuration.fields.timestamp,
|
||||
}
|
||||
: undefined,
|
||||
[configuration]
|
||||
),
|
||||
});
|
||||
|
||||
type FieldErrorMessage = string | JSX.Element;
|
||||
const logColumnsConfigurationFormState = useLogColumnsConfigurationFormState({
|
||||
initialFormState: useMemo(
|
||||
() =>
|
||||
configuration
|
||||
? {
|
||||
logColumns: configuration.logColumns,
|
||||
}
|
||||
: undefined,
|
||||
[configuration]
|
||||
),
|
||||
});
|
||||
|
||||
interface FormState {
|
||||
name: string;
|
||||
description: string;
|
||||
metricAlias: string;
|
||||
logAlias: string;
|
||||
fields: {
|
||||
container: string;
|
||||
host: string;
|
||||
message: string[];
|
||||
pod: string;
|
||||
tiebreaker: string;
|
||||
timestamp: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const useSourceConfigurationFormState = ({
|
||||
initialFormState,
|
||||
}: {
|
||||
initialFormState: FormState;
|
||||
}) => {
|
||||
const [updates, setUpdates] = useState<UpdateSourceInput[]>([]);
|
||||
|
||||
const addOrCombineLastUpdate = useCallback(
|
||||
(newUpdate: UpdateSourceInput) =>
|
||||
setUpdates(currentUpdates => [
|
||||
...currentUpdates.slice(0, -1),
|
||||
...maybeCombineUpdates(currentUpdates[currentUpdates.length - 1], newUpdate),
|
||||
]),
|
||||
[setUpdates]
|
||||
const errors = useMemo(
|
||||
() => [...indicesConfigurationFormState.errors, ...logColumnsConfigurationFormState.errors],
|
||||
[indicesConfigurationFormState.errors, logColumnsConfigurationFormState.errors]
|
||||
);
|
||||
|
||||
const resetForm = useCallback(() => setUpdates([]), []);
|
||||
|
||||
const formState = useMemo(
|
||||
() =>
|
||||
updates
|
||||
.map(convertChangeToUpdater)
|
||||
.reduce((state, updater) => updater(state), initialFormState),
|
||||
[updates, initialFormState]
|
||||
const resetForm = useCallback(
|
||||
() => {
|
||||
indicesConfigurationFormState.resetForm();
|
||||
logColumnsConfigurationFormState.resetForm();
|
||||
},
|
||||
[indicesConfigurationFormState.resetForm, logColumnsConfigurationFormState.formState]
|
||||
);
|
||||
|
||||
const nameFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.name),
|
||||
name: 'name',
|
||||
onChange: name => addOrCombineLastUpdate({ setName: { name } }),
|
||||
value: formState.name,
|
||||
}),
|
||||
[formState.name, addOrCombineLastUpdate]
|
||||
);
|
||||
const logAliasFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.logAlias),
|
||||
name: 'logAlias',
|
||||
onChange: logAlias => addOrCombineLastUpdate({ setAliases: { logAlias } }),
|
||||
value: formState.logAlias,
|
||||
}),
|
||||
[formState.logAlias, addOrCombineLastUpdate]
|
||||
);
|
||||
const metricAliasFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.metricAlias),
|
||||
name: 'metricAlias',
|
||||
onChange: metricAlias => addOrCombineLastUpdate({ setAliases: { metricAlias } }),
|
||||
value: formState.metricAlias,
|
||||
}),
|
||||
[formState.metricAlias, addOrCombineLastUpdate]
|
||||
);
|
||||
const containerFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.container),
|
||||
name: `containerField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { container: value } }),
|
||||
value: formState.fields.container,
|
||||
}),
|
||||
[formState.fields.container, addOrCombineLastUpdate]
|
||||
);
|
||||
const hostFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.host),
|
||||
name: `hostField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { host: value } }),
|
||||
value: formState.fields.host,
|
||||
}),
|
||||
[formState.fields.host, addOrCombineLastUpdate]
|
||||
);
|
||||
const podFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.pod),
|
||||
name: `podField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { pod: value } }),
|
||||
value: formState.fields.pod,
|
||||
}),
|
||||
[formState.fields.pod, addOrCombineLastUpdate]
|
||||
);
|
||||
const tiebreakerFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.tiebreaker),
|
||||
name: `tiebreakerField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { tiebreaker: value } }),
|
||||
value: formState.fields.tiebreaker,
|
||||
}),
|
||||
[formState.fields.tiebreaker, addOrCombineLastUpdate]
|
||||
);
|
||||
const timestampFieldFieldProps = useMemo(
|
||||
() =>
|
||||
createInputFieldProps({
|
||||
errors: validateInputFieldNotEmpty(formState.fields.timestamp),
|
||||
name: `timestampField`,
|
||||
onChange: value => addOrCombineLastUpdate({ setFields: { timestamp: value } }),
|
||||
value: formState.fields.timestamp,
|
||||
}),
|
||||
[formState.fields.timestamp, addOrCombineLastUpdate]
|
||||
);
|
||||
|
||||
const fieldProps = useMemo(
|
||||
() => ({
|
||||
name: nameFieldProps,
|
||||
logAlias: logAliasFieldProps,
|
||||
metricAlias: metricAliasFieldProps,
|
||||
containerField: containerFieldFieldProps,
|
||||
hostField: hostFieldFieldProps,
|
||||
podField: podFieldFieldProps,
|
||||
tiebreakerField: tiebreakerFieldFieldProps,
|
||||
timestampField: timestampFieldFieldProps,
|
||||
}),
|
||||
[
|
||||
nameFieldProps,
|
||||
logAliasFieldProps,
|
||||
metricAliasFieldProps,
|
||||
containerFieldFieldProps,
|
||||
hostFieldFieldProps,
|
||||
podFieldFieldProps,
|
||||
tiebreakerFieldFieldProps,
|
||||
timestampFieldFieldProps,
|
||||
]
|
||||
const isFormDirty = useMemo(
|
||||
() => indicesConfigurationFormState.isFormDirty || logColumnsConfigurationFormState.isFormDirty,
|
||||
[indicesConfigurationFormState.isFormDirty, logColumnsConfigurationFormState.isFormDirty]
|
||||
);
|
||||
|
||||
const isFormValid = useMemo(
|
||||
() => Object.values(fieldProps).every(({ error }) => error.length <= 0),
|
||||
[fieldProps]
|
||||
() => indicesConfigurationFormState.isFormValid && logColumnsConfigurationFormState.isFormValid,
|
||||
[indicesConfigurationFormState.isFormValid, logColumnsConfigurationFormState.isFormValid]
|
||||
);
|
||||
|
||||
const isFormDirty = useMemo(() => updates.length > 0, [updates]);
|
||||
const formState = useMemo(
|
||||
() => ({
|
||||
name: indicesConfigurationFormState.formState.name,
|
||||
description: indicesConfigurationFormState.formState.description,
|
||||
logAlias: indicesConfigurationFormState.formState.logAlias,
|
||||
metricAlias: indicesConfigurationFormState.formState.metricAlias,
|
||||
fields: {
|
||||
container: indicesConfigurationFormState.formState.containerField,
|
||||
host: indicesConfigurationFormState.formState.hostField,
|
||||
pod: indicesConfigurationFormState.formState.podField,
|
||||
tiebreaker: indicesConfigurationFormState.formState.tiebreakerField,
|
||||
timestamp: indicesConfigurationFormState.formState.timestampField,
|
||||
},
|
||||
logColumns: logColumnsConfigurationFormState.formState.logColumns,
|
||||
}),
|
||||
[indicesConfigurationFormState.formState, logColumnsConfigurationFormState.formState]
|
||||
);
|
||||
|
||||
const formStateChanges = useMemo(
|
||||
() => ({
|
||||
name: indicesConfigurationFormState.formStateChanges.name,
|
||||
description: indicesConfigurationFormState.formStateChanges.description,
|
||||
logAlias: indicesConfigurationFormState.formStateChanges.logAlias,
|
||||
metricAlias: indicesConfigurationFormState.formStateChanges.metricAlias,
|
||||
fields: {
|
||||
container: indicesConfigurationFormState.formStateChanges.containerField,
|
||||
host: indicesConfigurationFormState.formStateChanges.hostField,
|
||||
pod: indicesConfigurationFormState.formStateChanges.podField,
|
||||
tiebreaker: indicesConfigurationFormState.formStateChanges.tiebreakerField,
|
||||
timestamp: indicesConfigurationFormState.formStateChanges.timestampField,
|
||||
},
|
||||
logColumns: logColumnsConfigurationFormState.formStateChanges.logColumns,
|
||||
}),
|
||||
[
|
||||
indicesConfigurationFormState.formStateChanges,
|
||||
logColumnsConfigurationFormState.formStateChanges,
|
||||
]
|
||||
);
|
||||
|
||||
return {
|
||||
fieldProps,
|
||||
addLogColumn: logColumnsConfigurationFormState.addLogColumn,
|
||||
errors,
|
||||
formState,
|
||||
formStateChanges,
|
||||
isFormDirty,
|
||||
isFormValid,
|
||||
indicesConfigurationProps: indicesConfigurationFormState.fieldProps,
|
||||
logColumnConfigurationProps: logColumnsConfigurationFormState.logColumnConfigurationProps,
|
||||
resetForm,
|
||||
updates,
|
||||
};
|
||||
};
|
||||
|
||||
const createInputFieldProps = <
|
||||
Value extends string = string,
|
||||
FieldElement extends HTMLInputElement = HTMLInputElement
|
||||
>({
|
||||
errors,
|
||||
name,
|
||||
onChange,
|
||||
value,
|
||||
}: {
|
||||
errors: FieldErrorMessage[];
|
||||
name: string;
|
||||
onChange: (newValue: string) => void;
|
||||
value: Value;
|
||||
}): InputFieldProps<Value, FieldElement> => ({
|
||||
error: errors,
|
||||
isInvalid: errors.length > 0,
|
||||
name,
|
||||
onChange: (evt: React.ChangeEvent<FieldElement>) => onChange(evt.currentTarget.value),
|
||||
value,
|
||||
});
|
||||
|
||||
const validateInputFieldNotEmpty = (value: string) =>
|
||||
value === ''
|
||||
? [
|
||||
<FormattedMessage
|
||||
id="xpack.infra.sourceConfiguration.fieldEmptyErrorMessage"
|
||||
defaultMessage="The field must not be empty"
|
||||
/>,
|
||||
]
|
||||
: [];
|
||||
|
||||
/**
|
||||
* Tries to combine the given updates by naively checking whether they can be
|
||||
* merged into one update.
|
||||
*
|
||||
* This is only judged to be the case when all of the following conditions are
|
||||
* met:
|
||||
*
|
||||
* 1. The update only contains one operation.
|
||||
* 2. The operation is the same on in both updates.
|
||||
* 3. The operation is known to be safe to combine.
|
||||
*/
|
||||
const maybeCombineUpdates = (
|
||||
firstUpdate: UpdateSourceInput | undefined,
|
||||
secondUpdate: UpdateSourceInput
|
||||
): UpdateSourceInput[] => {
|
||||
if (!firstUpdate) {
|
||||
return [secondUpdate];
|
||||
}
|
||||
|
||||
const firstKeys = Object.keys(firstUpdate);
|
||||
const secondKeys = Object.keys(secondUpdate);
|
||||
|
||||
const isSingleOperation = firstKeys.length === secondKeys.length && firstKeys.length === 1;
|
||||
const isSameOperation = firstKeys[0] === secondKeys[0];
|
||||
// to guard against future operations, which might not be safe to merge naively
|
||||
const isMergeableOperation = mergeableOperations.indexOf(firstKeys[0]) > -1;
|
||||
|
||||
if (isSingleOperation && isSameOperation && isMergeableOperation) {
|
||||
return [mergeAll([firstUpdate, secondUpdate])];
|
||||
}
|
||||
|
||||
return [firstUpdate, secondUpdate];
|
||||
};
|
||||
|
||||
const mergeableOperations = ['setName', 'setDescription', 'setAliases', 'setFields'];
|
||||
|
|
|
@ -7,9 +7,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { SearchResult } from '../../../common/log_search_result';
|
||||
import { logEntriesActions, logEntriesSelectors, logPositionSelectors, State } from '../../store';
|
||||
import { LogEntry, LogEntryMessageSegment } from '../../utils/log_entry';
|
||||
import { LogEntry } from '../../utils/log_entry';
|
||||
import { asChildFunctionRenderer } from '../../utils/typed_react';
|
||||
import { bindPlainActionCreators } from '../../utils/typed_redux';
|
||||
|
||||
|
@ -49,27 +48,7 @@ const selectItems = createSelector(
|
|||
)
|
||||
);
|
||||
|
||||
const createLogEntryStreamItem = (logEntry: LogEntry, searchResult?: SearchResult) => ({
|
||||
const createLogEntryStreamItem = (logEntry: LogEntry) => ({
|
||||
kind: 'logEntry' as 'logEntry',
|
||||
logEntry: {
|
||||
gid: logEntry.gid,
|
||||
origin: {
|
||||
id: logEntry.gid,
|
||||
index: '',
|
||||
type: '',
|
||||
},
|
||||
fields: {
|
||||
time: logEntry.key.time,
|
||||
tiebreaker: logEntry.key.tiebreaker,
|
||||
message: logEntry.message.map(formatMessageSegment).join(''),
|
||||
},
|
||||
},
|
||||
searchResult,
|
||||
logEntry,
|
||||
});
|
||||
|
||||
const formatMessageSegment = (messageSegment: LogEntryMessageSegment): string =>
|
||||
messageSegment.__typename === 'InfraLogMessageFieldSegment'
|
||||
? messageSegment.value
|
||||
: messageSegment.__typename === 'InfraLogMessageConstantSegment'
|
||||
? messageSegment.constant
|
||||
: 'failed to format message';
|
||||
|
|
|
@ -15,9 +15,9 @@ import {
|
|||
export const createSourceMutation = gql`
|
||||
mutation CreateSourceConfigurationMutation(
|
||||
$sourceId: ID!
|
||||
$sourceConfiguration: CreateSourceInput!
|
||||
$sourceProperties: UpdateSourceInput!
|
||||
) {
|
||||
createSource(id: $sourceId, source: $sourceConfiguration) {
|
||||
createSource(id: $sourceId, sourceProperties: $sourceProperties) {
|
||||
source {
|
||||
...InfraSourceFields
|
||||
configuration {
|
||||
|
|
|
@ -9,7 +9,6 @@ import { useEffect, useMemo, useState } from 'react';
|
|||
|
||||
import {
|
||||
CreateSourceConfigurationMutation,
|
||||
CreateSourceInput,
|
||||
SourceQuery,
|
||||
UpdateSourceInput,
|
||||
UpdateSourceMutation,
|
||||
|
@ -51,7 +50,7 @@ export const useSource = ({ sourceId }: { sourceId: string }) => {
|
|||
|
||||
const [createSourceConfigurationRequest, createSourceConfiguration] = useTrackedPromise(
|
||||
{
|
||||
createPromise: async (newSourceConfiguration: CreateSourceInput) => {
|
||||
createPromise: async (sourceProperties: UpdateSourceInput) => {
|
||||
if (!apolloClient) {
|
||||
throw new DependencyError(
|
||||
'Failed to create source configuration: No apollo client available.'
|
||||
|
@ -66,21 +65,7 @@ export const useSource = ({ sourceId }: { sourceId: string }) => {
|
|||
fetchPolicy: 'no-cache',
|
||||
variables: {
|
||||
sourceId,
|
||||
sourceConfiguration: {
|
||||
name: newSourceConfiguration.name,
|
||||
description: newSourceConfiguration.description,
|
||||
metricAlias: newSourceConfiguration.metricAlias,
|
||||
logAlias: newSourceConfiguration.logAlias,
|
||||
fields: newSourceConfiguration.fields
|
||||
? {
|
||||
container: newSourceConfiguration.fields.container,
|
||||
host: newSourceConfiguration.fields.host,
|
||||
pod: newSourceConfiguration.fields.pod,
|
||||
tiebreaker: newSourceConfiguration.fields.tiebreaker,
|
||||
timestamp: newSourceConfiguration.fields.timestamp,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
sourceProperties,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -95,7 +80,7 @@ export const useSource = ({ sourceId }: { sourceId: string }) => {
|
|||
|
||||
const [updateSourceConfigurationRequest, updateSourceConfiguration] = useTrackedPromise(
|
||||
{
|
||||
createPromise: async (changes: UpdateSourceInput[]) => {
|
||||
createPromise: async (sourceProperties: UpdateSourceInput) => {
|
||||
if (!apolloClient) {
|
||||
throw new DependencyError(
|
||||
'Failed to update source configuration: No apollo client available.'
|
||||
|
@ -110,7 +95,7 @@ export const useSource = ({ sourceId }: { sourceId: string }) => {
|
|||
fetchPolicy: 'no-cache',
|
||||
variables: {
|
||||
sourceId,
|
||||
changes,
|
||||
sourceProperties,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -20,6 +20,24 @@ export const sourceConfigurationFieldsFragment = gql`
|
|||
tiebreaker
|
||||
timestamp
|
||||
}
|
||||
logColumns {
|
||||
... on InfraSourceTimestampLogColumn {
|
||||
timestampColumn {
|
||||
id
|
||||
}
|
||||
}
|
||||
... on InfraSourceMessageLogColumn {
|
||||
messageColumn {
|
||||
id
|
||||
}
|
||||
}
|
||||
... on InfraSourceFieldLogColumn {
|
||||
fieldColumn {
|
||||
id
|
||||
field
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@ import {
|
|||
} from './source_fields_fragment.gql_query';
|
||||
|
||||
export const updateSourceMutation = gql`
|
||||
mutation UpdateSourceMutation($sourceId: ID = "default", $changes: [UpdateSourceInput!]!) {
|
||||
updateSource(id: $sourceId, changes: $changes) {
|
||||
mutation UpdateSourceMutation($sourceId: ID = "default", $sourceProperties: UpdateSourceInput!) {
|
||||
updateSource(id: $sourceId, sourceProperties: $sourceProperties) {
|
||||
source {
|
||||
...InfraSourceFields
|
||||
configuration {
|
||||
|
|
|
@ -7,14 +7,14 @@
|
|||
import React, { useContext } from 'react';
|
||||
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
import { CreateSourceInput, SourceQuery, UpdateSourceInput } from '../../graphql/types';
|
||||
import { SourceQuery, UpdateSourceInput } from '../../graphql/types';
|
||||
import { RendererFunction } from '../../utils/typed_react';
|
||||
import { Source } from '../source';
|
||||
|
||||
interface WithSourceProps {
|
||||
children: RendererFunction<{
|
||||
configuration?: SourceQuery.Query['source']['configuration'];
|
||||
create: (sourceConfiguration: CreateSourceInput) => Promise<any> | undefined;
|
||||
create: (sourceProperties: UpdateSourceInput) => Promise<any> | undefined;
|
||||
derivedIndexPattern: StaticIndexPattern;
|
||||
exists?: boolean;
|
||||
hasFailed: boolean;
|
||||
|
@ -25,7 +25,7 @@ interface WithSourceProps {
|
|||
metricAlias?: string;
|
||||
metricIndicesExist?: boolean;
|
||||
sourceId: string;
|
||||
update: (changes: UpdateSourceInput[]) => Promise<any> | undefined;
|
||||
update: (sourceProperties: UpdateSourceInput) => Promise<any> | undefined;
|
||||
version?: string;
|
||||
}>;
|
||||
}
|
||||
|
|
|
@ -514,6 +514,26 @@
|
|||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "logColumns",
|
||||
"description": "The columns to use for log display",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "UNION", "name": "InfraSourceLogColumn", "ofType": null }
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
|
@ -612,6 +632,182 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "UNION",
|
||||
"name": "InfraSourceLogColumn",
|
||||
"description": "All known log column types",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": [
|
||||
{ "kind": "OBJECT", "name": "InfraSourceTimestampLogColumn", "ofType": null },
|
||||
{ "kind": "OBJECT", "name": "InfraSourceMessageLogColumn", "ofType": null },
|
||||
{ "kind": "OBJECT", "name": "InfraSourceFieldLogColumn", "ofType": null }
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceTimestampLogColumn",
|
||||
"description": "The built-in timestamp log column",
|
||||
"fields": [
|
||||
{
|
||||
"name": "timestampColumn",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceTimestampLogColumnAttributes",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceTimestampLogColumnAttributes",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "A unique id for the column",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "ID", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceMessageLogColumn",
|
||||
"description": "The built-in message log column",
|
||||
"fields": [
|
||||
{
|
||||
"name": "messageColumn",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceMessageLogColumnAttributes",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceMessageLogColumnAttributes",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "A unique id for the column",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "ID", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceFieldLogColumn",
|
||||
"description": "A log column containing a field value",
|
||||
"fields": [
|
||||
{
|
||||
"name": "fieldColumn",
|
||||
"description": "",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceFieldLogColumnAttributes",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceFieldLogColumnAttributes",
|
||||
"description": "",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "A unique id for the column",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "ID", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"description": "The field name this column refers to",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceStatus",
|
||||
|
@ -1134,6 +1330,74 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "columns",
|
||||
"description": "The columns used for rendering the log entry",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "UNION", "name": "InfraLogEntryColumn", "ofType": null }
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "UNION",
|
||||
"name": "InfraLogEntryColumn",
|
||||
"description": "A column of a log entry",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": [
|
||||
{ "kind": "OBJECT", "name": "InfraLogEntryTimestampColumn", "ofType": null },
|
||||
{ "kind": "OBJECT", "name": "InfraLogEntryMessageColumn", "ofType": null },
|
||||
{ "kind": "OBJECT", "name": "InfraLogEntryFieldColumn", "ofType": null }
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraLogEntryTimestampColumn",
|
||||
"description": "A special built-in column that contains the log entry's timestamp",
|
||||
"fields": [
|
||||
{
|
||||
"name": "timestamp",
|
||||
"description": "The timestamp",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "Float", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraLogEntryMessageColumn",
|
||||
"description": "A special built-in column that contains the log entry's constructed message",
|
||||
"fields": [
|
||||
{
|
||||
"name": "message",
|
||||
"description": "A list of the formatted log entry segments",
|
||||
|
@ -1231,7 +1495,7 @@
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraLogMessageConstantSegment",
|
||||
"description": "A segment of the log entry message that was derived from a field",
|
||||
"description": "A segment of the log entry message that was derived from a string literal",
|
||||
"fields": [
|
||||
{
|
||||
"name": "constant",
|
||||
|
@ -1251,6 +1515,41 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraLogEntryFieldColumn",
|
||||
"description": "A column that contains the value of a field of the log entry",
|
||||
"fields": [
|
||||
{
|
||||
"name": "field",
|
||||
"description": "The field name of the column",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"description": "The value of the field in the log entry",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraLogSummaryInterval",
|
||||
|
@ -2067,12 +2366,12 @@
|
|||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "source",
|
||||
"name": "sourceProperties",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "INPUT_OBJECT", "name": "CreateSourceInput", "ofType": null }
|
||||
"ofType": { "kind": "INPUT_OBJECT", "name": "UpdateSourceInput", "ofType": null }
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
|
@ -2080,14 +2379,14 @@
|
|||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "OBJECT", "name": "CreateSourceResult", "ofType": null }
|
||||
"ofType": { "kind": "OBJECT", "name": "UpdateSourceResult", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "updateSource",
|
||||
"description": "Modify an existing source using the given sequence of update operations",
|
||||
"description": "Modify an existing source",
|
||||
"args": [
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -2100,24 +2399,12 @@
|
|||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "changes",
|
||||
"description": "A sequence of update operations",
|
||||
"name": "sourceProperties",
|
||||
"description": "The properties to update the source with",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceInput",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
"ofType": { "kind": "INPUT_OBJECT", "name": "UpdateSourceInput", "ofType": null }
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
|
@ -2161,18 +2448,14 @@
|
|||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateSourceInput",
|
||||
"description": "The source to be created",
|
||||
"name": "UpdateSourceInput",
|
||||
"description": "The properties to update the source with",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the data source",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
|
@ -2196,7 +2479,25 @@
|
|||
{
|
||||
"name": "fields",
|
||||
"description": "The field mapping to use for this source",
|
||||
"type": { "kind": "INPUT_OBJECT", "name": "CreateSourceFieldsInput", "ofType": null },
|
||||
"type": { "kind": "INPUT_OBJECT", "name": "UpdateSourceFieldsInput", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "logColumns",
|
||||
"description": "The log columns to display for this source",
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceLogColumnInput",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
|
@ -2206,7 +2507,7 @@
|
|||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "CreateSourceFieldsInput",
|
||||
"name": "UpdateSourceFieldsInput",
|
||||
"description": "The mapping of semantic fields of the source to be created",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
|
@ -2245,61 +2546,40 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "CreateSourceResult",
|
||||
"description": "The result of a successful source creation",
|
||||
"fields": [
|
||||
{
|
||||
"name": "source",
|
||||
"description": "The source that was created",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "OBJECT", "name": "InfraSource", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceInput",
|
||||
"description": "The update operations to be performed",
|
||||
"name": "UpdateSourceLogColumnInput",
|
||||
"description": "One of the log column types to display for this source",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "setName",
|
||||
"description": "The name update operation to be performed",
|
||||
"type": { "kind": "INPUT_OBJECT", "name": "UpdateSourceNameInput", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "setDescription",
|
||||
"description": "The description update operation to be performed",
|
||||
"name": "fieldColumn",
|
||||
"description": "A custom field log column",
|
||||
"type": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceDescriptionInput",
|
||||
"name": "UpdateSourceFieldLogColumnInput",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "setAliases",
|
||||
"description": "The alias update operation to be performed",
|
||||
"type": { "kind": "INPUT_OBJECT", "name": "UpdateSourceAliasInput", "ofType": null },
|
||||
"name": "messageColumn",
|
||||
"description": "A built-in message log column",
|
||||
"type": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceMessageLogColumnInput",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "setFields",
|
||||
"description": "The field update operation to be performed",
|
||||
"type": { "kind": "INPUT_OBJECT", "name": "UpdateSourceFieldsInput", "ofType": null },
|
||||
"name": "timestampColumn",
|
||||
"description": "A built-in timestamp log column",
|
||||
"type": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceTimestampLogColumnInput",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
|
@ -2309,13 +2589,23 @@
|
|||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceNameInput",
|
||||
"description": "A name update operation",
|
||||
"name": "UpdateSourceFieldLogColumnInput",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The new name to be set",
|
||||
"name": "id",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "ID", "ofType": null }
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "field",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
|
@ -2330,17 +2620,17 @@
|
|||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceDescriptionInput",
|
||||
"description": "A description update operation",
|
||||
"name": "UpdateSourceMessageLogColumnInput",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "description",
|
||||
"description": "The new description to be set",
|
||||
"name": "id",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
"ofType": { "kind": "SCALAR", "name": "ID", "ofType": null }
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
|
@ -2351,61 +2641,18 @@
|
|||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceAliasInput",
|
||||
"description": "An alias update operation",
|
||||
"name": "UpdateSourceTimestampLogColumnInput",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "logAlias",
|
||||
"description": "The new log index pattern or alias to bet set",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "metricAlias",
|
||||
"description": "The new metric index pattern or alias to bet set",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "UpdateSourceFieldsInput",
|
||||
"description": "A field update operations",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "container",
|
||||
"description": "The new container field to be set",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "host",
|
||||
"description": "The new host field to be set",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "pod",
|
||||
"description": "The new pod field to be set",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "tiebreaker",
|
||||
"description": "The new tiebreaker field to be set",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "timestamp",
|
||||
"description": "The new timestamp field to be set",
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"name": "id",
|
||||
"description": "",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "ID", "ofType": null }
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
|
@ -2416,11 +2663,11 @@
|
|||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "UpdateSourceResult",
|
||||
"description": "The result of a sequence of source update operations",
|
||||
"description": "The result of a successful source update",
|
||||
"fields": [
|
||||
{
|
||||
"name": "source",
|
||||
"description": "The source after the operations were performed",
|
||||
"description": "The source that was updated",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
|
|
|
@ -53,6 +53,8 @@ export interface InfraSourceConfiguration {
|
|||
logAlias: string;
|
||||
/** The field mapping to use for this source */
|
||||
fields: InfraSourceFields;
|
||||
/** The columns to use for log display */
|
||||
logColumns: InfraSourceLogColumn[];
|
||||
}
|
||||
/** A mapping of semantic fields to their document counterparts */
|
||||
export interface InfraSourceFields {
|
||||
|
@ -69,6 +71,35 @@ export interface InfraSourceFields {
|
|||
/** The field to use as a timestamp for metrics and logs */
|
||||
timestamp: string;
|
||||
}
|
||||
/** The built-in timestamp log column */
|
||||
export interface InfraSourceTimestampLogColumn {
|
||||
timestampColumn: InfraSourceTimestampLogColumnAttributes;
|
||||
}
|
||||
|
||||
export interface InfraSourceTimestampLogColumnAttributes {
|
||||
/** A unique id for the column */
|
||||
id: string;
|
||||
}
|
||||
/** The built-in message log column */
|
||||
export interface InfraSourceMessageLogColumn {
|
||||
messageColumn: InfraSourceMessageLogColumnAttributes;
|
||||
}
|
||||
|
||||
export interface InfraSourceMessageLogColumnAttributes {
|
||||
/** A unique id for the column */
|
||||
id: string;
|
||||
}
|
||||
/** A log column containing a field value */
|
||||
export interface InfraSourceFieldLogColumn {
|
||||
fieldColumn: InfraSourceFieldLogColumnAttributes;
|
||||
}
|
||||
|
||||
export interface InfraSourceFieldLogColumnAttributes {
|
||||
/** A unique id for the column */
|
||||
id: string;
|
||||
/** The field name this column refers to */
|
||||
field: string;
|
||||
}
|
||||
/** The status of an infrastructure data source */
|
||||
export interface InfraSourceStatus {
|
||||
/** Whether the configured metric alias exists */
|
||||
|
@ -143,6 +174,16 @@ export interface InfraLogEntry {
|
|||
gid: string;
|
||||
/** The source id */
|
||||
source: string;
|
||||
/** The columns used for rendering the log entry */
|
||||
columns: InfraLogEntryColumn[];
|
||||
}
|
||||
/** A special built-in column that contains the log entry's timestamp */
|
||||
export interface InfraLogEntryTimestampColumn {
|
||||
/** The timestamp */
|
||||
timestamp: number;
|
||||
}
|
||||
/** A special built-in column that contains the log entry's constructed message */
|
||||
export interface InfraLogEntryMessageColumn {
|
||||
/** A list of the formatted log entry segments */
|
||||
message: InfraLogMessageSegment[];
|
||||
}
|
||||
|
@ -155,11 +196,18 @@ export interface InfraLogMessageFieldSegment {
|
|||
/** A list of highlighted substrings of the value */
|
||||
highlights: string[];
|
||||
}
|
||||
/** A segment of the log entry message that was derived from a field */
|
||||
/** A segment of the log entry message that was derived from a string literal */
|
||||
export interface InfraLogMessageConstantSegment {
|
||||
/** The segment's message */
|
||||
constant: string;
|
||||
}
|
||||
/** A column that contains the value of a field of the log entry */
|
||||
export interface InfraLogEntryFieldColumn {
|
||||
/** The field name of the column */
|
||||
field: string;
|
||||
/** The value of the field in the log entry */
|
||||
value: string;
|
||||
}
|
||||
/** A consecutive sequence of log summary buckets */
|
||||
export interface InfraLogSummaryInterval {
|
||||
/** The millisecond timestamp corresponding to the start of the interval covered by the summary */
|
||||
|
@ -246,20 +294,15 @@ export interface InfraDataPoint {
|
|||
|
||||
export interface Mutation {
|
||||
/** Create a new source of infrastructure data */
|
||||
createSource: CreateSourceResult;
|
||||
/** Modify an existing source using the given sequence of update operations */
|
||||
createSource: UpdateSourceResult;
|
||||
/** Modify an existing source */
|
||||
updateSource: UpdateSourceResult;
|
||||
/** Delete a source of infrastructure data */
|
||||
deleteSource: DeleteSourceResult;
|
||||
}
|
||||
/** The result of a successful source creation */
|
||||
export interface CreateSourceResult {
|
||||
/** The source that was created */
|
||||
source: InfraSource;
|
||||
}
|
||||
/** The result of a sequence of source update operations */
|
||||
/** The result of a successful source update */
|
||||
export interface UpdateSourceResult {
|
||||
/** The source after the operations were performed */
|
||||
/** The source that was updated */
|
||||
source: InfraSource;
|
||||
}
|
||||
/** The result of a source deletion operations */
|
||||
|
@ -298,10 +341,10 @@ export interface InfraSnapshotMetricInput {
|
|||
/** The type of metric */
|
||||
type: InfraSnapshotMetricType;
|
||||
}
|
||||
/** The source to be created */
|
||||
export interface CreateSourceInput {
|
||||
/** The properties to update the source with */
|
||||
export interface UpdateSourceInput {
|
||||
/** The name of the data source */
|
||||
name: string;
|
||||
name?: string | null;
|
||||
/** A description of the data source */
|
||||
description?: string | null;
|
||||
/** The alias to read metric data from */
|
||||
|
@ -309,10 +352,12 @@ export interface CreateSourceInput {
|
|||
/** The alias to read log data from */
|
||||
logAlias?: string | null;
|
||||
/** The field mapping to use for this source */
|
||||
fields?: CreateSourceFieldsInput | null;
|
||||
fields?: UpdateSourceFieldsInput | null;
|
||||
/** The log columns to display for this source */
|
||||
logColumns?: UpdateSourceLogColumnInput[] | null;
|
||||
}
|
||||
/** The mapping of semantic fields of the source to be created */
|
||||
export interface CreateSourceFieldsInput {
|
||||
export interface UpdateSourceFieldsInput {
|
||||
/** The field to identify a container by */
|
||||
container?: string | null;
|
||||
/** The fields to identify a host by */
|
||||
|
@ -324,46 +369,28 @@ export interface CreateSourceFieldsInput {
|
|||
/** The field to use as a timestamp for metrics and logs */
|
||||
timestamp?: string | null;
|
||||
}
|
||||
/** The update operations to be performed */
|
||||
export interface UpdateSourceInput {
|
||||
/** The name update operation to be performed */
|
||||
setName?: UpdateSourceNameInput | null;
|
||||
/** The description update operation to be performed */
|
||||
setDescription?: UpdateSourceDescriptionInput | null;
|
||||
/** The alias update operation to be performed */
|
||||
setAliases?: UpdateSourceAliasInput | null;
|
||||
/** The field update operation to be performed */
|
||||
setFields?: UpdateSourceFieldsInput | null;
|
||||
/** One of the log column types to display for this source */
|
||||
export interface UpdateSourceLogColumnInput {
|
||||
/** A custom field log column */
|
||||
fieldColumn?: UpdateSourceFieldLogColumnInput | null;
|
||||
/** A built-in message log column */
|
||||
messageColumn?: UpdateSourceMessageLogColumnInput | null;
|
||||
/** A built-in timestamp log column */
|
||||
timestampColumn?: UpdateSourceTimestampLogColumnInput | null;
|
||||
}
|
||||
/** A name update operation */
|
||||
export interface UpdateSourceNameInput {
|
||||
/** The new name to be set */
|
||||
name: string;
|
||||
|
||||
export interface UpdateSourceFieldLogColumnInput {
|
||||
id: string;
|
||||
|
||||
field: string;
|
||||
}
|
||||
/** A description update operation */
|
||||
export interface UpdateSourceDescriptionInput {
|
||||
/** The new description to be set */
|
||||
description: string;
|
||||
|
||||
export interface UpdateSourceMessageLogColumnInput {
|
||||
id: string;
|
||||
}
|
||||
/** An alias update operation */
|
||||
export interface UpdateSourceAliasInput {
|
||||
/** The new log index pattern or alias to bet set */
|
||||
logAlias?: string | null;
|
||||
/** The new metric index pattern or alias to bet set */
|
||||
metricAlias?: string | null;
|
||||
}
|
||||
/** A field update operations */
|
||||
export interface UpdateSourceFieldsInput {
|
||||
/** The new container field to be set */
|
||||
container?: string | null;
|
||||
/** The new host field to be set */
|
||||
host?: string | null;
|
||||
/** The new pod field to be set */
|
||||
pod?: string | null;
|
||||
/** The new tiebreaker field to be set */
|
||||
tiebreaker?: string | null;
|
||||
/** The new timestamp field to be set */
|
||||
timestamp?: string | null;
|
||||
|
||||
export interface UpdateSourceTimestampLogColumnInput {
|
||||
id: string;
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
|
@ -442,13 +469,13 @@ export interface CreateSourceMutationArgs {
|
|||
/** The id of the source */
|
||||
id: string;
|
||||
|
||||
source: CreateSourceInput;
|
||||
sourceProperties: UpdateSourceInput;
|
||||
}
|
||||
export interface UpdateSourceMutationArgs {
|
||||
/** The id of the source */
|
||||
id: string;
|
||||
/** A sequence of update operations */
|
||||
changes: UpdateSourceInput[];
|
||||
/** The properties to update the source with */
|
||||
sourceProperties: UpdateSourceInput;
|
||||
}
|
||||
export interface DeleteSourceMutationArgs {
|
||||
/** The id of the source */
|
||||
|
@ -515,6 +542,18 @@ export enum InfraMetric {
|
|||
// Unions
|
||||
// ====================================================
|
||||
|
||||
/** All known log column types */
|
||||
export type InfraSourceLogColumn =
|
||||
| InfraSourceTimestampLogColumn
|
||||
| InfraSourceMessageLogColumn
|
||||
| InfraSourceFieldLogColumn;
|
||||
|
||||
/** A column of a log entry */
|
||||
export type InfraLogEntryColumn =
|
||||
| InfraLogEntryTimestampColumn
|
||||
| InfraLogEntryMessageColumn
|
||||
| InfraLogEntryFieldColumn;
|
||||
|
||||
/** A segment of the log entry message */
|
||||
export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment;
|
||||
|
||||
|
@ -708,7 +747,7 @@ export namespace MetricsQuery {
|
|||
export namespace CreateSourceConfigurationMutation {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
sourceConfiguration: CreateSourceInput;
|
||||
sourceProperties: UpdateSourceInput;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
|
@ -718,7 +757,7 @@ export namespace CreateSourceConfigurationMutation {
|
|||
};
|
||||
|
||||
export type CreateSource = {
|
||||
__typename?: 'CreateSourceResult';
|
||||
__typename?: 'UpdateSourceResult';
|
||||
|
||||
source: Source;
|
||||
};
|
||||
|
@ -763,7 +802,7 @@ export namespace SourceQuery {
|
|||
export namespace UpdateSourceMutation {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
changes: UpdateSourceInput[];
|
||||
sourceProperties: UpdateSourceInput;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
|
@ -891,41 +930,7 @@ export namespace LogEntries {
|
|||
|
||||
export type End = InfraTimeKeyFields.Fragment;
|
||||
|
||||
export type Entries = {
|
||||
__typename?: 'InfraLogEntry';
|
||||
|
||||
gid: string;
|
||||
|
||||
key: Key;
|
||||
|
||||
message: Message[];
|
||||
};
|
||||
|
||||
export type Key = {
|
||||
__typename?: 'InfraTimeKey';
|
||||
|
||||
time: number;
|
||||
|
||||
tiebreaker: number;
|
||||
};
|
||||
|
||||
export type Message =
|
||||
| InfraLogMessageFieldSegmentInlineFragment
|
||||
| InfraLogMessageConstantSegmentInlineFragment;
|
||||
|
||||
export type InfraLogMessageFieldSegmentInlineFragment = {
|
||||
__typename?: 'InfraLogMessageFieldSegment';
|
||||
|
||||
field: string;
|
||||
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type InfraLogMessageConstantSegmentInlineFragment = {
|
||||
__typename?: 'InfraLogMessageConstantSegment';
|
||||
|
||||
constant: string;
|
||||
};
|
||||
export type Entries = InfraLogEntryFields.Fragment;
|
||||
}
|
||||
|
||||
export namespace SourceConfigurationFields {
|
||||
|
@ -941,6 +946,8 @@ export namespace SourceConfigurationFields {
|
|||
metricAlias: string;
|
||||
|
||||
fields: Fields;
|
||||
|
||||
logColumns: LogColumns[];
|
||||
};
|
||||
|
||||
export type Fields = {
|
||||
|
@ -958,6 +965,49 @@ export namespace SourceConfigurationFields {
|
|||
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
export type LogColumns =
|
||||
| InfraSourceTimestampLogColumnInlineFragment
|
||||
| InfraSourceMessageLogColumnInlineFragment
|
||||
| InfraSourceFieldLogColumnInlineFragment;
|
||||
|
||||
export type InfraSourceTimestampLogColumnInlineFragment = {
|
||||
__typename?: 'InfraSourceTimestampLogColumn';
|
||||
|
||||
timestampColumn: TimestampColumn;
|
||||
};
|
||||
|
||||
export type TimestampColumn = {
|
||||
__typename?: 'InfraSourceTimestampLogColumnAttributes';
|
||||
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type InfraSourceMessageLogColumnInlineFragment = {
|
||||
__typename?: 'InfraSourceMessageLogColumn';
|
||||
|
||||
messageColumn: MessageColumn;
|
||||
};
|
||||
|
||||
export type MessageColumn = {
|
||||
__typename?: 'InfraSourceMessageLogColumnAttributes';
|
||||
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type InfraSourceFieldLogColumnInlineFragment = {
|
||||
__typename?: 'InfraSourceFieldLogColumn';
|
||||
|
||||
fieldColumn: FieldColumn;
|
||||
};
|
||||
|
||||
export type FieldColumn = {
|
||||
__typename?: 'InfraSourceFieldLogColumnAttributes';
|
||||
|
||||
id: string;
|
||||
|
||||
field: string;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace SourceStatusFields {
|
||||
|
@ -1005,3 +1055,66 @@ export namespace InfraSourceFields {
|
|||
updatedAt?: number | null;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace InfraLogEntryFields {
|
||||
export type Fragment = {
|
||||
__typename?: 'InfraLogEntry';
|
||||
|
||||
gid: string;
|
||||
|
||||
key: Key;
|
||||
|
||||
columns: Columns[];
|
||||
};
|
||||
|
||||
export type Key = {
|
||||
__typename?: 'InfraTimeKey';
|
||||
|
||||
time: number;
|
||||
|
||||
tiebreaker: number;
|
||||
};
|
||||
|
||||
export type Columns =
|
||||
| InfraLogEntryTimestampColumnInlineFragment
|
||||
| InfraLogEntryMessageColumnInlineFragment
|
||||
| InfraLogEntryFieldColumnInlineFragment;
|
||||
|
||||
export type InfraLogEntryTimestampColumnInlineFragment = {
|
||||
__typename?: 'InfraLogEntryTimestampColumn';
|
||||
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
export type InfraLogEntryMessageColumnInlineFragment = {
|
||||
__typename?: 'InfraLogEntryMessageColumn';
|
||||
|
||||
message: Message[];
|
||||
};
|
||||
|
||||
export type Message =
|
||||
| InfraLogMessageFieldSegmentInlineFragment
|
||||
| InfraLogMessageConstantSegmentInlineFragment;
|
||||
|
||||
export type InfraLogMessageFieldSegmentInlineFragment = {
|
||||
__typename?: 'InfraLogMessageFieldSegment';
|
||||
|
||||
field: string;
|
||||
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type InfraLogMessageConstantSegmentInlineFragment = {
|
||||
__typename?: 'InfraLogMessageConstantSegment';
|
||||
|
||||
constant: string;
|
||||
};
|
||||
|
||||
export type InfraLogEntryFieldColumnInlineFragment = {
|
||||
__typename?: 'InfraLogEntryFieldColumn';
|
||||
|
||||
field: string;
|
||||
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import { InfraKibanaObservableApiAdapter } from '../adapters/observable_api/kiba
|
|||
|
||||
export function compose(): InfraFrontendLibs {
|
||||
const cache = new InMemoryCache({
|
||||
addTypename: false,
|
||||
fragmentMatcher: new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData,
|
||||
}),
|
||||
|
|
|
@ -30,7 +30,7 @@ import { Source } from '../../containers/source';
|
|||
import { LogsToolbar } from './page_toolbar';
|
||||
|
||||
export const LogsPageLogsContent: React.FunctionComponent = () => {
|
||||
const { derivedIndexPattern } = useContext(Source.Context);
|
||||
const { derivedIndexPattern, sourceId, version } = useContext(Source.Context);
|
||||
const { intervalSize, textScale, textWrap } = useContext(LogViewConfiguration.Context);
|
||||
const {
|
||||
setFlyoutVisibility,
|
||||
|
@ -50,10 +50,10 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
|
|||
<WithLogTextviewUrlState />
|
||||
<WithFlyoutOptionsUrlState />
|
||||
<LogsToolbar />
|
||||
<WithLogPosition>
|
||||
{({ jumpToTargetPosition, stopLiveStreaming }) => (
|
||||
<WithLogFilter indexPattern={derivedIndexPattern}>
|
||||
{({ applyFilterQueryFromKueryExpression }) =>
|
||||
<WithLogFilter indexPattern={derivedIndexPattern}>
|
||||
{({ applyFilterQueryFromKueryExpression }) => (
|
||||
<WithLogPosition>
|
||||
{({ jumpToTargetPosition, stopLiveStreaming }) =>
|
||||
flyoutVisible ? (
|
||||
<LogFlyout
|
||||
setFilter={applyFilterQueryFromKueryExpression}
|
||||
|
@ -68,98 +68,74 @@ export const LogsPageLogsContent: React.FunctionComponent = () => {
|
|||
/>
|
||||
) : null
|
||||
}
|
||||
</WithLogFilter>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
<WithLogFilter indexPattern={derivedIndexPattern}>
|
||||
{({ filterQuery }) => (
|
||||
<PageContent>
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => (
|
||||
<LogPageEventStreamColumn innerRef={measureRef}>
|
||||
<WithLogPosition>
|
||||
{({
|
||||
isAutoReloading,
|
||||
jumpToTargetPosition,
|
||||
reportVisiblePositions,
|
||||
targetPosition,
|
||||
}) => (
|
||||
<WithStreamItems initializeOnMount={!isAutoReloading}>
|
||||
{({
|
||||
hasMoreAfterEnd,
|
||||
hasMoreBeforeStart,
|
||||
isLoadingMore,
|
||||
isReloading,
|
||||
items,
|
||||
lastLoadedTime,
|
||||
loadNewerEntries,
|
||||
}) => (
|
||||
<ScrollableLogTextStreamView
|
||||
hasMoreAfterEnd={hasMoreAfterEnd}
|
||||
hasMoreBeforeStart={hasMoreBeforeStart}
|
||||
height={height}
|
||||
isLoadingMore={isLoadingMore}
|
||||
isReloading={isReloading}
|
||||
isStreaming={isAutoReloading}
|
||||
items={items}
|
||||
jumpToTarget={jumpToTargetPosition}
|
||||
lastLoadedTime={lastLoadedTime}
|
||||
loadNewerItems={loadNewerEntries}
|
||||
reportVisibleInterval={reportVisiblePositions}
|
||||
scale={textScale}
|
||||
target={targetPosition}
|
||||
width={width}
|
||||
wrap={textWrap}
|
||||
setFlyoutItem={setFlyoutId}
|
||||
setFlyoutVisibility={setFlyoutVisibility}
|
||||
highlightedItem={surroundingLogsId ? surroundingLogsId : null}
|
||||
/>
|
||||
)}
|
||||
</WithStreamItems>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
</LogPageEventStreamColumn>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => {
|
||||
return (
|
||||
<LogPageMinimapColumn innerRef={measureRef}>
|
||||
<WithSummary>
|
||||
{({ buckets }) => (
|
||||
<WithLogPosition>
|
||||
{({ jumpToTargetPosition, visibleMidpointTime, visibleTimeInterval }) => (
|
||||
<LogMinimap
|
||||
height={height}
|
||||
width={width}
|
||||
highlightedInterval={visibleTimeInterval}
|
||||
intervalSize={intervalSize}
|
||||
jumpToTarget={jumpToTargetPosition}
|
||||
summaryBuckets={buckets}
|
||||
target={visibleMidpointTime}
|
||||
/>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
)}
|
||||
</WithSummary>
|
||||
</LogPageMinimapColumn>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
</PageContent>
|
||||
</WithLogPosition>
|
||||
)}
|
||||
</WithLogFilter>
|
||||
<PageContent key={`${sourceId}-${version}`}>
|
||||
<WithLogPosition>
|
||||
{({ isAutoReloading, jumpToTargetPosition, reportVisiblePositions, targetPosition }) => (
|
||||
<WithStreamItems initializeOnMount={!isAutoReloading}>
|
||||
{({
|
||||
hasMoreAfterEnd,
|
||||
hasMoreBeforeStart,
|
||||
isLoadingMore,
|
||||
isReloading,
|
||||
items,
|
||||
lastLoadedTime,
|
||||
loadNewerEntries,
|
||||
}) => (
|
||||
<ScrollableLogTextStreamView
|
||||
hasMoreAfterEnd={hasMoreAfterEnd}
|
||||
hasMoreBeforeStart={hasMoreBeforeStart}
|
||||
isLoadingMore={isLoadingMore}
|
||||
isReloading={isReloading}
|
||||
isStreaming={isAutoReloading}
|
||||
items={items}
|
||||
jumpToTarget={jumpToTargetPosition}
|
||||
lastLoadedTime={lastLoadedTime}
|
||||
loadNewerItems={loadNewerEntries}
|
||||
reportVisibleInterval={reportVisiblePositions}
|
||||
scale={textScale}
|
||||
target={targetPosition}
|
||||
wrap={textWrap}
|
||||
setFlyoutItem={setFlyoutId}
|
||||
setFlyoutVisibility={setFlyoutVisibility}
|
||||
highlightedItem={surroundingLogsId ? surroundingLogsId : null}
|
||||
/>
|
||||
)}
|
||||
</WithStreamItems>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
<AutoSizer content>
|
||||
{({ measureRef, content: { width = 0, height = 0 } }) => {
|
||||
return (
|
||||
<LogPageMinimapColumn innerRef={measureRef}>
|
||||
<WithSummary>
|
||||
{({ buckets }) => (
|
||||
<WithLogPosition>
|
||||
{({ jumpToTargetPosition, visibleMidpointTime, visibleTimeInterval }) => (
|
||||
<LogMinimap
|
||||
height={height}
|
||||
width={width}
|
||||
highlightedInterval={visibleTimeInterval}
|
||||
intervalSize={intervalSize}
|
||||
jumpToTarget={jumpToTargetPosition}
|
||||
summaryBuckets={buckets}
|
||||
target={visibleMidpointTime}
|
||||
/>
|
||||
)}
|
||||
</WithLogPosition>
|
||||
)}
|
||||
</WithSummary>
|
||||
</LogPageMinimapColumn>
|
||||
);
|
||||
}}
|
||||
</AutoSizer>
|
||||
</PageContent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const LogPageEventStreamColumn = euiStyled.div`
|
||||
flex: 1 0 0%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const LogPageMinimapColumn = euiStyled.div`
|
||||
flex: 1 0 0%;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -33,24 +33,12 @@ export const logEntriesQuery = gql`
|
|||
hasMoreBefore
|
||||
hasMoreAfter
|
||||
entries {
|
||||
gid
|
||||
key {
|
||||
time
|
||||
tiebreaker
|
||||
}
|
||||
message {
|
||||
... on InfraLogMessageFieldSegment {
|
||||
field
|
||||
value
|
||||
}
|
||||
... on InfraLogMessageConstantSegment {
|
||||
constant
|
||||
}
|
||||
}
|
||||
...InfraLogEntryFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraTimeKey}
|
||||
${sharedFragments.InfraLogEntryFields}
|
||||
`;
|
||||
|
|
|
@ -7,11 +7,18 @@
|
|||
import { bisector } from 'd3-array';
|
||||
|
||||
import { compareToTimeKey, getIndexAtTimeKey, TimeKey } from '../../../common/time';
|
||||
import { LogEntries as LogEntriesQuery } from '../../graphql/types';
|
||||
import { InfraLogEntryFields } from '../../graphql/types';
|
||||
|
||||
export type LogEntry = LogEntriesQuery.Entries;
|
||||
export type LogEntry = InfraLogEntryFields.Fragment;
|
||||
|
||||
export type LogEntryMessageSegment = LogEntriesQuery.Message;
|
||||
export type LogEntryColumn = InfraLogEntryFields.Columns;
|
||||
export type LogEntryMessageColumn = InfraLogEntryFields.InfraLogEntryMessageColumnInlineFragment;
|
||||
export type LogEntryTimestampColumn = InfraLogEntryFields.InfraLogEntryTimestampColumnInlineFragment;
|
||||
export type LogEntryFieldColumn = InfraLogEntryFields.InfraLogEntryFieldColumnInlineFragment;
|
||||
|
||||
export type LogEntryMessageSegment = InfraLogEntryFields.Message;
|
||||
export type LogEntryConstantMessageSegment = InfraLogEntryFields.InfraLogMessageConstantSegmentInlineFragment;
|
||||
export type LogEntryFieldMessageSegment = InfraLogEntryFields.InfraLogMessageFieldSegmentInlineFragment;
|
||||
|
||||
export const getLogEntryKey = (entry: LogEntry) => entry.key;
|
||||
|
||||
|
@ -26,3 +33,20 @@ export const getLogEntryAtTime = (entries: LogEntry[], time: TimeKey) => {
|
|||
|
||||
return entryIndex !== null ? entries[entryIndex] : null;
|
||||
};
|
||||
|
||||
export const isTimestampColumn = (column: LogEntryColumn): column is LogEntryTimestampColumn =>
|
||||
'timestamp' in column;
|
||||
|
||||
export const isMessageColumn = (column: LogEntryColumn): column is LogEntryMessageColumn =>
|
||||
'message' in column;
|
||||
|
||||
export const isFieldColumn = (column: LogEntryColumn): column is LogEntryFieldColumn =>
|
||||
'field' in column;
|
||||
|
||||
export const isConstantSegment = (
|
||||
segment: LogEntryMessageSegment
|
||||
): segment is LogEntryConstantMessageSegment => 'constant' in segment;
|
||||
|
||||
export const isFieldSegment = (
|
||||
segment: LogEntryMessageSegment
|
||||
): segment is LogEntryFieldMessageSegment => 'field' in segment && 'value' in segment;
|
||||
|
|
28
x-pack/plugins/infra/public/utils/source_configuration.ts
Normal file
28
x-pack/plugins/infra/public/utils/source_configuration.ts
Normal file
|
@ -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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SourceConfigurationFields } from '../graphql/types';
|
||||
|
||||
export type SourceConfiguration = SourceConfigurationFields.Fragment;
|
||||
|
||||
export type LogColumnConfiguration = SourceConfigurationFields.LogColumns;
|
||||
export type FieldLogColumnConfiguration = SourceConfigurationFields.InfraSourceFieldLogColumnInlineFragment;
|
||||
export type MessageLogColumnConfiguration = SourceConfigurationFields.InfraSourceMessageLogColumnInlineFragment;
|
||||
export type TimestampLogColumnConfiguration = SourceConfigurationFields.InfraSourceTimestampLogColumnInlineFragment;
|
||||
|
||||
export const isFieldLogColumnConfiguration = (
|
||||
logColumnConfiguration: LogColumnConfiguration
|
||||
): logColumnConfiguration is FieldLogColumnConfiguration => 'fieldColumn' in logColumnConfiguration;
|
||||
|
||||
export const isMessageLogColumnConfiguration = (
|
||||
logColumnConfiguration: LogColumnConfiguration
|
||||
): logColumnConfiguration is MessageLogColumnConfiguration =>
|
||||
'messageColumn' in logColumnConfiguration;
|
||||
|
||||
export const isTimestampLogColumnConfiguration = (
|
||||
logColumnConfiguration: LogColumnConfiguration
|
||||
): logColumnConfiguration is TimestampLogColumnConfiguration =>
|
||||
'timestampColumn' in logColumnConfiguration;
|
|
@ -4,13 +4,20 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { failure } from 'io-ts/lib/PathReporter';
|
||||
|
||||
import {
|
||||
InfraLogEntryColumn,
|
||||
InfraLogEntryFieldColumn,
|
||||
InfraLogEntryMessageColumn,
|
||||
InfraLogEntryTimestampColumn,
|
||||
InfraLogMessageConstantSegment,
|
||||
InfraLogMessageFieldSegment,
|
||||
InfraLogMessageSegment,
|
||||
InfraSourceResolvers,
|
||||
} from '../../graphql/types';
|
||||
import { InfraLogEntriesDomain } from '../../lib/domains/log_entries_domain';
|
||||
import { SourceConfigurationRuntimeType } from '../../lib/sources';
|
||||
import { UsageCollector } from '../../usage/usage_collector';
|
||||
import { parseFilterQuery } from '../../utils/serialized_query';
|
||||
import { ChildResolverOf, InfraResolverOf } from '../../utils/typed_resolvers';
|
||||
|
@ -45,6 +52,15 @@ export const createLogEntriesResolvers = (libs: {
|
|||
logSummaryBetween: InfraSourceLogSummaryBetweenResolver;
|
||||
logItem: InfraSourceLogItem;
|
||||
};
|
||||
InfraLogEntryColumn: {
|
||||
__resolveType(
|
||||
logEntryColumn: InfraLogEntryColumn
|
||||
):
|
||||
| 'InfraLogEntryTimestampColumn'
|
||||
| 'InfraLogEntryMessageColumn'
|
||||
| 'InfraLogEntryFieldColumn'
|
||||
| null;
|
||||
};
|
||||
InfraLogMessageSegment: {
|
||||
__resolveType(
|
||||
messageSegment: InfraLogMessageSegment
|
||||
|
@ -122,11 +138,34 @@ export const createLogEntriesResolvers = (libs: {
|
|||
};
|
||||
},
|
||||
async logItem(source, args, { req }) {
|
||||
return await libs.logEntries.getLogItem(req, args.id, source.configuration);
|
||||
const sourceConfiguration = SourceConfigurationRuntimeType.decode(
|
||||
source.configuration
|
||||
).getOrElseL(errors => {
|
||||
throw new Error(failure(errors).join('\n'));
|
||||
});
|
||||
|
||||
return await libs.logEntries.getLogItem(req, args.id, sourceConfiguration);
|
||||
},
|
||||
},
|
||||
InfraLogEntryColumn: {
|
||||
__resolveType(logEntryColumn) {
|
||||
if (isTimestampColumn(logEntryColumn)) {
|
||||
return 'InfraLogEntryTimestampColumn';
|
||||
}
|
||||
|
||||
if (isMessageColumn(logEntryColumn)) {
|
||||
return 'InfraLogEntryMessageColumn';
|
||||
}
|
||||
|
||||
if (isFieldColumn(logEntryColumn)) {
|
||||
return 'InfraLogEntryFieldColumn';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
InfraLogMessageSegment: {
|
||||
__resolveType: (messageSegment: InfraLogMessageSegment) => {
|
||||
__resolveType(messageSegment) {
|
||||
if (isConstantSegment(messageSegment)) {
|
||||
return 'InfraLogMessageConstantSegment';
|
||||
}
|
||||
|
@ -140,6 +179,15 @@ export const createLogEntriesResolvers = (libs: {
|
|||
},
|
||||
});
|
||||
|
||||
const isTimestampColumn = (column: InfraLogEntryColumn): column is InfraLogEntryTimestampColumn =>
|
||||
'timestamp' in column;
|
||||
|
||||
const isMessageColumn = (column: InfraLogEntryColumn): column is InfraLogEntryMessageColumn =>
|
||||
'message' in column;
|
||||
|
||||
const isFieldColumn = (column: InfraLogEntryColumn): column is InfraLogEntryFieldColumn =>
|
||||
'field' in column && 'value' in column;
|
||||
|
||||
const isConstantSegment = (
|
||||
segment: InfraLogMessageSegment
|
||||
): segment is InfraLogMessageConstantSegment => 'constant' in segment;
|
||||
|
|
|
@ -17,7 +17,7 @@ export const logEntriesSchema = gql`
|
|||
highlights: [String!]!
|
||||
}
|
||||
|
||||
"A segment of the log entry message that was derived from a field"
|
||||
"A segment of the log entry message that was derived from a string literal"
|
||||
type InfraLogMessageConstantSegment {
|
||||
"The segment's message"
|
||||
constant: String!
|
||||
|
@ -26,6 +26,32 @@ export const logEntriesSchema = gql`
|
|||
"A segment of the log entry message"
|
||||
union InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment
|
||||
|
||||
"A special built-in column that contains the log entry's timestamp"
|
||||
type InfraLogEntryTimestampColumn {
|
||||
"The timestamp"
|
||||
timestamp: Float!
|
||||
}
|
||||
|
||||
"A special built-in column that contains the log entry's constructed message"
|
||||
type InfraLogEntryMessageColumn {
|
||||
"A list of the formatted log entry segments"
|
||||
message: [InfraLogMessageSegment!]!
|
||||
}
|
||||
|
||||
"A column that contains the value of a field of the log entry"
|
||||
type InfraLogEntryFieldColumn {
|
||||
"The field name of the column"
|
||||
field: String!
|
||||
"The value of the field in the log entry"
|
||||
value: String!
|
||||
}
|
||||
|
||||
"A column of a log entry"
|
||||
union InfraLogEntryColumn =
|
||||
InfraLogEntryTimestampColumn
|
||||
| InfraLogEntryMessageColumn
|
||||
| InfraLogEntryFieldColumn
|
||||
|
||||
"A log entry"
|
||||
type InfraLogEntry {
|
||||
"A unique representation of the log entry's position in the event stream"
|
||||
|
@ -34,8 +60,8 @@ export const logEntriesSchema = gql`
|
|||
gid: String!
|
||||
"The source id"
|
||||
source: String!
|
||||
"A list of the formatted log entry segments"
|
||||
message: [InfraLogMessageSegment!]!
|
||||
"The columns used for rendering the log entry"
|
||||
columns: [InfraLogEntryColumn!]!
|
||||
}
|
||||
|
||||
"A log summary bucket"
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { failure } from 'io-ts/lib/PathReporter';
|
||||
|
||||
import { InfraSourceResolvers } from '../../graphql/types';
|
||||
import { InfraMetricsDomain } from '../../lib/domains/metrics_domain';
|
||||
import { SourceConfigurationRuntimeType } from '../../lib/sources';
|
||||
import { UsageCollector } from '../../usage/usage_collector';
|
||||
import { ChildResolverOf, InfraResolverOf } from '../../utils/typed_resolvers';
|
||||
import { QuerySourceResolver } from '../sources/resolvers';
|
||||
|
@ -28,13 +31,19 @@ export const createMetricResolvers = (
|
|||
} => ({
|
||||
InfraSource: {
|
||||
async metrics(source, args, { req }) {
|
||||
const sourceConfiguration = SourceConfigurationRuntimeType.decode(
|
||||
source.configuration
|
||||
).getOrElseL(errors => {
|
||||
throw new Error(failure(errors).join('\n'));
|
||||
});
|
||||
|
||||
UsageCollector.countNode(args.nodeType);
|
||||
const options = {
|
||||
nodeId: args.nodeId,
|
||||
nodeType: args.nodeType,
|
||||
timerange: args.timerange,
|
||||
metrics: args.metrics,
|
||||
sourceConfiguration: source.configuration,
|
||||
sourceConfiguration,
|
||||
};
|
||||
return libs.metrics.getMetrics(req, options);
|
||||
},
|
||||
|
|
|
@ -4,10 +4,24 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { convertChangeToUpdater } from '../../../common/source_configuration';
|
||||
import { InfraSourceResolvers, MutationResolvers, QueryResolvers } from '../../graphql/types';
|
||||
import { UserInputError } from 'apollo-server-errors';
|
||||
import { failure } from 'io-ts/lib/PathReporter';
|
||||
|
||||
import {
|
||||
InfraSourceLogColumn,
|
||||
InfraSourceResolvers,
|
||||
MutationResolvers,
|
||||
QueryResolvers,
|
||||
UpdateSourceLogColumnInput,
|
||||
} from '../../graphql/types';
|
||||
import { InfraSourceStatus } from '../../lib/source_status';
|
||||
import { InfraSources } from '../../lib/sources';
|
||||
import {
|
||||
InfraSources,
|
||||
SavedSourceConfigurationFieldColumnRuntimeType,
|
||||
SavedSourceConfigurationMessageColumnRuntimeType,
|
||||
SavedSourceConfigurationTimestampColumnRuntimeType,
|
||||
SavedSourceConfigurationColumnRuntimeType,
|
||||
} from '../../lib/sources';
|
||||
import {
|
||||
ChildResolverOf,
|
||||
InfraResolverOf,
|
||||
|
@ -59,6 +73,15 @@ export const createSourcesResolvers = (
|
|||
InfraSource: {
|
||||
status: InfraSourceStatusResolver;
|
||||
};
|
||||
InfraSourceLogColumn: {
|
||||
__resolveType(
|
||||
logColumn: InfraSourceLogColumn
|
||||
):
|
||||
| 'InfraSourceTimestampLogColumn'
|
||||
| 'InfraSourceMessageLogColumn'
|
||||
| 'InfraSourceFieldLogColumn'
|
||||
| null;
|
||||
};
|
||||
Mutation: {
|
||||
createSource: MutationCreateSourceResolver;
|
||||
deleteSource: MutationDeleteSourceResolver;
|
||||
|
@ -82,14 +105,34 @@ export const createSourcesResolvers = (
|
|||
return source;
|
||||
},
|
||||
},
|
||||
InfraSourceLogColumn: {
|
||||
__resolveType(logColumn) {
|
||||
if (SavedSourceConfigurationTimestampColumnRuntimeType.is(logColumn)) {
|
||||
return 'InfraSourceTimestampLogColumn';
|
||||
}
|
||||
|
||||
if (SavedSourceConfigurationMessageColumnRuntimeType.is(logColumn)) {
|
||||
return 'InfraSourceMessageLogColumn';
|
||||
}
|
||||
|
||||
if (SavedSourceConfigurationFieldColumnRuntimeType.is(logColumn)) {
|
||||
return 'InfraSourceFieldLogColumn';
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
Mutation: {
|
||||
async createSource(root, args, { req }) {
|
||||
const sourceConfiguration = await libs.sources.createSourceConfiguration(
|
||||
req,
|
||||
args.id,
|
||||
compactObject({
|
||||
...args.source,
|
||||
fields: args.source.fields ? compactObject(args.source.fields) : undefined,
|
||||
...args.sourceProperties,
|
||||
fields: args.sourceProperties.fields
|
||||
? compactObject(args.sourceProperties.fields)
|
||||
: undefined,
|
||||
logColumns: decodeLogColumns(args.sourceProperties.logColumns),
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -105,12 +148,16 @@ export const createSourcesResolvers = (
|
|||
};
|
||||
},
|
||||
async updateSource(root, args, { req }) {
|
||||
const updaters = args.changes.map(convertChangeToUpdater);
|
||||
|
||||
const updatedSourceConfiguration = await libs.sources.updateSourceConfiguration(
|
||||
req,
|
||||
args.id,
|
||||
updaters
|
||||
compactObject({
|
||||
...args.sourceProperties,
|
||||
fields: args.sourceProperties.fields
|
||||
? compactObject(args.sourceProperties.fields)
|
||||
: undefined,
|
||||
logColumns: decodeLogColumns(args.sourceProperties.logColumns),
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -133,3 +180,12 @@ const compactObject = <T>(obj: T): CompactObject<T> =>
|
|||
},
|
||||
{} as CompactObject<T>
|
||||
);
|
||||
|
||||
const decodeLogColumns = (logColumns?: UpdateSourceLogColumnInput[] | null) =>
|
||||
logColumns
|
||||
? logColumns.map(logColumn =>
|
||||
SavedSourceConfigurationColumnRuntimeType.decode(logColumn).getOrElseL(errors => {
|
||||
throw new UserInputError(failure(errors).join('\n'));
|
||||
})
|
||||
)
|
||||
: undefined;
|
||||
|
|
|
@ -36,6 +36,8 @@ export const sourcesSchema = gql`
|
|||
logAlias: String!
|
||||
"The field mapping to use for this source"
|
||||
fields: InfraSourceFields!
|
||||
"The columns to use for log display"
|
||||
logColumns: [InfraSourceLogColumn!]!
|
||||
}
|
||||
|
||||
"A mapping of semantic fields to their document counterparts"
|
||||
|
@ -54,6 +56,44 @@ export const sourcesSchema = gql`
|
|||
timestamp: String!
|
||||
}
|
||||
|
||||
"The built-in timestamp log column"
|
||||
type InfraSourceTimestampLogColumn {
|
||||
timestampColumn: InfraSourceTimestampLogColumnAttributes!
|
||||
}
|
||||
|
||||
type InfraSourceTimestampLogColumnAttributes {
|
||||
"A unique id for the column"
|
||||
id: ID!
|
||||
}
|
||||
|
||||
"The built-in message log column"
|
||||
type InfraSourceMessageLogColumn {
|
||||
messageColumn: InfraSourceMessageLogColumnAttributes!
|
||||
}
|
||||
|
||||
type InfraSourceMessageLogColumnAttributes {
|
||||
"A unique id for the column"
|
||||
id: ID!
|
||||
}
|
||||
|
||||
"A log column containing a field value"
|
||||
type InfraSourceFieldLogColumn {
|
||||
fieldColumn: InfraSourceFieldLogColumnAttributes!
|
||||
}
|
||||
|
||||
type InfraSourceFieldLogColumnAttributes {
|
||||
"A unique id for the column"
|
||||
id: ID!
|
||||
"The field name this column refers to"
|
||||
field: String!
|
||||
}
|
||||
|
||||
"All known log column types"
|
||||
union InfraSourceLogColumn =
|
||||
InfraSourceTimestampLogColumn
|
||||
| InfraSourceMessageLogColumn
|
||||
| InfraSourceFieldLogColumn
|
||||
|
||||
extend type Query {
|
||||
"""
|
||||
Get an infrastructure data source by id.
|
||||
|
@ -74,10 +114,10 @@ export const sourcesSchema = gql`
|
|||
allSources: [InfraSource!]!
|
||||
}
|
||||
|
||||
"The source to be created"
|
||||
input CreateSourceInput {
|
||||
"The properties to update the source with"
|
||||
input UpdateSourceInput {
|
||||
"The name of the data source"
|
||||
name: String!
|
||||
name: String
|
||||
"A description of the data source"
|
||||
description: String
|
||||
"The alias to read metric data from"
|
||||
|
@ -85,11 +125,13 @@ export const sourcesSchema = gql`
|
|||
"The alias to read log data from"
|
||||
logAlias: String
|
||||
"The field mapping to use for this source"
|
||||
fields: CreateSourceFieldsInput
|
||||
fields: UpdateSourceFieldsInput
|
||||
"The log columns to display for this source"
|
||||
logColumns: [UpdateSourceLogColumnInput!]
|
||||
}
|
||||
|
||||
"The mapping of semantic fields of the source to be created"
|
||||
input CreateSourceFieldsInput {
|
||||
input UpdateSourceFieldsInput {
|
||||
"The field to identify a container by"
|
||||
container: String
|
||||
"The fields to identify a host by"
|
||||
|
@ -102,61 +144,32 @@ export const sourcesSchema = gql`
|
|||
timestamp: String
|
||||
}
|
||||
|
||||
"The result of a successful source creation"
|
||||
type CreateSourceResult {
|
||||
"The source that was created"
|
||||
source: InfraSource!
|
||||
"One of the log column types to display for this source"
|
||||
input UpdateSourceLogColumnInput {
|
||||
"A custom field log column"
|
||||
fieldColumn: UpdateSourceFieldLogColumnInput
|
||||
"A built-in message log column"
|
||||
messageColumn: UpdateSourceMessageLogColumnInput
|
||||
"A built-in timestamp log column"
|
||||
timestampColumn: UpdateSourceTimestampLogColumnInput
|
||||
}
|
||||
|
||||
"The update operations to be performed"
|
||||
input UpdateSourceInput {
|
||||
"The name update operation to be performed"
|
||||
setName: UpdateSourceNameInput
|
||||
"The description update operation to be performed"
|
||||
setDescription: UpdateSourceDescriptionInput
|
||||
"The alias update operation to be performed"
|
||||
setAliases: UpdateSourceAliasInput
|
||||
"The field update operation to be performed"
|
||||
setFields: UpdateSourceFieldsInput
|
||||
input UpdateSourceFieldLogColumnInput {
|
||||
id: ID!
|
||||
field: String!
|
||||
}
|
||||
|
||||
"A name update operation"
|
||||
input UpdateSourceNameInput {
|
||||
"The new name to be set"
|
||||
name: String!
|
||||
input UpdateSourceMessageLogColumnInput {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
"A description update operation"
|
||||
input UpdateSourceDescriptionInput {
|
||||
"The new description to be set"
|
||||
description: String!
|
||||
input UpdateSourceTimestampLogColumnInput {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
"An alias update operation"
|
||||
input UpdateSourceAliasInput {
|
||||
"The new log index pattern or alias to bet set"
|
||||
logAlias: String
|
||||
"The new metric index pattern or alias to bet set"
|
||||
metricAlias: String
|
||||
}
|
||||
|
||||
"A field update operations"
|
||||
input UpdateSourceFieldsInput {
|
||||
"The new container field to be set"
|
||||
container: String
|
||||
"The new host field to be set"
|
||||
host: String
|
||||
"The new pod field to be set"
|
||||
pod: String
|
||||
"The new tiebreaker field to be set"
|
||||
tiebreaker: String
|
||||
"The new timestamp field to be set"
|
||||
timestamp: String
|
||||
}
|
||||
|
||||
"The result of a sequence of source update operations"
|
||||
"The result of a successful source update"
|
||||
type UpdateSourceResult {
|
||||
"The source after the operations were performed"
|
||||
"The source that was updated"
|
||||
source: InfraSource!
|
||||
}
|
||||
|
||||
|
@ -168,13 +181,17 @@ export const sourcesSchema = gql`
|
|||
|
||||
extend type Mutation {
|
||||
"Create a new source of infrastructure data"
|
||||
createSource("The id of the source" id: ID!, source: CreateSourceInput!): CreateSourceResult!
|
||||
"Modify an existing source using the given sequence of update operations"
|
||||
createSource(
|
||||
"The id of the source"
|
||||
id: ID!
|
||||
sourceProperties: UpdateSourceInput!
|
||||
): UpdateSourceResult!
|
||||
"Modify an existing source"
|
||||
updateSource(
|
||||
"The id of the source"
|
||||
id: ID!
|
||||
"A sequence of update operations"
|
||||
changes: [UpdateSourceInput!]!
|
||||
"The properties to update the source with"
|
||||
sourceProperties: UpdateSourceInput!
|
||||
): UpdateSourceResult!
|
||||
"Delete a source of infrastructure data"
|
||||
deleteSource("The id of the source" id: ID!): DeleteSourceResult!
|
||||
|
|
|
@ -81,6 +81,8 @@ export interface InfraSourceConfiguration {
|
|||
logAlias: string;
|
||||
/** The field mapping to use for this source */
|
||||
fields: InfraSourceFields;
|
||||
/** The columns to use for log display */
|
||||
logColumns: InfraSourceLogColumn[];
|
||||
}
|
||||
/** A mapping of semantic fields to their document counterparts */
|
||||
export interface InfraSourceFields {
|
||||
|
@ -97,6 +99,35 @@ export interface InfraSourceFields {
|
|||
/** The field to use as a timestamp for metrics and logs */
|
||||
timestamp: string;
|
||||
}
|
||||
/** The built-in timestamp log column */
|
||||
export interface InfraSourceTimestampLogColumn {
|
||||
timestampColumn: InfraSourceTimestampLogColumnAttributes;
|
||||
}
|
||||
|
||||
export interface InfraSourceTimestampLogColumnAttributes {
|
||||
/** A unique id for the column */
|
||||
id: string;
|
||||
}
|
||||
/** The built-in message log column */
|
||||
export interface InfraSourceMessageLogColumn {
|
||||
messageColumn: InfraSourceMessageLogColumnAttributes;
|
||||
}
|
||||
|
||||
export interface InfraSourceMessageLogColumnAttributes {
|
||||
/** A unique id for the column */
|
||||
id: string;
|
||||
}
|
||||
/** A log column containing a field value */
|
||||
export interface InfraSourceFieldLogColumn {
|
||||
fieldColumn: InfraSourceFieldLogColumnAttributes;
|
||||
}
|
||||
|
||||
export interface InfraSourceFieldLogColumnAttributes {
|
||||
/** A unique id for the column */
|
||||
id: string;
|
||||
/** The field name this column refers to */
|
||||
field: string;
|
||||
}
|
||||
/** The status of an infrastructure data source */
|
||||
export interface InfraSourceStatus {
|
||||
/** Whether the configured metric alias exists */
|
||||
|
@ -171,6 +202,16 @@ export interface InfraLogEntry {
|
|||
gid: string;
|
||||
/** The source id */
|
||||
source: string;
|
||||
/** The columns used for rendering the log entry */
|
||||
columns: InfraLogEntryColumn[];
|
||||
}
|
||||
/** A special built-in column that contains the log entry's timestamp */
|
||||
export interface InfraLogEntryTimestampColumn {
|
||||
/** The timestamp */
|
||||
timestamp: number;
|
||||
}
|
||||
/** A special built-in column that contains the log entry's constructed message */
|
||||
export interface InfraLogEntryMessageColumn {
|
||||
/** A list of the formatted log entry segments */
|
||||
message: InfraLogMessageSegment[];
|
||||
}
|
||||
|
@ -183,11 +224,18 @@ export interface InfraLogMessageFieldSegment {
|
|||
/** A list of highlighted substrings of the value */
|
||||
highlights: string[];
|
||||
}
|
||||
/** A segment of the log entry message that was derived from a field */
|
||||
/** A segment of the log entry message that was derived from a string literal */
|
||||
export interface InfraLogMessageConstantSegment {
|
||||
/** The segment's message */
|
||||
constant: string;
|
||||
}
|
||||
/** A column that contains the value of a field of the log entry */
|
||||
export interface InfraLogEntryFieldColumn {
|
||||
/** The field name of the column */
|
||||
field: string;
|
||||
/** The value of the field in the log entry */
|
||||
value: string;
|
||||
}
|
||||
/** A consecutive sequence of log summary buckets */
|
||||
export interface InfraLogSummaryInterval {
|
||||
/** The millisecond timestamp corresponding to the start of the interval covered by the summary */
|
||||
|
@ -274,20 +322,15 @@ export interface InfraDataPoint {
|
|||
|
||||
export interface Mutation {
|
||||
/** Create a new source of infrastructure data */
|
||||
createSource: CreateSourceResult;
|
||||
/** Modify an existing source using the given sequence of update operations */
|
||||
createSource: UpdateSourceResult;
|
||||
/** Modify an existing source */
|
||||
updateSource: UpdateSourceResult;
|
||||
/** Delete a source of infrastructure data */
|
||||
deleteSource: DeleteSourceResult;
|
||||
}
|
||||
/** The result of a successful source creation */
|
||||
export interface CreateSourceResult {
|
||||
/** The source that was created */
|
||||
source: InfraSource;
|
||||
}
|
||||
/** The result of a sequence of source update operations */
|
||||
/** The result of a successful source update */
|
||||
export interface UpdateSourceResult {
|
||||
/** The source after the operations were performed */
|
||||
/** The source that was updated */
|
||||
source: InfraSource;
|
||||
}
|
||||
/** The result of a source deletion operations */
|
||||
|
@ -326,10 +369,10 @@ export interface InfraSnapshotMetricInput {
|
|||
/** The type of metric */
|
||||
type: InfraSnapshotMetricType;
|
||||
}
|
||||
/** The source to be created */
|
||||
export interface CreateSourceInput {
|
||||
/** The properties to update the source with */
|
||||
export interface UpdateSourceInput {
|
||||
/** The name of the data source */
|
||||
name: string;
|
||||
name?: string | null;
|
||||
/** A description of the data source */
|
||||
description?: string | null;
|
||||
/** The alias to read metric data from */
|
||||
|
@ -337,10 +380,12 @@ export interface CreateSourceInput {
|
|||
/** The alias to read log data from */
|
||||
logAlias?: string | null;
|
||||
/** The field mapping to use for this source */
|
||||
fields?: CreateSourceFieldsInput | null;
|
||||
fields?: UpdateSourceFieldsInput | null;
|
||||
/** The log columns to display for this source */
|
||||
logColumns?: UpdateSourceLogColumnInput[] | null;
|
||||
}
|
||||
/** The mapping of semantic fields of the source to be created */
|
||||
export interface CreateSourceFieldsInput {
|
||||
export interface UpdateSourceFieldsInput {
|
||||
/** The field to identify a container by */
|
||||
container?: string | null;
|
||||
/** The fields to identify a host by */
|
||||
|
@ -352,46 +397,28 @@ export interface CreateSourceFieldsInput {
|
|||
/** The field to use as a timestamp for metrics and logs */
|
||||
timestamp?: string | null;
|
||||
}
|
||||
/** The update operations to be performed */
|
||||
export interface UpdateSourceInput {
|
||||
/** The name update operation to be performed */
|
||||
setName?: UpdateSourceNameInput | null;
|
||||
/** The description update operation to be performed */
|
||||
setDescription?: UpdateSourceDescriptionInput | null;
|
||||
/** The alias update operation to be performed */
|
||||
setAliases?: UpdateSourceAliasInput | null;
|
||||
/** The field update operation to be performed */
|
||||
setFields?: UpdateSourceFieldsInput | null;
|
||||
/** One of the log column types to display for this source */
|
||||
export interface UpdateSourceLogColumnInput {
|
||||
/** A custom field log column */
|
||||
fieldColumn?: UpdateSourceFieldLogColumnInput | null;
|
||||
/** A built-in message log column */
|
||||
messageColumn?: UpdateSourceMessageLogColumnInput | null;
|
||||
/** A built-in timestamp log column */
|
||||
timestampColumn?: UpdateSourceTimestampLogColumnInput | null;
|
||||
}
|
||||
/** A name update operation */
|
||||
export interface UpdateSourceNameInput {
|
||||
/** The new name to be set */
|
||||
name: string;
|
||||
|
||||
export interface UpdateSourceFieldLogColumnInput {
|
||||
id: string;
|
||||
|
||||
field: string;
|
||||
}
|
||||
/** A description update operation */
|
||||
export interface UpdateSourceDescriptionInput {
|
||||
/** The new description to be set */
|
||||
description: string;
|
||||
|
||||
export interface UpdateSourceMessageLogColumnInput {
|
||||
id: string;
|
||||
}
|
||||
/** An alias update operation */
|
||||
export interface UpdateSourceAliasInput {
|
||||
/** The new log index pattern or alias to bet set */
|
||||
logAlias?: string | null;
|
||||
/** The new metric index pattern or alias to bet set */
|
||||
metricAlias?: string | null;
|
||||
}
|
||||
/** A field update operations */
|
||||
export interface UpdateSourceFieldsInput {
|
||||
/** The new container field to be set */
|
||||
container?: string | null;
|
||||
/** The new host field to be set */
|
||||
host?: string | null;
|
||||
/** The new pod field to be set */
|
||||
pod?: string | null;
|
||||
/** The new tiebreaker field to be set */
|
||||
tiebreaker?: string | null;
|
||||
/** The new timestamp field to be set */
|
||||
timestamp?: string | null;
|
||||
|
||||
export interface UpdateSourceTimestampLogColumnInput {
|
||||
id: string;
|
||||
}
|
||||
|
||||
// ====================================================
|
||||
|
@ -470,13 +497,13 @@ export interface CreateSourceMutationArgs {
|
|||
/** The id of the source */
|
||||
id: string;
|
||||
|
||||
source: CreateSourceInput;
|
||||
sourceProperties: UpdateSourceInput;
|
||||
}
|
||||
export interface UpdateSourceMutationArgs {
|
||||
/** The id of the source */
|
||||
id: string;
|
||||
/** A sequence of update operations */
|
||||
changes: UpdateSourceInput[];
|
||||
/** The properties to update the source with */
|
||||
sourceProperties: UpdateSourceInput;
|
||||
}
|
||||
export interface DeleteSourceMutationArgs {
|
||||
/** The id of the source */
|
||||
|
@ -543,6 +570,18 @@ export enum InfraMetric {
|
|||
// Unions
|
||||
// ====================================================
|
||||
|
||||
/** All known log column types */
|
||||
export type InfraSourceLogColumn =
|
||||
| InfraSourceTimestampLogColumn
|
||||
| InfraSourceMessageLogColumn
|
||||
| InfraSourceFieldLogColumn;
|
||||
|
||||
/** A column of a log entry */
|
||||
export type InfraLogEntryColumn =
|
||||
| InfraLogEntryTimestampColumn
|
||||
| InfraLogEntryMessageColumn
|
||||
| InfraLogEntryFieldColumn;
|
||||
|
||||
/** A segment of the log entry message */
|
||||
export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment;
|
||||
|
||||
|
@ -742,6 +781,8 @@ export namespace InfraSourceConfigurationResolvers {
|
|||
logAlias?: LogAliasResolver<string, TypeParent, Context>;
|
||||
/** The field mapping to use for this source */
|
||||
fields?: FieldsResolver<InfraSourceFields, TypeParent, Context>;
|
||||
/** The columns to use for log display */
|
||||
logColumns?: LogColumnsResolver<InfraSourceLogColumn[], TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type NameResolver<
|
||||
|
@ -769,6 +810,11 @@ export namespace InfraSourceConfigurationResolvers {
|
|||
Parent = InfraSourceConfiguration,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type LogColumnsResolver<
|
||||
R = InfraSourceLogColumn[],
|
||||
Parent = InfraSourceConfiguration,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** A mapping of semantic fields to their document counterparts */
|
||||
export namespace InfraSourceFieldsResolvers {
|
||||
|
@ -818,6 +864,105 @@ export namespace InfraSourceFieldsResolvers {
|
|||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** The built-in timestamp log column */
|
||||
export namespace InfraSourceTimestampLogColumnResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = InfraSourceTimestampLogColumn> {
|
||||
timestampColumn?: TimestampColumnResolver<
|
||||
InfraSourceTimestampLogColumnAttributes,
|
||||
TypeParent,
|
||||
Context
|
||||
>;
|
||||
}
|
||||
|
||||
export type TimestampColumnResolver<
|
||||
R = InfraSourceTimestampLogColumnAttributes,
|
||||
Parent = InfraSourceTimestampLogColumn,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
||||
export namespace InfraSourceTimestampLogColumnAttributesResolvers {
|
||||
export interface Resolvers<
|
||||
Context = InfraContext,
|
||||
TypeParent = InfraSourceTimestampLogColumnAttributes
|
||||
> {
|
||||
/** A unique id for the column */
|
||||
id?: IdResolver<string, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type IdResolver<
|
||||
R = string,
|
||||
Parent = InfraSourceTimestampLogColumnAttributes,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** The built-in message log column */
|
||||
export namespace InfraSourceMessageLogColumnResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = InfraSourceMessageLogColumn> {
|
||||
messageColumn?: MessageColumnResolver<
|
||||
InfraSourceMessageLogColumnAttributes,
|
||||
TypeParent,
|
||||
Context
|
||||
>;
|
||||
}
|
||||
|
||||
export type MessageColumnResolver<
|
||||
R = InfraSourceMessageLogColumnAttributes,
|
||||
Parent = InfraSourceMessageLogColumn,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
||||
export namespace InfraSourceMessageLogColumnAttributesResolvers {
|
||||
export interface Resolvers<
|
||||
Context = InfraContext,
|
||||
TypeParent = InfraSourceMessageLogColumnAttributes
|
||||
> {
|
||||
/** A unique id for the column */
|
||||
id?: IdResolver<string, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type IdResolver<
|
||||
R = string,
|
||||
Parent = InfraSourceMessageLogColumnAttributes,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** A log column containing a field value */
|
||||
export namespace InfraSourceFieldLogColumnResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = InfraSourceFieldLogColumn> {
|
||||
fieldColumn?: FieldColumnResolver<InfraSourceFieldLogColumnAttributes, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type FieldColumnResolver<
|
||||
R = InfraSourceFieldLogColumnAttributes,
|
||||
Parent = InfraSourceFieldLogColumn,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
||||
export namespace InfraSourceFieldLogColumnAttributesResolvers {
|
||||
export interface Resolvers<
|
||||
Context = InfraContext,
|
||||
TypeParent = InfraSourceFieldLogColumnAttributes
|
||||
> {
|
||||
/** A unique id for the column */
|
||||
id?: IdResolver<string, TypeParent, Context>;
|
||||
/** The field name this column refers to */
|
||||
field?: FieldResolver<string, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type IdResolver<
|
||||
R = string,
|
||||
Parent = InfraSourceFieldLogColumnAttributes,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type FieldResolver<
|
||||
R = string,
|
||||
Parent = InfraSourceFieldLogColumnAttributes,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** The status of an infrastructure data source */
|
||||
export namespace InfraSourceStatusResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = InfraSourceStatus> {
|
||||
|
@ -1039,8 +1184,8 @@ export namespace InfraLogEntryResolvers {
|
|||
gid?: GidResolver<string, TypeParent, Context>;
|
||||
/** The source id */
|
||||
source?: SourceResolver<string, TypeParent, Context>;
|
||||
/** A list of the formatted log entry segments */
|
||||
message?: MessageResolver<InfraLogMessageSegment[], TypeParent, Context>;
|
||||
/** The columns used for rendering the log entry */
|
||||
columns?: ColumnsResolver<InfraLogEntryColumn[], TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type KeyResolver<
|
||||
|
@ -1058,9 +1203,35 @@ export namespace InfraLogEntryResolvers {
|
|||
Parent,
|
||||
Context
|
||||
>;
|
||||
export type ColumnsResolver<
|
||||
R = InfraLogEntryColumn[],
|
||||
Parent = InfraLogEntry,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** A special built-in column that contains the log entry's timestamp */
|
||||
export namespace InfraLogEntryTimestampColumnResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = InfraLogEntryTimestampColumn> {
|
||||
/** The timestamp */
|
||||
timestamp?: TimestampResolver<number, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type TimestampResolver<
|
||||
R = number,
|
||||
Parent = InfraLogEntryTimestampColumn,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** A special built-in column that contains the log entry's constructed message */
|
||||
export namespace InfraLogEntryMessageColumnResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = InfraLogEntryMessageColumn> {
|
||||
/** A list of the formatted log entry segments */
|
||||
message?: MessageResolver<InfraLogMessageSegment[], TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type MessageResolver<
|
||||
R = InfraLogMessageSegment[],
|
||||
Parent = InfraLogEntry,
|
||||
Parent = InfraLogEntryMessageColumn,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
|
@ -1091,7 +1262,7 @@ export namespace InfraLogMessageFieldSegmentResolvers {
|
|||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** A segment of the log entry message that was derived from a field */
|
||||
/** A segment of the log entry message that was derived from a string literal */
|
||||
export namespace InfraLogMessageConstantSegmentResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = InfraLogMessageConstantSegment> {
|
||||
/** The segment's message */
|
||||
|
@ -1104,6 +1275,26 @@ export namespace InfraLogMessageConstantSegmentResolvers {
|
|||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** A column that contains the value of a field of the log entry */
|
||||
export namespace InfraLogEntryFieldColumnResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = InfraLogEntryFieldColumn> {
|
||||
/** The field name of the column */
|
||||
field?: FieldResolver<string, TypeParent, Context>;
|
||||
/** The value of the field in the log entry */
|
||||
value?: ValueResolver<string, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type FieldResolver<
|
||||
R = string,
|
||||
Parent = InfraLogEntryFieldColumn,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type ValueResolver<
|
||||
R = string,
|
||||
Parent = InfraLogEntryFieldColumn,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** A consecutive sequence of log summary buckets */
|
||||
export namespace InfraLogSummaryIntervalResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = InfraLogSummaryInterval> {
|
||||
|
@ -1371,15 +1562,15 @@ export namespace InfraDataPointResolvers {
|
|||
export namespace MutationResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = never> {
|
||||
/** Create a new source of infrastructure data */
|
||||
createSource?: CreateSourceResolver<CreateSourceResult, TypeParent, Context>;
|
||||
/** Modify an existing source using the given sequence of update operations */
|
||||
createSource?: CreateSourceResolver<UpdateSourceResult, TypeParent, Context>;
|
||||
/** Modify an existing source */
|
||||
updateSource?: UpdateSourceResolver<UpdateSourceResult, TypeParent, Context>;
|
||||
/** Delete a source of infrastructure data */
|
||||
deleteSource?: DeleteSourceResolver<DeleteSourceResult, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type CreateSourceResolver<
|
||||
R = CreateSourceResult,
|
||||
R = UpdateSourceResult,
|
||||
Parent = never,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context, CreateSourceArgs>;
|
||||
|
@ -1387,7 +1578,7 @@ export namespace MutationResolvers {
|
|||
/** The id of the source */
|
||||
id: string;
|
||||
|
||||
source: CreateSourceInput;
|
||||
sourceProperties: UpdateSourceInput;
|
||||
}
|
||||
|
||||
export type UpdateSourceResolver<
|
||||
|
@ -1398,8 +1589,8 @@ export namespace MutationResolvers {
|
|||
export interface UpdateSourceArgs {
|
||||
/** The id of the source */
|
||||
id: string;
|
||||
/** A sequence of update operations */
|
||||
changes: UpdateSourceInput[];
|
||||
/** The properties to update the source with */
|
||||
sourceProperties: UpdateSourceInput;
|
||||
}
|
||||
|
||||
export type DeleteSourceResolver<
|
||||
|
@ -1412,23 +1603,10 @@ export namespace MutationResolvers {
|
|||
id: string;
|
||||
}
|
||||
}
|
||||
/** The result of a successful source creation */
|
||||
export namespace CreateSourceResultResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = CreateSourceResult> {
|
||||
/** The source that was created */
|
||||
source?: SourceResolver<InfraSource, TypeParent, Context>;
|
||||
}
|
||||
|
||||
export type SourceResolver<
|
||||
R = InfraSource,
|
||||
Parent = CreateSourceResult,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
}
|
||||
/** The result of a sequence of source update operations */
|
||||
/** The result of a successful source update */
|
||||
export namespace UpdateSourceResultResolvers {
|
||||
export interface Resolvers<Context = InfraContext, TypeParent = UpdateSourceResult> {
|
||||
/** The source after the operations were performed */
|
||||
/** The source that was updated */
|
||||
source?: SourceResolver<InfraSource, TypeParent, Context>;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,20 +4,28 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import stringify from 'json-stable-stringify';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
import { TimeKey } from '../../../../common/time';
|
||||
import { JsonObject } from '../../../../common/typed_json';
|
||||
import { InfraLogItem } from '../../../graphql/types';
|
||||
import {
|
||||
InfraLogEntry,
|
||||
InfraLogItem,
|
||||
InfraLogMessageSegment,
|
||||
InfraLogSummaryBucket,
|
||||
} from '../../../graphql/types';
|
||||
import { InfraDateRangeAggregationBucket, InfraFrameworkRequest } from '../../adapters/framework';
|
||||
import { InfraSourceConfiguration, InfraSources } from '../../sources';
|
||||
import {
|
||||
InfraSourceConfiguration,
|
||||
InfraSources,
|
||||
SavedSourceConfigurationFieldColumnRuntimeType,
|
||||
SavedSourceConfigurationMessageColumnRuntimeType,
|
||||
SavedSourceConfigurationTimestampColumnRuntimeType,
|
||||
} from '../../sources';
|
||||
import { getBuiltinRules } from './builtin_rules';
|
||||
import { convertDocumentSourceToLogItemFields } from './convert_document_source_to_log_item_fields';
|
||||
import { compileFormattingRules } from './message';
|
||||
import { compileFormattingRules, CompiledLogMessageFormattingRule } from './message';
|
||||
|
||||
export class InfraLogEntriesDomain {
|
||||
constructor(
|
||||
|
@ -42,12 +50,15 @@ export class InfraLogEntriesDomain {
|
|||
}
|
||||
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const formattingRules = compileFormattingRules(getBuiltinRules(configuration.fields.message));
|
||||
const messageFormattingRules = compileFormattingRules(
|
||||
getBuiltinRules(configuration.fields.message)
|
||||
);
|
||||
const requiredFields = getRequiredFields(configuration, messageFormattingRules);
|
||||
|
||||
const documentsBefore = await this.adapter.getAdjacentLogEntryDocuments(
|
||||
request,
|
||||
configuration,
|
||||
formattingRules.requiredFields,
|
||||
requiredFields,
|
||||
key,
|
||||
'desc',
|
||||
Math.max(maxCountBefore, 1),
|
||||
|
@ -65,7 +76,7 @@ export class InfraLogEntriesDomain {
|
|||
const documentsAfter = await this.adapter.getAdjacentLogEntryDocuments(
|
||||
request,
|
||||
configuration,
|
||||
formattingRules.requiredFields,
|
||||
messageFormattingRules.requiredFields,
|
||||
lastKeyBefore,
|
||||
'asc',
|
||||
maxCountAfter,
|
||||
|
@ -75,9 +86,11 @@ export class InfraLogEntriesDomain {
|
|||
|
||||
return {
|
||||
entriesBefore: (maxCountBefore > 0 ? documentsBefore : []).map(
|
||||
convertLogDocumentToEntry(sourceId, formattingRules.format)
|
||||
convertLogDocumentToEntry(sourceId, configuration.logColumns, messageFormattingRules.format)
|
||||
),
|
||||
entriesAfter: documentsAfter.map(
|
||||
convertLogDocumentToEntry(sourceId, configuration.logColumns, messageFormattingRules.format)
|
||||
),
|
||||
entriesAfter: documentsAfter.map(convertLogDocumentToEntry(sourceId, formattingRules.format)),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -90,17 +103,22 @@ export class InfraLogEntriesDomain {
|
|||
highlightQuery?: string
|
||||
): Promise<InfraLogEntry[]> {
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const formattingRules = compileFormattingRules(getBuiltinRules(configuration.fields.message));
|
||||
const messageFormattingRules = compileFormattingRules(
|
||||
getBuiltinRules(configuration.fields.message)
|
||||
);
|
||||
const requiredFields = getRequiredFields(configuration, messageFormattingRules);
|
||||
const documents = await this.adapter.getContainedLogEntryDocuments(
|
||||
request,
|
||||
configuration,
|
||||
formattingRules.requiredFields,
|
||||
requiredFields,
|
||||
startKey,
|
||||
endKey,
|
||||
filterQuery,
|
||||
highlightQuery
|
||||
);
|
||||
const entries = documents.map(convertLogDocumentToEntry(sourceId, formattingRules.format));
|
||||
const entries = documents.map(
|
||||
convertLogDocumentToEntry(sourceId, configuration.logColumns, messageFormattingRules.format)
|
||||
);
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
@ -210,12 +228,28 @@ export interface LogEntryDocumentFields {
|
|||
|
||||
const convertLogDocumentToEntry = (
|
||||
sourceId: string,
|
||||
logColumns: InfraSourceConfiguration['logColumns'],
|
||||
formatLogMessage: (fields: LogEntryDocumentFields) => InfraLogMessageSegment[]
|
||||
) => (document: LogEntryDocument): InfraLogEntry => ({
|
||||
key: document.key,
|
||||
gid: document.gid,
|
||||
source: sourceId,
|
||||
message: formatLogMessage(document.fields),
|
||||
columns: logColumns.map(logColumn => {
|
||||
if (SavedSourceConfigurationTimestampColumnRuntimeType.is(logColumn)) {
|
||||
return {
|
||||
timestamp: document.key.time,
|
||||
};
|
||||
} else if (SavedSourceConfigurationMessageColumnRuntimeType.is(logColumn)) {
|
||||
return {
|
||||
message: formatLogMessage(document.fields),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
field: logColumn.fieldColumn.field,
|
||||
value: stringify(document.fields[logColumn.fieldColumn.field] || null),
|
||||
};
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
const convertDateRangeBucketToSummaryBucket = (
|
||||
|
@ -225,3 +259,21 @@ const convertDateRangeBucketToSummaryBucket = (
|
|||
start: bucket.from || 0,
|
||||
end: bucket.to || 0,
|
||||
});
|
||||
|
||||
const getRequiredFields = (
|
||||
configuration: InfraSourceConfiguration,
|
||||
messageFormattingRules: CompiledLogMessageFormattingRule
|
||||
): string[] => {
|
||||
const fieldsFromCustomColumns = configuration.logColumns.reduce<string[]>(
|
||||
(accumulatedFields, logColumn) => {
|
||||
if (SavedSourceConfigurationFieldColumnRuntimeType.is(logColumn)) {
|
||||
return [...accumulatedFields, logColumn.fieldColumn.field];
|
||||
}
|
||||
return accumulatedFields;
|
||||
},
|
||||
[]
|
||||
);
|
||||
const fieldsFromFormattingRules = messageFormattingRules.requiredFields;
|
||||
|
||||
return Array.from(new Set([...fieldsFromCustomColumns, ...fieldsFromFormattingRules]));
|
||||
};
|
||||
|
|
|
@ -13,7 +13,9 @@ import {
|
|||
LogMessageFormattingRule,
|
||||
} from './rule_types';
|
||||
|
||||
export function compileFormattingRules(rules: LogMessageFormattingRule[]) {
|
||||
export function compileFormattingRules(
|
||||
rules: LogMessageFormattingRule[]
|
||||
): CompiledLogMessageFormattingRule {
|
||||
const compiledRules = rules.map(compileRule);
|
||||
|
||||
return {
|
||||
|
@ -28,7 +30,7 @@ export function compileFormattingRules(rules: LogMessageFormattingRule[]) {
|
|||
)
|
||||
)
|
||||
),
|
||||
format: (fields: Fields): InfraLogMessageSegment[] => {
|
||||
format(fields): InfraLogMessageSegment[] {
|
||||
for (const compiledRule of compiledRules) {
|
||||
if (compiledRule.fulfillsCondition(fields)) {
|
||||
return compiledRule.format(fields);
|
||||
|
@ -37,6 +39,9 @@ export function compileFormattingRules(rules: LogMessageFormattingRule[]) {
|
|||
|
||||
return [];
|
||||
},
|
||||
fulfillsCondition() {
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -163,18 +168,18 @@ interface Fields {
|
|||
[fieldName: string]: string | number | object | boolean | null;
|
||||
}
|
||||
|
||||
interface CompiledLogMessageFormattingRule {
|
||||
export interface CompiledLogMessageFormattingRule {
|
||||
requiredFields: string[];
|
||||
fulfillsCondition(fields: Fields): boolean;
|
||||
format(fields: Fields): InfraLogMessageSegment[];
|
||||
}
|
||||
|
||||
interface CompiledLogMessageFormattingCondition {
|
||||
export interface CompiledLogMessageFormattingCondition {
|
||||
conditionFields: string[];
|
||||
fulfillsCondition(fields: Fields): boolean;
|
||||
}
|
||||
|
||||
interface CompiledLogMessageFormattingInstruction {
|
||||
export interface CompiledLogMessageFormattingInstruction {
|
||||
formattingFields: string[];
|
||||
format(fields: Fields): InfraLogMessageSegment[];
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const defaultSourceConfiguration = {
|
||||
import { InfraSourceConfiguration } from './types';
|
||||
|
||||
export const defaultSourceConfiguration: InfraSourceConfiguration = {
|
||||
name: 'Default',
|
||||
description: '',
|
||||
metricAlias: 'metricbeat-*',
|
||||
|
@ -17,4 +19,22 @@ export const defaultSourceConfiguration = {
|
|||
tiebreaker: '_doc',
|
||||
timestamp: '@timestamp',
|
||||
},
|
||||
logColumns: [
|
||||
{
|
||||
timestampColumn: {
|
||||
id: '5e7f964a-be8a-40d8-88d2-fbcfbdca0e2f',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldColumn: {
|
||||
id: ' eb9777a8-fcd3-420e-ba7d-172fff6da7a2',
|
||||
field: 'event.dataset',
|
||||
},
|
||||
},
|
||||
{
|
||||
messageColumn: {
|
||||
id: 'b645d6da-824b-4723-9a2a-e8cece1645c0',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -45,6 +45,35 @@ export const infraSourceConfigurationSavedObjectMappings: {
|
|||
},
|
||||
},
|
||||
},
|
||||
logColumns: {
|
||||
type: 'nested',
|
||||
properties: {
|
||||
timestampColumn: {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
messageColumn: {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
fieldColumn: {
|
||||
properties: {
|
||||
id: {
|
||||
type: 'keyword',
|
||||
},
|
||||
field: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -87,7 +87,7 @@ export class InfraSources {
|
|||
.getScopedSavedObjectsClient(request[internalInfraFrameworkRequest])
|
||||
.create(
|
||||
infraSourceConfigurationSavedObjectType,
|
||||
pickSavedSourceConfiguration(newSourceConfiguration),
|
||||
pickSavedSourceConfiguration(newSourceConfiguration) as any,
|
||||
{ id: sourceId }
|
||||
)
|
||||
);
|
||||
|
@ -110,15 +110,15 @@ export class InfraSources {
|
|||
public async updateSourceConfiguration(
|
||||
request: InfraFrameworkRequest,
|
||||
sourceId: string,
|
||||
updaters: Array<(configuration: InfraSourceConfiguration) => InfraSourceConfiguration>
|
||||
sourceProperties: InfraSavedSourceConfiguration
|
||||
) {
|
||||
const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration();
|
||||
|
||||
const { configuration, version } = await this.getSourceConfiguration(request, sourceId);
|
||||
|
||||
const updatedConfigurationAttributes = updaters.reduce(
|
||||
(accumulatedConfiguration, updater) => updater(accumulatedConfiguration),
|
||||
configuration
|
||||
const updatedSourceConfigurationAttributes = mergeSourceConfiguration(
|
||||
configuration,
|
||||
sourceProperties
|
||||
);
|
||||
|
||||
const updatedSourceConfiguration = convertSavedObjectToSavedSourceConfiguration(
|
||||
|
@ -127,7 +127,7 @@ export class InfraSources {
|
|||
.update(
|
||||
infraSourceConfigurationSavedObjectType,
|
||||
sourceId,
|
||||
pickSavedSourceConfiguration(updatedConfigurationAttributes),
|
||||
pickSavedSourceConfiguration(updatedSourceConfigurationAttributes) as any,
|
||||
{
|
||||
version,
|
||||
}
|
||||
|
|
|
@ -34,23 +34,56 @@ const SavedSourceConfigurationFieldsRuntimeType = runtimeTypes.partial({
|
|||
timestamp: runtimeTypes.string,
|
||||
});
|
||||
|
||||
export const SavedSourceConfigurationTimestampColumnRuntimeType = runtimeTypes.type({
|
||||
timestampColumn: runtimeTypes.type({
|
||||
id: runtimeTypes.string,
|
||||
}),
|
||||
});
|
||||
|
||||
export const SavedSourceConfigurationMessageColumnRuntimeType = runtimeTypes.type({
|
||||
messageColumn: runtimeTypes.type({
|
||||
id: runtimeTypes.string,
|
||||
}),
|
||||
});
|
||||
|
||||
export const SavedSourceConfigurationFieldColumnRuntimeType = runtimeTypes.type({
|
||||
fieldColumn: runtimeTypes.type({
|
||||
id: runtimeTypes.string,
|
||||
field: runtimeTypes.string,
|
||||
}),
|
||||
});
|
||||
|
||||
export const SavedSourceConfigurationColumnRuntimeType = runtimeTypes.union([
|
||||
SavedSourceConfigurationTimestampColumnRuntimeType,
|
||||
SavedSourceConfigurationMessageColumnRuntimeType,
|
||||
SavedSourceConfigurationFieldColumnRuntimeType,
|
||||
]);
|
||||
|
||||
export const SavedSourceConfigurationRuntimeType = runtimeTypes.partial({
|
||||
name: runtimeTypes.string,
|
||||
description: runtimeTypes.string,
|
||||
metricAlias: runtimeTypes.string,
|
||||
logAlias: runtimeTypes.string,
|
||||
fields: SavedSourceConfigurationFieldsRuntimeType,
|
||||
logColumns: runtimeTypes.array(SavedSourceConfigurationColumnRuntimeType),
|
||||
});
|
||||
|
||||
export interface InfraSavedSourceConfiguration
|
||||
extends runtimeTypes.TypeOf<typeof SavedSourceConfigurationRuntimeType> {}
|
||||
|
||||
export const pickSavedSourceConfiguration = (value: InfraSourceConfiguration) => {
|
||||
const { container, host, pod, tiebreaker, timestamp } = value.fields;
|
||||
export const pickSavedSourceConfiguration = (
|
||||
value: InfraSourceConfiguration
|
||||
): InfraSavedSourceConfiguration => {
|
||||
const { name, description, metricAlias, logAlias, fields, logColumns } = value;
|
||||
const { container, host, pod, tiebreaker, timestamp } = fields;
|
||||
|
||||
return {
|
||||
...value,
|
||||
name,
|
||||
description,
|
||||
metricAlias,
|
||||
logAlias,
|
||||
fields: { container, host, pod, tiebreaker, timestamp },
|
||||
logColumns,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -69,6 +102,7 @@ export const StaticSourceConfigurationRuntimeType = runtimeTypes.partial({
|
|||
metricAlias: runtimeTypes.string,
|
||||
logAlias: runtimeTypes.string,
|
||||
fields: StaticSourceConfigurationFieldsRuntimeType,
|
||||
logColumns: runtimeTypes.array(SavedSourceConfigurationColumnRuntimeType),
|
||||
});
|
||||
|
||||
export interface InfraStaticSourceConfiguration
|
||||
|
@ -85,6 +119,7 @@ const SourceConfigurationFieldsRuntimeType = runtimeTypes.type({
|
|||
export const SourceConfigurationRuntimeType = runtimeTypes.type({
|
||||
...SavedSourceConfigurationRuntimeType.props,
|
||||
fields: SourceConfigurationFieldsRuntimeType,
|
||||
logColumns: runtimeTypes.array(SavedSourceConfigurationColumnRuntimeType),
|
||||
});
|
||||
|
||||
export interface InfraSourceConfiguration
|
||||
|
|
|
@ -8,6 +8,8 @@ export type ElasticsearchMappingOf<Type> = Type extends string
|
|||
? ElasticsearchStringFieldMapping
|
||||
: Type extends number
|
||||
? ElasticsearchNumberFieldMapping
|
||||
: Type extends object[]
|
||||
? ElasticsearchNestedFieldMapping<Type>
|
||||
: Type extends {}
|
||||
? ElasticsearchObjectFieldMapping<Type>
|
||||
: never;
|
||||
|
@ -29,6 +31,11 @@ export interface ElasticsearchNumberFieldMapping {
|
|||
| 'date';
|
||||
}
|
||||
|
||||
export interface ElasticsearchNestedFieldMapping<Obj extends object[]> {
|
||||
type?: 'nested';
|
||||
properties: { [K in keyof Obj[0]]-?: ElasticsearchMappingOf<Obj[0][K]> };
|
||||
}
|
||||
|
||||
export interface ElasticsearchObjectFieldMapping<Obj extends {}> {
|
||||
type?: 'object';
|
||||
properties: { [K in keyof Obj]-?: ElasticsearchMappingOf<Obj[K]> };
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
import expect from '@kbn/expect';
|
||||
import { ascending, pairs } from 'd3-array';
|
||||
import gql from 'graphql-tag';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { sharedFragments } from '../../../../plugins/infra/common/graphql/shared';
|
||||
import { InfraTimeKey } from '../../../../plugins/infra/public/graphql/types';
|
||||
import { KbnTestProvider } from './types';
|
||||
|
||||
|
@ -40,34 +42,22 @@ const logEntriesAroundQuery = gql`
|
|||
filterQuery: $filterQuery
|
||||
) {
|
||||
start {
|
||||
time
|
||||
tiebreaker
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
end {
|
||||
time
|
||||
tiebreaker
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
hasMoreBefore
|
||||
hasMoreAfter
|
||||
entries {
|
||||
gid
|
||||
key {
|
||||
time
|
||||
tiebreaker
|
||||
}
|
||||
message {
|
||||
... on InfraLogMessageFieldSegment {
|
||||
field
|
||||
value
|
||||
}
|
||||
... on InfraLogMessageConstantSegment {
|
||||
constant
|
||||
}
|
||||
}
|
||||
...InfraLogEntryFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraTimeKey}
|
||||
${sharedFragments.InfraLogEntryFields}
|
||||
`;
|
||||
|
||||
const logEntriesBetweenQuery = gql`
|
||||
|
@ -80,165 +70,266 @@ const logEntriesBetweenQuery = gql`
|
|||
id
|
||||
logEntriesBetween(startKey: $startKey, endKey: $endKey, filterQuery: $filterQuery) {
|
||||
start {
|
||||
time
|
||||
tiebreaker
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
end {
|
||||
time
|
||||
tiebreaker
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
hasMoreBefore
|
||||
hasMoreAfter
|
||||
entries {
|
||||
gid
|
||||
key {
|
||||
time
|
||||
tiebreaker
|
||||
}
|
||||
message {
|
||||
... on InfraLogMessageFieldSegment {
|
||||
field
|
||||
value
|
||||
}
|
||||
... on InfraLogMessageConstantSegment {
|
||||
constant
|
||||
}
|
||||
}
|
||||
...InfraLogEntryFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraTimeKey}
|
||||
${sharedFragments.InfraLogEntryFields}
|
||||
`;
|
||||
|
||||
const logEntriesTests: KbnTestProvider = ({ getService }) => {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const client = getService('infraOpsGraphQLClient');
|
||||
const sourceConfigurationService = getService('infraOpsSourceConfiguration');
|
||||
|
||||
describe('log entry apis', () => {
|
||||
before(() => esArchiver.load('infra/metrics_and_logs'));
|
||||
after(() => esArchiver.unload('infra/metrics_and_logs'));
|
||||
|
||||
describe('logEntriesAround', () => {
|
||||
it('should return newer and older log entries when present', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesAround },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: KEY_WITHIN_DATA_RANGE,
|
||||
countBefore: 100,
|
||||
countAfter: 100,
|
||||
},
|
||||
describe('with the default source', () => {
|
||||
before(() => esArchiver.load('empty_kibana'));
|
||||
after(() => esArchiver.unload('empty_kibana'));
|
||||
|
||||
it('should return newer and older log entries when present', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesAround },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: KEY_WITHIN_DATA_RANGE,
|
||||
countBefore: 100,
|
||||
countAfter: 100,
|
||||
},
|
||||
});
|
||||
|
||||
expect(logEntriesAround).to.have.property('entries');
|
||||
expect(logEntriesAround.entries).to.have.length(200);
|
||||
expect(isSorted(ascendingTimeKey)(logEntriesAround.entries)).to.equal(true);
|
||||
|
||||
expect(logEntriesAround.hasMoreBefore).to.equal(true);
|
||||
expect(logEntriesAround.hasMoreAfter).to.equal(true);
|
||||
});
|
||||
|
||||
expect(logEntriesAround).to.have.property('entries');
|
||||
expect(logEntriesAround.entries).to.have.length(200);
|
||||
expect(isSorted(ascendingTimeKey)(logEntriesAround.entries)).to.equal(true);
|
||||
it('should indicate if no older entries are present', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesAround },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: EARLIEST_KEY_WITH_DATA,
|
||||
countBefore: 100,
|
||||
countAfter: 100,
|
||||
},
|
||||
});
|
||||
|
||||
expect(logEntriesAround.hasMoreBefore).to.equal(true);
|
||||
expect(logEntriesAround.hasMoreAfter).to.equal(true);
|
||||
expect(logEntriesAround.hasMoreBefore).to.equal(false);
|
||||
expect(logEntriesAround.hasMoreAfter).to.equal(true);
|
||||
});
|
||||
|
||||
it('should indicate if no newer entries are present', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesAround },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: LATEST_KEY_WITH_DATA,
|
||||
countBefore: 100,
|
||||
countAfter: 100,
|
||||
},
|
||||
});
|
||||
|
||||
expect(logEntriesAround.hasMoreBefore).to.equal(true);
|
||||
expect(logEntriesAround.hasMoreAfter).to.equal(false);
|
||||
});
|
||||
|
||||
it('should return the default columns', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: {
|
||||
logEntriesAround: {
|
||||
entries: [entry],
|
||||
},
|
||||
},
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: KEY_WITHIN_DATA_RANGE,
|
||||
countAfter: 1,
|
||||
},
|
||||
});
|
||||
|
||||
expect(entry.columns).to.have.length(3);
|
||||
expect(entry.columns[0]).to.have.property('timestamp');
|
||||
expect(entry.columns[0].timestamp).to.be.a('number');
|
||||
expect(entry.columns[1]).to.have.property('field');
|
||||
expect(entry.columns[1].field).to.be('event.dataset');
|
||||
expect(entry.columns[1]).to.have.property('value');
|
||||
expect(JSON.parse)
|
||||
.withArgs(entry.columns[1].value)
|
||||
.to.not.throwException();
|
||||
expect(entry.columns[2]).to.have.property('message');
|
||||
expect(entry.columns[2].message).to.be.an('array');
|
||||
expect(entry.columns[2].message.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should indicate if no older entries are present', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesAround },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: EARLIEST_KEY_WITH_DATA,
|
||||
countBefore: 100,
|
||||
countAfter: 100,
|
||||
},
|
||||
describe('with a configured source', () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('empty_kibana');
|
||||
await sourceConfigurationService.createConfiguration('default', {
|
||||
name: 'Test Source',
|
||||
logColumns: [
|
||||
{
|
||||
timestampColumn: {
|
||||
id: uuidv4(),
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldColumn: {
|
||||
id: uuidv4(),
|
||||
field: 'host.name',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldColumn: {
|
||||
id: uuidv4(),
|
||||
field: 'event.dataset',
|
||||
},
|
||||
},
|
||||
{
|
||||
messageColumn: {
|
||||
id: uuidv4(),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
after(() => esArchiver.unload('empty_kibana'));
|
||||
|
||||
expect(logEntriesAround.hasMoreBefore).to.equal(false);
|
||||
expect(logEntriesAround.hasMoreAfter).to.equal(true);
|
||||
});
|
||||
it('should return the configured columns', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: {
|
||||
logEntriesAround: {
|
||||
entries: [entry],
|
||||
},
|
||||
},
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: KEY_WITHIN_DATA_RANGE,
|
||||
countAfter: 1,
|
||||
},
|
||||
});
|
||||
|
||||
it('should indicate if no newer entries are present', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesAround },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: LATEST_KEY_WITH_DATA,
|
||||
countBefore: 100,
|
||||
countAfter: 100,
|
||||
},
|
||||
expect(entry.columns).to.have.length(4);
|
||||
expect(entry.columns[0]).to.have.property('timestamp');
|
||||
expect(entry.columns[0].timestamp).to.be.a('number');
|
||||
expect(entry.columns[1]).to.have.property('field');
|
||||
expect(entry.columns[1].field).to.be('host.name');
|
||||
expect(entry.columns[1]).to.have.property('value');
|
||||
expect(JSON.parse)
|
||||
.withArgs(entry.columns[1].value)
|
||||
.to.not.throwException();
|
||||
expect(entry.columns[2]).to.have.property('field');
|
||||
expect(entry.columns[2].field).to.be('event.dataset');
|
||||
expect(entry.columns[2]).to.have.property('value');
|
||||
expect(JSON.parse)
|
||||
.withArgs(entry.columns[2].value)
|
||||
.to.not.throwException();
|
||||
expect(entry.columns[3]).to.have.property('message');
|
||||
expect(entry.columns[3].message).to.be.an('array');
|
||||
expect(entry.columns[3].message.length).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
expect(logEntriesAround.hasMoreBefore).to.equal(true);
|
||||
expect(logEntriesAround.hasMoreAfter).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logEntriesBetween', () => {
|
||||
it('should return log entries between the start and end keys', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesBetween },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesBetweenQuery,
|
||||
variables: {
|
||||
startKey: EARLIEST_KEY_WITH_DATA,
|
||||
endKey: KEY_WITHIN_DATA_RANGE,
|
||||
},
|
||||
});
|
||||
describe('with the default source', () => {
|
||||
before(() => esArchiver.load('empty_kibana'));
|
||||
after(() => esArchiver.unload('empty_kibana'));
|
||||
|
||||
expect(logEntriesBetween).to.have.property('entries');
|
||||
expect(logEntriesBetween.entries).to.not.be.empty();
|
||||
expect(isSorted(ascendingTimeKey)(logEntriesBetween.entries)).to.equal(true);
|
||||
|
||||
expect(
|
||||
ascendingTimeKey(logEntriesBetween.entries[0], { key: EARLIEST_KEY_WITH_DATA })
|
||||
).to.be.above(-1);
|
||||
expect(
|
||||
ascendingTimeKey(logEntriesBetween.entries[logEntriesBetween.entries.length - 1], {
|
||||
key: KEY_WITHIN_DATA_RANGE,
|
||||
})
|
||||
).to.be.below(1);
|
||||
});
|
||||
|
||||
it('should return results consistent with logEntriesAround', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesAround },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: KEY_WITHIN_DATA_RANGE,
|
||||
countBefore: 100,
|
||||
countAfter: 100,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesBetween },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesBetweenQuery,
|
||||
variables: {
|
||||
startKey: {
|
||||
time: logEntriesAround.start.time,
|
||||
tiebreaker: logEntriesAround.start.tiebreaker - 1,
|
||||
it('should return log entries between the start and end keys', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesBetween },
|
||||
},
|
||||
endKey: {
|
||||
time: logEntriesAround.end.time,
|
||||
tiebreaker: logEntriesAround.end.tiebreaker + 1,
|
||||
} = await client.query<any>({
|
||||
query: logEntriesBetweenQuery,
|
||||
variables: {
|
||||
startKey: EARLIEST_KEY_WITH_DATA,
|
||||
endKey: KEY_WITHIN_DATA_RANGE,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(logEntriesBetween).to.have.property('entries');
|
||||
expect(logEntriesBetween.entries).to.not.be.empty();
|
||||
expect(isSorted(ascendingTimeKey)(logEntriesBetween.entries)).to.equal(true);
|
||||
|
||||
expect(
|
||||
ascendingTimeKey(logEntriesBetween.entries[0], { key: EARLIEST_KEY_WITH_DATA })
|
||||
).to.be.above(-1);
|
||||
expect(
|
||||
ascendingTimeKey(logEntriesBetween.entries[logEntriesBetween.entries.length - 1], {
|
||||
key: KEY_WITHIN_DATA_RANGE,
|
||||
})
|
||||
).to.be.below(1);
|
||||
});
|
||||
|
||||
expect(logEntriesBetween).to.eql(logEntriesAround);
|
||||
it('should return results consistent with logEntriesAround', async () => {
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesAround },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesAroundQuery,
|
||||
variables: {
|
||||
timeKey: KEY_WITHIN_DATA_RANGE,
|
||||
countBefore: 100,
|
||||
countAfter: 100,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: {
|
||||
source: { logEntriesBetween },
|
||||
},
|
||||
} = await client.query<any>({
|
||||
query: logEntriesBetweenQuery,
|
||||
variables: {
|
||||
startKey: {
|
||||
time: logEntriesAround.start.time,
|
||||
tiebreaker: logEntriesAround.start.tiebreaker - 1,
|
||||
},
|
||||
endKey: {
|
||||
time: logEntriesAround.end.time,
|
||||
tiebreaker: logEntriesAround.end.tiebreaker + 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(logEntriesBetween).to.eql(logEntriesAround);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ import expect from '@kbn/expect';
|
|||
import { ascending, pairs } from 'd3-array';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { sharedFragments } from '../../../../plugins/infra/common/graphql/shared';
|
||||
import { InfraTimeKey } from '../../../../plugins/infra/public/graphql/types';
|
||||
import { KbnTestProvider } from './types';
|
||||
|
||||
|
@ -117,34 +118,22 @@ const logEntriesAroundQuery = gql`
|
|||
filterQuery: $filterQuery
|
||||
) {
|
||||
start {
|
||||
time
|
||||
tiebreaker
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
end {
|
||||
time
|
||||
tiebreaker
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
hasMoreBefore
|
||||
hasMoreAfter
|
||||
entries {
|
||||
gid
|
||||
key {
|
||||
time
|
||||
tiebreaker
|
||||
}
|
||||
message {
|
||||
... on InfraLogMessageFieldSegment {
|
||||
field
|
||||
value
|
||||
}
|
||||
... on InfraLogMessageConstantSegment {
|
||||
constant
|
||||
}
|
||||
}
|
||||
...InfraLogEntryFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraTimeKey}
|
||||
${sharedFragments.InfraLogEntryFields}
|
||||
`;
|
||||
|
||||
const logEntriesBetweenQuery = gql`
|
||||
|
@ -157,34 +146,22 @@ const logEntriesBetweenQuery = gql`
|
|||
id
|
||||
logEntriesBetween(startKey: $startKey, endKey: $endKey, filterQuery: $filterQuery) {
|
||||
start {
|
||||
time
|
||||
tiebreaker
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
end {
|
||||
time
|
||||
tiebreaker
|
||||
...InfraTimeKeyFields
|
||||
}
|
||||
hasMoreBefore
|
||||
hasMoreAfter
|
||||
entries {
|
||||
gid
|
||||
key {
|
||||
time
|
||||
tiebreaker
|
||||
}
|
||||
message {
|
||||
... on InfraLogMessageFieldSegment {
|
||||
field
|
||||
value
|
||||
}
|
||||
... on InfraLogMessageConstantSegment {
|
||||
constant
|
||||
}
|
||||
}
|
||||
...InfraLogEntryFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraTimeKey}
|
||||
${sharedFragments.InfraLogEntryFields}
|
||||
`;
|
||||
|
||||
const logSummaryBetweenQuery = gql`
|
||||
|
|
|
@ -8,8 +8,13 @@ import expect from '@kbn/expect';
|
|||
import gql from 'graphql-tag';
|
||||
|
||||
import { sourceQuery } from '../../../../plugins/infra/public/containers/source/query_source.gql_query';
|
||||
import {
|
||||
sourceConfigurationFieldsFragment,
|
||||
sourceStatusFieldsFragment,
|
||||
} from '../../../../plugins/infra/public/containers/source/source_fields_fragment.gql_query';
|
||||
import { SourceQuery } from '../../../../plugins/infra/public/graphql/types';
|
||||
import { KbnTestProvider } from './types';
|
||||
import { sharedFragments } from '../../../../plugins/infra/common/graphql/shared';
|
||||
|
||||
const sourcesTests: KbnTestProvider = ({ getService }) => {
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
@ -40,6 +45,10 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
expect(sourceConfiguration.fields.container).to.be('container.id');
|
||||
expect(sourceConfiguration.fields.host).to.be('host.name');
|
||||
expect(sourceConfiguration.fields.pod).to.be('kubernetes.pod.uid');
|
||||
expect(sourceConfiguration.logColumns).to.have.length(3);
|
||||
expect(sourceConfiguration.logColumns[0]).to.have.key('timestampColumn');
|
||||
expect(sourceConfiguration.logColumns[1]).to.have.key('fieldColumn');
|
||||
expect(sourceConfiguration.logColumns[2]).to.have.key('messageColumn');
|
||||
|
||||
// test data in x-pack/test/functional/es_archives/infra/data.json.gz
|
||||
expect(sourceStatus.indexFields.length).to.be(1765);
|
||||
|
@ -53,7 +62,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const response = await client.mutate<any>({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
source: {
|
||||
sourceProperties: {
|
||||
name: 'NAME',
|
||||
description: 'DESCRIPTION',
|
||||
logAlias: 'filebeat-**',
|
||||
|
@ -65,6 +74,13 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
tiebreaker: 'TIEBREAKER',
|
||||
timestamp: 'TIMESTAMP',
|
||||
},
|
||||
logColumns: [
|
||||
{
|
||||
messageColumn: {
|
||||
id: 'MESSAGE_COLUMN',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
sourceId: 'default',
|
||||
},
|
||||
|
@ -84,6 +100,9 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
expect(configuration.fields.pod).to.be('POD');
|
||||
expect(configuration.fields.tiebreaker).to.be('TIEBREAKER');
|
||||
expect(configuration.fields.timestamp).to.be('TIMESTAMP');
|
||||
expect(configuration.logColumns).to.have.length(1);
|
||||
expect(configuration.logColumns[0]).to.have.key('messageColumn');
|
||||
|
||||
expect(status.logIndicesExist).to.be(true);
|
||||
expect(status.metricIndicesExist).to.be(true);
|
||||
});
|
||||
|
@ -92,7 +111,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const response = await client.mutate<any>({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
source: {
|
||||
sourceProperties: {
|
||||
name: 'NAME',
|
||||
},
|
||||
sourceId: 'default',
|
||||
|
@ -113,6 +132,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
expect(configuration.fields.pod).to.be('kubernetes.pod.uid');
|
||||
expect(configuration.fields.tiebreaker).to.be('_doc');
|
||||
expect(configuration.fields.timestamp).to.be('@timestamp');
|
||||
expect(configuration.logColumns).to.have.length(3);
|
||||
expect(status.logIndicesExist).to.be(true);
|
||||
expect(status.metricIndicesExist).to.be(true);
|
||||
});
|
||||
|
@ -121,7 +141,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
await client.mutate<any>({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
source: {
|
||||
sourceProperties: {
|
||||
name: 'NAME',
|
||||
},
|
||||
sourceId: 'default',
|
||||
|
@ -132,7 +152,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
.mutate<any>({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
source: {
|
||||
sourceProperties: {
|
||||
name: 'NAME',
|
||||
},
|
||||
sourceId: 'default',
|
||||
|
@ -154,7 +174,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const creationResponse = await client.mutate<any>({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
source: {
|
||||
sourceProperties: {
|
||||
name: 'NAME',
|
||||
},
|
||||
sourceId: 'default',
|
||||
|
@ -179,11 +199,11 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
});
|
||||
|
||||
describe('updateSource mutation', () => {
|
||||
it('applies multiple updates to an existing source', async () => {
|
||||
it('applies all top-level field updates to an existing source', async () => {
|
||||
const creationResponse = await client.mutate<any>({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
source: {
|
||||
sourceProperties: {
|
||||
name: 'NAME',
|
||||
},
|
||||
sourceId: 'default',
|
||||
|
@ -200,33 +220,12 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
mutation: updateSourceMutation,
|
||||
variables: {
|
||||
sourceId: 'default',
|
||||
changes: [
|
||||
{
|
||||
setName: {
|
||||
name: 'UPDATED_NAME',
|
||||
},
|
||||
},
|
||||
{
|
||||
setDescription: {
|
||||
description: 'UPDATED_DESCRIPTION',
|
||||
},
|
||||
},
|
||||
{
|
||||
setAliases: {
|
||||
logAlias: 'filebeat-**',
|
||||
metricAlias: 'metricbeat-**',
|
||||
},
|
||||
},
|
||||
{
|
||||
setFields: {
|
||||
container: 'UPDATED_CONTAINER',
|
||||
host: 'UPDATED_HOST',
|
||||
pod: 'UPDATED_POD',
|
||||
tiebreaker: 'UPDATED_TIEBREAKER',
|
||||
timestamp: 'UPDATED_TIMESTAMP',
|
||||
},
|
||||
},
|
||||
],
|
||||
sourceProperties: {
|
||||
name: 'UPDATED_NAME',
|
||||
description: 'UPDATED_DESCRIPTION',
|
||||
metricAlias: 'metricbeat-**',
|
||||
logAlias: 'filebeat-**',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -240,20 +239,21 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
expect(configuration.description).to.be('UPDATED_DESCRIPTION');
|
||||
expect(configuration.metricAlias).to.be('metricbeat-**');
|
||||
expect(configuration.logAlias).to.be('filebeat-**');
|
||||
expect(configuration.fields.container).to.be('UPDATED_CONTAINER');
|
||||
expect(configuration.fields.host).to.be('UPDATED_HOST');
|
||||
expect(configuration.fields.pod).to.be('UPDATED_POD');
|
||||
expect(configuration.fields.tiebreaker).to.be('UPDATED_TIEBREAKER');
|
||||
expect(configuration.fields.timestamp).to.be('UPDATED_TIMESTAMP');
|
||||
expect(configuration.fields.host).to.be('host.name');
|
||||
expect(configuration.fields.pod).to.be('kubernetes.pod.uid');
|
||||
expect(configuration.fields.tiebreaker).to.be('_doc');
|
||||
expect(configuration.fields.timestamp).to.be('@timestamp');
|
||||
expect(configuration.fields.container).to.be('container.id');
|
||||
expect(configuration.logColumns).to.have.length(3);
|
||||
expect(status.logIndicesExist).to.be(true);
|
||||
expect(status.metricIndicesExist).to.be(true);
|
||||
});
|
||||
|
||||
it('updates a single alias', async () => {
|
||||
it('applies a single top-level update to an existing source', async () => {
|
||||
const creationResponse = await client.mutate<any>({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
source: {
|
||||
sourceProperties: {
|
||||
name: 'NAME',
|
||||
},
|
||||
sourceId: 'default',
|
||||
|
@ -270,13 +270,9 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
mutation: updateSourceMutation,
|
||||
variables: {
|
||||
sourceId: 'default',
|
||||
changes: [
|
||||
{
|
||||
setAliases: {
|
||||
metricAlias: 'metricbeat-**',
|
||||
},
|
||||
},
|
||||
],
|
||||
sourceProperties: {
|
||||
metricAlias: 'metricbeat-**',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -292,11 +288,56 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
expect(status.metricIndicesExist).to.be(true);
|
||||
});
|
||||
|
||||
it('updates a single field', async () => {
|
||||
it('applies a single nested field update to an existing source', async () => {
|
||||
const creationResponse = await client.mutate<any>({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
source: {
|
||||
sourceProperties: {
|
||||
name: 'NAME',
|
||||
fields: {
|
||||
host: 'HOST',
|
||||
},
|
||||
},
|
||||
sourceId: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
const { version: initialVersion, updatedAt: createdAt } =
|
||||
creationResponse.data && creationResponse.data.createSource.source;
|
||||
|
||||
expect(initialVersion).to.be.a('string');
|
||||
expect(createdAt).to.be.greaterThan(0);
|
||||
|
||||
const updateResponse = await client.mutate<any>({
|
||||
mutation: updateSourceMutation,
|
||||
variables: {
|
||||
sourceId: 'default',
|
||||
sourceProperties: {
|
||||
fields: {
|
||||
container: 'UPDATED_CONTAINER',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { version, updatedAt, configuration } =
|
||||
updateResponse.data && updateResponse.data.updateSource.source;
|
||||
|
||||
expect(version).to.be.a('string');
|
||||
expect(version).to.not.be(initialVersion);
|
||||
expect(updatedAt).to.be.greaterThan(createdAt);
|
||||
expect(configuration.fields.container).to.be('UPDATED_CONTAINER');
|
||||
expect(configuration.fields.host).to.be('HOST');
|
||||
expect(configuration.fields.pod).to.be('kubernetes.pod.uid');
|
||||
expect(configuration.fields.tiebreaker).to.be('_doc');
|
||||
expect(configuration.fields.timestamp).to.be('@timestamp');
|
||||
});
|
||||
|
||||
it('applies a log column update to an existing source', async () => {
|
||||
const creationResponse = await client.mutate<any>({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
sourceProperties: {
|
||||
name: 'NAME',
|
||||
},
|
||||
sourceId: 'default',
|
||||
|
@ -313,13 +354,16 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
mutation: updateSourceMutation,
|
||||
variables: {
|
||||
sourceId: 'default',
|
||||
changes: [
|
||||
{
|
||||
setFields: {
|
||||
container: 'UPDATED_CONTAINER',
|
||||
sourceProperties: {
|
||||
logColumns: [
|
||||
{
|
||||
fieldColumn: {
|
||||
id: 'ADDED_COLUMN_ID',
|
||||
field: 'ADDED_COLUMN_FIELD',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -329,11 +373,13 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
expect(version).to.be.a('string');
|
||||
expect(version).to.not.be(initialVersion);
|
||||
expect(updatedAt).to.be.greaterThan(createdAt);
|
||||
expect(configuration.fields.container).to.be('UPDATED_CONTAINER');
|
||||
expect(configuration.fields.host).to.be('host.name');
|
||||
expect(configuration.fields.pod).to.be('kubernetes.pod.uid');
|
||||
expect(configuration.fields.tiebreaker).to.be('_doc');
|
||||
expect(configuration.fields.timestamp).to.be('@timestamp');
|
||||
expect(configuration.logColumns).to.have.length(1);
|
||||
expect(configuration.logColumns[0]).to.have.key('fieldColumn');
|
||||
expect(configuration.logColumns[0].fieldColumn).to.have.property('id', 'ADDED_COLUMN_ID');
|
||||
expect(configuration.logColumns[0].fieldColumn).to.have.property(
|
||||
'field',
|
||||
'ADDED_COLUMN_FIELD'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -343,32 +389,23 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
export default sourcesTests;
|
||||
|
||||
const createSourceMutation = gql`
|
||||
mutation createSource($sourceId: ID!, $source: CreateSourceInput!) {
|
||||
createSource(id: $sourceId, source: $source) {
|
||||
mutation createSource($sourceId: ID!, $sourceProperties: UpdateSourceInput!) {
|
||||
createSource(id: $sourceId, sourceProperties: $sourceProperties) {
|
||||
source {
|
||||
id
|
||||
version
|
||||
updatedAt
|
||||
...InfraSourceFields
|
||||
configuration {
|
||||
name
|
||||
description
|
||||
metricAlias
|
||||
logAlias
|
||||
fields {
|
||||
container
|
||||
host
|
||||
pod
|
||||
tiebreaker
|
||||
timestamp
|
||||
}
|
||||
...SourceConfigurationFields
|
||||
}
|
||||
status {
|
||||
logIndicesExist
|
||||
metricIndicesExist
|
||||
...SourceStatusFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraSourceFields}
|
||||
${sourceConfigurationFieldsFragment}
|
||||
${sourceStatusFieldsFragment}
|
||||
`;
|
||||
|
||||
const deleteSourceMutation = gql`
|
||||
|
@ -380,30 +417,21 @@ const deleteSourceMutation = gql`
|
|||
`;
|
||||
|
||||
const updateSourceMutation = gql`
|
||||
mutation updateSource($sourceId: ID!, $changes: [UpdateSourceInput!]!) {
|
||||
updateSource(id: $sourceId, changes: $changes) {
|
||||
mutation updateSource($sourceId: ID!, $sourceProperties: UpdateSourceInput!) {
|
||||
updateSource(id: $sourceId, sourceProperties: $sourceProperties) {
|
||||
source {
|
||||
id
|
||||
version
|
||||
updatedAt
|
||||
...InfraSourceFields
|
||||
configuration {
|
||||
name
|
||||
description
|
||||
metricAlias
|
||||
logAlias
|
||||
fields {
|
||||
container
|
||||
host
|
||||
pod
|
||||
tiebreaker
|
||||
timestamp
|
||||
}
|
||||
...SourceConfigurationFields
|
||||
}
|
||||
status {
|
||||
logIndicesExist
|
||||
metricIndicesExist
|
||||
...SourceStatusFields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${sharedFragments.InfraSourceFields}
|
||||
${sourceConfigurationFieldsFragment}
|
||||
${sourceStatusFieldsFragment}
|
||||
`;
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
import { InMemoryCache } from 'apollo-cache-inmemory';
|
||||
import { ApolloClient } from 'apollo-client';
|
||||
|
||||
import {
|
||||
UpdateSourceInput,
|
||||
UpdateSourceResult,
|
||||
} from '../../../../plugins/infra/public/graphql/types';
|
||||
|
||||
export interface EsArchiver {
|
||||
load(name: string): void;
|
||||
unload(name: string): void;
|
||||
|
@ -18,6 +23,13 @@ interface InfraOpsGraphQLClientFactoryOptions {
|
|||
basePath: string;
|
||||
}
|
||||
|
||||
interface InfraOpsSourceConfigurationService {
|
||||
createConfiguration(
|
||||
sourceId: string,
|
||||
sourceProperties: UpdateSourceInput
|
||||
): UpdateSourceResult['source']['version'];
|
||||
}
|
||||
|
||||
export interface KbnTestProviderOptions {
|
||||
getService(name: string): any;
|
||||
getService(name: 'esArchiver'): EsArchiver;
|
||||
|
@ -25,6 +37,7 @@ export interface KbnTestProviderOptions {
|
|||
getService(
|
||||
name: 'infraOpsGraphQLClientFactory'
|
||||
): (options: InfraOpsGraphQLClientFactoryOptions) => ApolloClient<InMemoryCache>;
|
||||
getService(name: 'infraOpsSourceConfiguration'): InfraOpsSourceConfigurationService;
|
||||
}
|
||||
|
||||
export type KbnTestProvider = (options: KbnTestProviderOptions) => void;
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
InfraOpsGraphQLClientProvider,
|
||||
InfraOpsGraphQLClientFactoryProvider,
|
||||
SiemGraphQLProvider,
|
||||
InfraOpsSourceConfigurationProvider,
|
||||
} from './services';
|
||||
|
||||
import {
|
||||
|
@ -41,6 +42,7 @@ export default async function ({ readConfigFile }) {
|
|||
infraOpsGraphQLClient: InfraOpsGraphQLClientProvider,
|
||||
infraOpsGraphQLClientFactory: InfraOpsGraphQLClientFactoryProvider,
|
||||
siemGraphQLClient: SiemGraphQLProvider,
|
||||
infraOpsSourceConfiguration: InfraOpsSourceConfigurationProvider,
|
||||
es: EsProvider,
|
||||
esArchiver: kibanaCommonConfig.get('services.esArchiver'),
|
||||
usageAPI: UsageAPIProvider,
|
||||
|
|
|
@ -10,3 +10,4 @@ export { SupertestWithoutAuthProvider } from './supertest_without_auth';
|
|||
export { UsageAPIProvider } from './usage_api';
|
||||
export { InfraOpsGraphQLClientProvider, InfraOpsGraphQLClientFactoryProvider } from './infraops_graphql_client';
|
||||
export { SiemGraphQLProvider } from './siem_graphql_client';
|
||||
export { InfraOpsSourceConfigurationProvider } from './infraops_source_configuration';
|
||||
|
|
|
@ -39,6 +39,17 @@ export function InfraOpsGraphQLClientFactoryProvider({ getService }) {
|
|||
introspectionQueryResultData,
|
||||
}),
|
||||
}),
|
||||
defaultOptions: {
|
||||
query: {
|
||||
fetchPolicy: 'no-cache'
|
||||
},
|
||||
watchQuery: {
|
||||
fetchPolicy: 'no-cache'
|
||||
},
|
||||
mutate: {
|
||||
fetchPolicy: 'no-cache'
|
||||
},
|
||||
},
|
||||
link: httpLink,
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
const createSourceMutation = gql`
|
||||
mutation createSource($sourceId: ID!, $sourceProperties: UpdateSourceInput!) {
|
||||
createSource(id: $sourceId, sourceProperties: $sourceProperties) {
|
||||
source {
|
||||
id
|
||||
version
|
||||
configuration {
|
||||
name
|
||||
logColumns {
|
||||
... on InfraSourceTimestampLogColumn {
|
||||
timestampColumn {
|
||||
id
|
||||
}
|
||||
}
|
||||
... on InfraSourceMessageLogColumn {
|
||||
messageColumn {
|
||||
id
|
||||
}
|
||||
}
|
||||
... on InfraSourceFieldLogColumn {
|
||||
fieldColumn {
|
||||
id
|
||||
field
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function InfraOpsSourceConfigurationProvider({ getService }) {
|
||||
const client = getService('infraOpsGraphQLClient');
|
||||
const log = getService('log');
|
||||
|
||||
return {
|
||||
async createConfiguration(sourceId, sourceProperties) {
|
||||
log.debug(`Creating Infra UI source configuration "${sourceId}" with properties ${JSON.stringify(sourceProperties)}`);
|
||||
const response = await client.mutate({
|
||||
mutation: createSourceMutation,
|
||||
variables: {
|
||||
sourceProperties,
|
||||
sourceId,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data.createSource.source.version;
|
||||
},
|
||||
};
|
||||
}
|
|
@ -4,13 +4,16 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) => {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const infraLogStream = getService('infraLogStream');
|
||||
const infraSourceConfigurationFlyout = getService('infraSourceConfigurationFlyout');
|
||||
const pageObjects = getPageObjects(['common', 'infraLogs']);
|
||||
const pageObjects = getPageObjects(['infraLogs']);
|
||||
|
||||
describe('Logs Page', () => {
|
||||
before(async () => {
|
||||
|
@ -29,12 +32,13 @@ export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProvi
|
|||
});
|
||||
|
||||
it('renders the log stream', async () => {
|
||||
await pageObjects.common.navigateToApp('infraLogs');
|
||||
await pageObjects.infraLogs.navigateTo();
|
||||
await pageObjects.infraLogs.getLogStream();
|
||||
});
|
||||
|
||||
it('can change the log indices to a pattern that matches nothing', async () => {
|
||||
await pageObjects.infraLogs.openSourceConfigurationFlyout();
|
||||
await infraSourceConfigurationFlyout.switchToIndicesAndFieldsTab();
|
||||
|
||||
const nameInput = await infraSourceConfigurationFlyout.getNameInput();
|
||||
await nameInput.clearValueWithKeyboard({ charByChar: true });
|
||||
|
@ -54,6 +58,7 @@ export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProvi
|
|||
|
||||
it('can change the log indices back to a pattern that matches something', async () => {
|
||||
await pageObjects.infraLogs.openSourceConfigurationFlyout();
|
||||
await infraSourceConfigurationFlyout.switchToIndicesAndFieldsTab();
|
||||
|
||||
const logIndicesInput = await infraSourceConfigurationFlyout.getLogIndicesInput();
|
||||
await logIndicesInput.clearValueWithKeyboard({ charByChar: true });
|
||||
|
@ -66,6 +71,44 @@ export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProvi
|
|||
it('renders the log stream again', async () => {
|
||||
await pageObjects.infraLogs.getLogStream();
|
||||
});
|
||||
|
||||
it('renders the default log columns', async () => {
|
||||
const logStreamEntries = await infraLogStream.getStreamEntries();
|
||||
|
||||
expect(logStreamEntries.length).to.be.greaterThan(0);
|
||||
|
||||
const firstLogStreamEntry = logStreamEntries[0];
|
||||
const logStreamEntryColumns = await infraLogStream.getLogColumnsOfStreamEntry(
|
||||
firstLogStreamEntry
|
||||
);
|
||||
|
||||
expect(logStreamEntryColumns).to.have.length(3);
|
||||
});
|
||||
|
||||
it('can change the log columns', async () => {
|
||||
await pageObjects.infraLogs.openSourceConfigurationFlyout();
|
||||
await infraSourceConfigurationFlyout.switchToLogsTab();
|
||||
|
||||
await infraSourceConfigurationFlyout.removeAllLogColumns();
|
||||
await infraSourceConfigurationFlyout.addTimestampLogColumn();
|
||||
await infraSourceConfigurationFlyout.addFieldLogColumn('host.name');
|
||||
|
||||
await infraSourceConfigurationFlyout.saveConfiguration();
|
||||
await infraSourceConfigurationFlyout.closeFlyout();
|
||||
});
|
||||
|
||||
it('renders the changed log columns', async () => {
|
||||
const logStreamEntries = await infraLogStream.getStreamEntries();
|
||||
|
||||
expect(logStreamEntries.length).to.be.greaterThan(0);
|
||||
|
||||
const firstLogStreamEntry = logStreamEntries[0];
|
||||
const logStreamEntryColumns = await infraLogStream.getLogColumnsOfStreamEntry(
|
||||
firstLogStreamEntry
|
||||
);
|
||||
|
||||
expect(logStreamEntryColumns).to.have.length(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -39,6 +39,7 @@ export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProvi
|
|||
|
||||
it('can change the metric indices to a pattern that matches nothing', async () => {
|
||||
await pageObjects.infraHome.openSourceConfigurationFlyout();
|
||||
await infraSourceConfigurationFlyout.switchToIndicesAndFieldsTab();
|
||||
|
||||
const nameInput = await infraSourceConfigurationFlyout.getNameInput();
|
||||
await nameInput.clearValueWithKeyboard({ charByChar: true });
|
||||
|
@ -58,6 +59,7 @@ export default ({ getPageObjects, getService }: KibanaFunctionalTestDefaultProvi
|
|||
|
||||
it('can change the log indices back to a pattern that matches something', async () => {
|
||||
await pageObjects.infraHome.openSourceConfigurationFlyout();
|
||||
await infraSourceConfigurationFlyout.switchToIndicesAndFieldsTab();
|
||||
|
||||
const metricIndicesInput = await infraSourceConfigurationFlyout.getMetricIndicesInput();
|
||||
await metricIndicesInput.clearValueWithKeyboard({ charByChar: true });
|
||||
|
|
|
@ -59,6 +59,7 @@ import {
|
|||
UserMenuProvider,
|
||||
UptimeProvider,
|
||||
InfraSourceConfigurationFlyoutProvider,
|
||||
InfraLogStreamProvider,
|
||||
} from './services';
|
||||
|
||||
import {
|
||||
|
@ -148,6 +149,7 @@ export default async function ({ readConfigFile }) {
|
|||
uptime: UptimeProvider,
|
||||
rollup: RollupPageProvider,
|
||||
infraSourceConfigurationFlyout: InfraSourceConfigurationFlyoutProvider,
|
||||
infraLogStream: InfraLogStreamProvider,
|
||||
},
|
||||
|
||||
// just like services, PageObjects are defined as a map of
|
||||
|
|
|
@ -9,12 +9,18 @@
|
|||
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../types/providers';
|
||||
|
||||
export function InfraLogsPageProvider({ getService }: KibanaFunctionalTestDefaultProviders) {
|
||||
export function InfraLogsPageProvider({
|
||||
getPageObjects,
|
||||
getService,
|
||||
}: KibanaFunctionalTestDefaultProviders) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
// const find = getService('find');
|
||||
// const browser = getService('browser');
|
||||
const pageObjects = getPageObjects(['common']);
|
||||
|
||||
return {
|
||||
async navigateTo() {
|
||||
await pageObjects.common.navigateToApp('infraLogs');
|
||||
},
|
||||
|
||||
async getLogStream() {
|
||||
return await testSubjects.find('logStream');
|
||||
},
|
||||
|
|
|
@ -13,3 +13,4 @@ export { GrokDebuggerProvider } from './grok_debugger';
|
|||
export { UserMenuProvider } from './user_menu';
|
||||
export { UptimeProvider } from './uptime';
|
||||
export { InfraSourceConfigurationFlyoutProvider } from './infra_source_configuration_flyout';
|
||||
export { InfraLogStreamProvider } from './infra_log_stream';
|
||||
|
|
24
x-pack/test/functional/services/infra_log_stream.ts
Normal file
24
x-pack/test/functional/services/infra_log_stream.ts
Normal 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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../types/providers';
|
||||
import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
|
||||
|
||||
export function InfraLogStreamProvider({ getService }: KibanaFunctionalTestDefaultProviders) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
async getStreamEntries(): Promise<WebElementWrapper[]> {
|
||||
return await testSubjects.findAll('streamEntry');
|
||||
},
|
||||
|
||||
async getLogColumnsOfStreamEntry(
|
||||
entryElement: WebElementWrapper
|
||||
): Promise<WebElementWrapper[]> {
|
||||
return await testSubjects.findAllDescendant('logColumn', entryElement);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -5,38 +5,106 @@
|
|||
*/
|
||||
|
||||
import { KibanaFunctionalTestDefaultProviders } from '../../types/providers';
|
||||
import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper';
|
||||
|
||||
export function InfraSourceConfigurationFlyoutProvider({
|
||||
getService,
|
||||
}: KibanaFunctionalTestDefaultProviders) {
|
||||
const find = getService('find');
|
||||
const retry = getService('retry');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Tab navigation
|
||||
*/
|
||||
async switchToIndicesAndFieldsTab() {
|
||||
await (await find.descendantDisplayedByCssSelector(
|
||||
'#indicesAndFieldsTab',
|
||||
await this.getFlyout()
|
||||
)).click();
|
||||
await testSubjects.find('sourceConfigurationNameSectionTitle');
|
||||
},
|
||||
async switchToLogsTab() {
|
||||
await (await find.descendantDisplayedByCssSelector(
|
||||
'#logsTab',
|
||||
await this.getFlyout()
|
||||
)).click();
|
||||
await testSubjects.find('sourceConfigurationLogColumnsSectionTitle');
|
||||
},
|
||||
|
||||
/**
|
||||
* Indices and fields
|
||||
*/
|
||||
async getNameInput() {
|
||||
return await testSubjects.find('nameInput');
|
||||
return await testSubjects.findDescendant('nameInput', await this.getFlyout());
|
||||
},
|
||||
|
||||
async getLogIndicesInput() {
|
||||
return await testSubjects.find('logIndicesInput');
|
||||
return await testSubjects.findDescendant('logIndicesInput', await this.getFlyout());
|
||||
},
|
||||
|
||||
async getMetricIndicesInput() {
|
||||
return await testSubjects.find('metricIndicesInput');
|
||||
return await testSubjects.findDescendant('metricIndicesInput', await this.getFlyout());
|
||||
},
|
||||
|
||||
/**
|
||||
* Logs
|
||||
*/
|
||||
async getAddLogColumnButton(): Promise<WebElementWrapper> {
|
||||
return await testSubjects.findDescendant('addLogColumnButton', await this.getFlyout());
|
||||
},
|
||||
async getAddLogColumnPopover(): Promise<WebElementWrapper> {
|
||||
return await testSubjects.find('addLogColumnPopover');
|
||||
},
|
||||
async addTimestampLogColumn() {
|
||||
await (await this.getAddLogColumnButton()).click();
|
||||
await (await testSubjects.findDescendant(
|
||||
'addTimestampLogColumn',
|
||||
await this.getAddLogColumnPopover()
|
||||
)).click();
|
||||
},
|
||||
async addFieldLogColumn(fieldName: string) {
|
||||
await (await this.getAddLogColumnButton()).click();
|
||||
const popover = await this.getAddLogColumnPopover();
|
||||
await (await testSubjects.findDescendant('fieldSearchInput', popover)).type(fieldName);
|
||||
await (await testSubjects.findDescendant(`addFieldLogColumn:${fieldName}`, popover)).click();
|
||||
},
|
||||
async getLogColumnPanels(): Promise<WebElementWrapper[]> {
|
||||
return await testSubjects.findAllDescendant('logColumnPanel', await this.getFlyout());
|
||||
},
|
||||
async removeLogColumn(columnIndex: number) {
|
||||
const logColumnPanel = (await this.getLogColumnPanels())[columnIndex];
|
||||
await (await testSubjects.findDescendant('removeLogColumnButton', logColumnPanel)).click();
|
||||
await testSubjects.waitForDeleted(logColumnPanel);
|
||||
},
|
||||
async removeAllLogColumns() {
|
||||
for (const _ of await this.getLogColumnPanels()) {
|
||||
await this.removeLogColumn(0);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Form and flyout
|
||||
*/
|
||||
async getFlyout() {
|
||||
return await testSubjects.find('sourceConfigurationFlyout');
|
||||
},
|
||||
async saveConfiguration() {
|
||||
await testSubjects.click('updateSourceConfigurationButton');
|
||||
await (await testSubjects.findDescendant(
|
||||
'updateSourceConfigurationButton',
|
||||
await this.getFlyout()
|
||||
)).click();
|
||||
|
||||
await retry.try(async () => {
|
||||
const element = await testSubjects.find('updateSourceConfigurationButton');
|
||||
const element = await testSubjects.findDescendant(
|
||||
'updateSourceConfigurationButton',
|
||||
await this.getFlyout()
|
||||
);
|
||||
return !(await element.isEnabled());
|
||||
});
|
||||
},
|
||||
|
||||
async closeFlyout() {
|
||||
const flyout = await testSubjects.find('sourceConfigurationFlyout');
|
||||
await testSubjects.click('closeFlyoutButton');
|
||||
const flyout = await this.getFlyout();
|
||||
await (await testSubjects.findDescendant('closeFlyoutButton', flyout)).click();
|
||||
await testSubjects.waitForDeleted(flyout);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,10 +5,16 @@
|
|||
*/
|
||||
|
||||
import { EsArchiver } from '../../../src/es_archiver';
|
||||
import { InfraLogStreamProvider } from '../functional/services/infra_log_stream';
|
||||
import { InfraSourceConfigurationFlyoutProvider } from '../functional/services/infra_source_configuration_flyout';
|
||||
import { UptimeProvider } from '../functional/services/uptime';
|
||||
|
||||
export interface KibanaFunctionalTestDefaultProviders {
|
||||
getService(serviceName: 'esArchiver'): EsArchiver;
|
||||
getService(serviceName: 'infraLogStream'): ReturnType<typeof InfraLogStreamProvider>;
|
||||
getService(
|
||||
serviceName: 'infraSourceConfigurationFlyout'
|
||||
): ReturnType<typeof InfraSourceConfigurationFlyoutProvider>;
|
||||
getService(serviceName: 'uptime'): ReturnType<typeof UptimeProvider>;
|
||||
getService(serviceName: string): any;
|
||||
getPageObjects(pageObjectNames: string[]): any;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue