[7.x] [Logs UI] HTTP API for log entry (#53485) (#53691)

* Implement `log_entries/item` api

* Use endpoint in the `useLogFlyout` hook

* Clean up GraphQL implementation and types

* Extract type for log entries cursor

* fixup! Extract type for log entries cursor

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Alejandro Fernández 2019-12-20 22:22:24 +01:00 committed by GitHub
parent a5b5a8d201
commit 69ccb62b83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 310 additions and 607 deletions

View file

@ -35,7 +35,6 @@ export interface InfraSource {
/** Sequences of log entries matching sets of highlighting queries within an interval */
logEntryHighlights: InfraLogEntryInterval[];
logItem: InfraLogItem;
/** A snapshot of nodes */
snapshot?: InfraSnapshotResponse | null;
@ -205,24 +204,6 @@ export interface InfraLogEntryFieldColumn {
highlights: string[];
}
export interface InfraLogItem {
/** The ID of the document */
id: string;
/** The index where the document was found */
index: string;
/** Time key for the document - derived from the source configuration timestamp and tiebreaker settings */
key: InfraTimeKey;
/** An array of flattened fields and values */
fields: InfraLogItemField[];
}
export interface InfraLogItemField {
/** The flattened field name */
field: string;
/** The value for the Field as a string */
value: string;
}
export interface InfraSnapshotResponse {
/** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */
nodes: InfraSnapshotNode[];
@ -424,9 +405,6 @@ export interface LogEntryHighlightsInfraSourceArgs {
/** The highlighting to apply to the log entries */
highlights: InfraLogEntryHighlightInput[];
}
export interface LogItemInfraSourceArgs {
id: string;
}
export interface SnapshotInfraSourceArgs {
timerange: InfraTimerangeInput;
@ -600,49 +578,6 @@ export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessa
// Documents
// ====================================================
export namespace FlyoutItemQuery {
export type Variables = {
sourceId: string;
itemId: string;
};
export type Query = {
__typename?: 'Query';
source: Source;
};
export type Source = {
__typename?: 'InfraSource';
id: string;
logItem: LogItem;
};
export type LogItem = {
__typename?: 'InfraLogItem';
id: string;
index: string;
key: Key;
fields: Fields[];
};
export type Key = InfraTimeKeyFields.Fragment;
export type Fields = {
__typename?: 'InfraLogItemField';
field: string;
value: string;
};
}
export namespace LogEntryHighlightsQuery {
export type Variables = {
sourceId?: string | null;

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as rt from 'io-ts';
export const logEntriesCursorRT = rt.type({
time: rt.number,
tiebreaker: rt.number,
});

View file

@ -4,5 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
export * from './item';
export * from './summary';
export * from './summary_highlights';

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import * as rt from 'io-ts';
import { logEntriesCursorRT } from './common';
export const LOG_ENTRIES_ITEM_PATH = '/api/log_entries/item';
export const logEntriesItemRequestRT = rt.type({
sourceId: rt.string,
id: rt.string,
});
export type LogEntriesItemRequest = rt.TypeOf<typeof logEntriesItemRequestRT>;
const logEntriesItemFieldRT = rt.type({ field: rt.string, value: rt.string });
const logEntriesItemRT = rt.type({
id: rt.string,
index: rt.string,
fields: rt.array(logEntriesItemFieldRT),
key: logEntriesCursorRT,
});
export const logEntriesItemResponseRT = rt.type({
data: logEntriesItemRT,
});
export type LogEntriesItemField = rt.TypeOf<typeof logEntriesItemFieldRT>;
export type LogEntriesItem = rt.TypeOf<typeof logEntriesItemRT>;
export type LogEntriesItemResponse = rt.TypeOf<typeof logEntriesItemResponseRT>;

View file

@ -6,6 +6,7 @@
import * as rt from 'io-ts';
import { logEntriesSummaryRequestRT, logEntriesSummaryBucketRT } from './summary';
import { logEntriesCursorRT } from './common';
export const LOG_ENTRIES_SUMMARY_HIGHLIGHTS_PATH = '/api/log_entries/summary_highlights';
@ -23,10 +24,7 @@ export type LogEntriesSummaryHighlightsRequest = rt.TypeOf<
export const logEntriesSummaryHighlightsBucketRT = rt.intersection([
logEntriesSummaryBucketRT,
rt.type({
representativeKey: rt.type({
time: rt.number,
tiebreaker: rt.number,
}),
representativeKey: logEntriesCursorRT,
}),
]);

View file

@ -9,14 +9,14 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React, { useMemo } from 'react';
import url from 'url';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { InfraLogItem } from '../../../graphql/types';
import { useVisibilityState } from '../../../utils/use_visibility_state';
import { getTraceUrl } from '../../../../../apm/public/components/shared/Links/apm/ExternalLinks';
import { LogEntriesItem } from '../../../../common/http_api';
const UPTIME_FIELDS = ['container.id', 'host.ip', 'kubernetes.pod.uid'];
export const LogEntryActionsMenu: React.FunctionComponent<{
logItem: InfraLogItem;
logItem: LogEntriesItem;
}> = ({ logItem }) => {
const prependBasePath = useKibana().services.http?.basePath?.prepend;
const { hide, isVisible, show } = useVisibilityState(false);
@ -89,7 +89,7 @@ export const LogEntryActionsMenu: React.FunctionComponent<{
);
};
const getUptimeLink = (logItem: InfraLogItem) => {
const getUptimeLink = (logItem: LogEntriesItem) => {
const searchExpressions = logItem.fields
.filter(({ field, value }) => value != null && UPTIME_FIELDS.includes(field))
.map(({ field, value }) => `${field}:${value}`);
@ -104,7 +104,7 @@ const getUptimeLink = (logItem: InfraLogItem) => {
});
};
const getAPMLink = (logItem: InfraLogItem) => {
const getAPMLink = (logItem: LogEntriesItem) => {
const traceIdEntry = logItem.fields.find(
({ field, value }) => value != null && field === 'trace.id'
);

View file

@ -22,12 +22,12 @@ import React, { useCallback, useMemo } from 'react';
import euiStyled from '../../../../../../common/eui_styled_components';
import { TimeKey } from '../../../../common/time';
import { InfraLogItem, InfraLogItemField } from '../../../graphql/types';
import { InfraLoadingPanel } from '../../loading';
import { LogEntryActionsMenu } from './log_entry_actions_menu';
import { LogEntriesItem, LogEntriesItemField } from '../../../../common/http_api';
interface Props {
flyoutItem: InfraLogItem | null;
flyoutItem: LogEntriesItem | null;
setFlyoutVisibility: (visible: boolean) => void;
setFilter: (filter: string) => void;
setTarget: (timeKey: TimeKey, flyoutItemId: string) => void;
@ -43,7 +43,7 @@ export const LogEntryFlyout = ({
setTarget,
}: Props) => {
const createFilterHandler = useCallback(
(field: InfraLogItemField) => () => {
(field: LogEntriesItemField) => () => {
const filter = `${field.field}:"${field.value}"`;
setFilter(filter);
@ -80,7 +80,7 @@ export const LogEntryFlyout = ({
defaultMessage: 'Value',
}),
sortable: true,
render: (_name: string, item: InfraLogItemField) => (
render: (_name: string, item: LogEntriesItemField) => (
<span>
<EuiToolTip
content={i18n.translate('xpack.infra.logFlyout.setFilterTooltip', {

View file

@ -1,29 +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 gql from 'graphql-tag';
import { sharedFragments } from '../../../common/graphql/shared';
export const flyoutItemQuery = gql`
query FlyoutItemQuery($sourceId: ID!, $itemId: ID!) {
source(id: $sourceId) {
id
logItem(id: $itemId) {
id
index
key {
...InfraTimeKeyFields
}
fields {
field
value
}
}
}
}
${sharedFragments.InfraTimeKey}
`;

View file

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import { kfetch } from 'ui/kfetch';
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
import {
LOG_ENTRIES_ITEM_PATH,
LogEntriesItemRequest,
logEntriesItemRequestRT,
logEntriesItemResponseRT,
} from '../../../../../common/http_api';
export const fetchLogEntriesItem = async (requestArgs: LogEntriesItemRequest) => {
const response = await kfetch({
method: 'POST',
pathname: LOG_ENTRIES_ITEM_PATH,
body: JSON.stringify(logEntriesItemRequestRT.encode(requestArgs)),
});
return pipe(
logEntriesItemResponseRT.decode(response),
fold(throwErrors(createPlainError), identity)
);
};

View file

@ -8,12 +8,11 @@ import createContainer from 'constate';
import { isString } from 'lodash';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { FlyoutItemQuery, InfraLogItem } from '../../graphql/types';
import { useApolloClient } from '../../utils/apollo_context';
import { UrlStateContainer } from '../../utils/url_state';
import { useTrackedPromise } from '../../utils/use_tracked_promise';
import { Source } from '../source';
import { flyoutItemQuery } from './flyout_item.gql_query';
import { fetchLogEntriesItem } from './log_entries/api/fetch_log_entries_item';
import { LogEntriesItem } from '../../../common/http_api';
export enum FlyoutVisibility {
hidden = 'hidden',
@ -30,40 +29,26 @@ export const useLogFlyout = () => {
const { sourceId } = useContext(Source.Context);
const [flyoutVisible, setFlyoutVisibility] = useState<boolean>(false);
const [flyoutId, setFlyoutId] = useState<string | null>(null);
const [flyoutItem, setFlyoutItem] = useState<InfraLogItem | null>(null);
const [flyoutItem, setFlyoutItem] = useState<LogEntriesItem | null>(null);
const [surroundingLogsId, setSurroundingLogsId] = useState<string | null>(null);
const apolloClient = useApolloClient();
const [loadFlyoutItemRequest, loadFlyoutItem] = useTrackedPromise(
{
cancelPreviousOn: 'creation',
createPromise: async () => {
if (!apolloClient) {
throw new Error('Failed to load flyout item: No apollo client available.');
}
if (!flyoutId) {
return;
}
return await apolloClient.query<FlyoutItemQuery.Query, FlyoutItemQuery.Variables>({
fetchPolicy: 'no-cache',
query: flyoutItemQuery,
variables: {
itemId: flyoutId,
sourceId,
},
});
return await fetchLogEntriesItem({ sourceId, id: flyoutId });
},
onResolve: response => {
if (response) {
const { data } = response;
setFlyoutItem((data && data.source && data.source.logItem) || null);
setFlyoutItem(data || null);
}
},
},
[apolloClient, sourceId, flyoutId]
[sourceId, flyoutId]
);
const isLoading = useMemo(() => {

View file

@ -286,29 +286,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "logItem",
"description": "",
"args": [
{
"name": "id",
"description": "",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "ID", "ofType": null }
},
"defaultValue": null
}
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "InfraLogItem", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "snapshot",
"description": "A snapshot of nodes",
@ -1537,108 +1514,6 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "InfraLogItem",
"description": "",
"fields": [
{
"name": "id",
"description": "The ID of the document",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "ID", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "index",
"description": "The index where the document was found",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "key",
"description": "Time key for the document - derived from the source configuration timestamp and tiebreaker settings",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "InfraTimeKey", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "fields",
"description": "An array of flattened fields and values",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "InfraLogItemField", "ofType": null }
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "InfraLogItemField",
"description": "",
"fields": [
{
"name": "field",
"description": "The flattened field name",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "value",
"description": "The value for the Field as a string",
"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": "INPUT_OBJECT",
"name": "InfraTimerangeInput",
@ -1836,9 +1711,24 @@
{ "name": "memory", "description": "", "isDeprecated": false, "deprecationReason": null },
{ "name": "tx", "description": "", "isDeprecated": false, "deprecationReason": null },
{ "name": "rx", "description": "", "isDeprecated": false, "deprecationReason": null },
{ "name": "logRate", "description": "", "isDeprecated": false, "deprecationReason": null },
{ "name": "diskIOReadBytes", "description": "", "isDeprecated": false, "deprecationReason": null },
{ "name": "diskIOWriteBytes", "description": "", "isDeprecated": false, "deprecationReason": null }
{
"name": "logRate",
"description": "",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "diskIOReadBytes",
"description": "",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "diskIOWriteBytes",
"description": "",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},

View file

@ -37,7 +37,6 @@ export interface InfraSource {
/** Sequences of log entries matching sets of highlighting queries within an interval */
logEntryHighlights: InfraLogEntryInterval[];
logItem: InfraLogItem;
/** A snapshot of nodes */
snapshot?: InfraSnapshotResponse | null;
@ -207,24 +206,6 @@ export interface InfraLogEntryFieldColumn {
highlights: string[];
}
export interface InfraLogItem {
/** The ID of the document */
id: string;
/** The index where the document was found */
index: string;
/** Time key for the document - derived from the source configuration timestamp and tiebreaker settings */
key: InfraTimeKey;
/** An array of flattened fields and values */
fields: InfraLogItemField[];
}
export interface InfraLogItemField {
/** The flattened field name */
field: string;
/** The value for the Field as a string */
value: string;
}
export interface InfraSnapshotResponse {
/** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */
nodes: InfraSnapshotNode[];
@ -426,9 +407,6 @@ export interface LogEntryHighlightsInfraSourceArgs {
/** The highlighting to apply to the log entries */
highlights: InfraLogEntryHighlightInput[];
}
export interface LogItemInfraSourceArgs {
id: string;
}
export interface SnapshotInfraSourceArgs {
timerange: InfraTimerangeInput;
@ -602,49 +580,6 @@ export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessa
// Documents
// ====================================================
export namespace FlyoutItemQuery {
export type Variables = {
sourceId: string;
itemId: string;
};
export type Query = {
__typename?: 'Query';
source: Source;
};
export type Source = {
__typename?: 'InfraSource';
id: string;
logItem: LogItem;
};
export type LogItem = {
__typename?: 'InfraLogItem';
id: string;
index: string;
key: Key;
fields: Fields[];
};
export type Key = InfraTimeKeyFields.Fragment;
export type Fields = {
__typename?: 'InfraLogItemField';
field: string;
value: string;
};
}
export namespace LogEntryHighlightsQuery {
export type Variables = {
sourceId?: string | null;

View file

@ -4,11 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { failure } from 'io-ts/lib/PathReporter';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import {
InfraLogEntryColumn,
InfraLogEntryFieldColumn,
@ -20,7 +15,6 @@ import {
InfraSourceResolvers,
} from '../../graphql/types';
import { InfraLogEntriesDomain } from '../../lib/domains/log_entries_domain';
import { SourceConfigurationRuntimeType } from '../../lib/sources';
import { parseFilterQuery } from '../../utils/serialized_query';
import { ChildResolverOf, InfraResolverOf } from '../../utils/typed_resolvers';
import { QuerySourceResolver } from '../sources/resolvers';
@ -40,11 +34,6 @@ export type InfraSourceLogEntryHighlightsResolver = ChildResolverOf<
QuerySourceResolver
>;
export type InfraSourceLogItem = ChildResolverOf<
InfraResolverOf<InfraSourceResolvers.LogItemResolver>,
QuerySourceResolver
>;
export const createLogEntriesResolvers = (libs: {
logEntries: InfraLogEntriesDomain;
}): {
@ -52,7 +41,6 @@ export const createLogEntriesResolvers = (libs: {
logEntriesAround: InfraSourceLogEntriesAroundResolver;
logEntriesBetween: InfraSourceLogEntriesBetweenResolver;
logEntryHighlights: InfraSourceLogEntryHighlightsResolver;
logItem: InfraSourceLogItem;
};
InfraLogEntryColumn: {
__resolveType(
@ -137,16 +125,6 @@ export const createLogEntriesResolvers = (libs: {
entries,
}));
},
async logItem(source, args, { req }) {
const sourceConfiguration = pipe(
SourceConfigurationRuntimeType.decode(source.configuration),
fold(errors => {
throw new Error(failure(errors).join('\n'));
}, identity)
);
return await libs.logEntries.getLogItem(req, args.id, sourceConfiguration);
},
},
InfraLogEntryColumn: {
__resolveType(logEntryColumn) {

View file

@ -100,24 +100,6 @@ export const logEntriesSchema = gql`
entries: [InfraLogEntry!]!
}
type InfraLogItemField {
"The flattened field name"
field: String!
"The value for the Field as a string"
value: String!
}
type InfraLogItem {
"The ID of the document"
id: ID!
"The index where the document was found"
index: String!
"Time key for the document - derived from the source configuration timestamp and tiebreaker settings"
key: InfraTimeKey!
"An array of flattened fields and values"
fields: [InfraLogItemField!]!
}
extend type InfraSource {
"A consecutive span of log entries surrounding a point in time"
logEntriesAround(
@ -150,6 +132,5 @@ export const logEntriesSchema = gql`
"The highlighting to apply to the log entries"
highlights: [InfraLogEntryHighlightInput!]!
): [InfraLogEntryInterval!]!
logItem(id: ID!): InfraLogItem!
}
`;

View file

@ -63,7 +63,6 @@ export interface InfraSource {
/** Sequences of log entries matching sets of highlighting queries within an interval */
logEntryHighlights: InfraLogEntryInterval[];
logItem: InfraLogItem;
/** A snapshot of nodes */
snapshot?: InfraSnapshotResponse | null;
@ -233,24 +232,6 @@ export interface InfraLogEntryFieldColumn {
highlights: string[];
}
export interface InfraLogItem {
/** The ID of the document */
id: string;
/** The index where the document was found */
index: string;
/** Time key for the document - derived from the source configuration timestamp and tiebreaker settings */
key: InfraTimeKey;
/** An array of flattened fields and values */
fields: InfraLogItemField[];
}
export interface InfraLogItemField {
/** The flattened field name */
field: string;
/** The value for the Field as a string */
value: string;
}
export interface InfraSnapshotResponse {
/** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */
nodes: InfraSnapshotNode[];
@ -452,9 +433,6 @@ export interface LogEntryHighlightsInfraSourceArgs {
/** The highlighting to apply to the log entries */
highlights: InfraLogEntryHighlightInput[];
}
export interface LogItemInfraSourceArgs {
id: string;
}
export interface SnapshotInfraSourceArgs {
timerange: InfraTimerangeInput;
@ -513,7 +491,7 @@ export enum InfraNodeType {
awsEC2 = 'awsEC2',
awsS3 = 'awsS3',
awsRDS = 'awsRDS',
awsSQS = 'awsSQS'
awsSQS = 'awsSQS',
}
export enum InfraSnapshotMetricType {
@ -675,7 +653,6 @@ export namespace InfraSourceResolvers {
/** Sequences of log entries matching sets of highlighting queries within an interval */
logEntryHighlights?: LogEntryHighlightsResolver<InfraLogEntryInterval[], TypeParent, Context>;
logItem?: LogItemResolver<InfraLogItem, TypeParent, Context>;
/** A snapshot of nodes */
snapshot?: SnapshotResolver<InfraSnapshotResponse | null, TypeParent, Context>;
@ -758,15 +735,6 @@ export namespace InfraSourceResolvers {
highlights: InfraLogEntryHighlightInput[];
}
export type LogItemResolver<
R = InfraLogItem,
Parent = InfraSource,
Context = InfraContext
> = Resolver<R, Parent, Context, LogItemArgs>;
export interface LogItemArgs {
id: string;
}
export type SnapshotResolver<
R = InfraSnapshotResponse | null,
Parent = InfraSource,
@ -1311,60 +1279,6 @@ export namespace InfraLogEntryFieldColumnResolvers {
> = Resolver<R, Parent, Context>;
}
export namespace InfraLogItemResolvers {
export interface Resolvers<Context = InfraContext, TypeParent = InfraLogItem> {
/** The ID of the document */
id?: IdResolver<string, TypeParent, Context>;
/** The index where the document was found */
index?: IndexResolver<string, TypeParent, Context>;
/** Time key for the document - derived from the source configuration timestamp and tiebreaker settings */
key?: KeyResolver<InfraTimeKey, TypeParent, Context>;
/** An array of flattened fields and values */
fields?: FieldsResolver<InfraLogItemField[], TypeParent, Context>;
}
export type IdResolver<R = string, Parent = InfraLogItem, Context = InfraContext> = Resolver<
R,
Parent,
Context
>;
export type IndexResolver<R = string, Parent = InfraLogItem, Context = InfraContext> = Resolver<
R,
Parent,
Context
>;
export type KeyResolver<
R = InfraTimeKey,
Parent = InfraLogItem,
Context = InfraContext
> = Resolver<R, Parent, Context>;
export type FieldsResolver<
R = InfraLogItemField[],
Parent = InfraLogItem,
Context = InfraContext
> = Resolver<R, Parent, Context>;
}
export namespace InfraLogItemFieldResolvers {
export interface Resolvers<Context = InfraContext, TypeParent = InfraLogItemField> {
/** The flattened field name */
field?: FieldResolver<string, TypeParent, Context>;
/** The value for the Field as a string */
value?: ValueResolver<string, TypeParent, Context>;
}
export type FieldResolver<
R = string,
Parent = InfraLogItemField,
Context = InfraContext
> = Resolver<R, Parent, Context>;
export type ValueResolver<
R = string,
Parent = InfraLogItemField,
Context = InfraContext
> = Resolver<R, Parent, Context>;
}
export namespace InfraSnapshotResponseResolvers {
export interface Resolvers<Context = InfraContext, TypeParent = InfraSnapshotResponse> {
/** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */

View file

@ -22,6 +22,7 @@ import { initNodeDetailsRoute } from './routes/node_details';
import {
initLogEntriesSummaryRoute,
initLogEntriesSummaryHighlightsRoute,
initLogEntriesItemRoute,
} from './routes/log_entries';
import { initInventoryMetaRoute } from './routes/inventory_metadata';
@ -44,6 +45,7 @@ export const initInfraServer = (libs: InfraBackendLibs) => {
initValidateLogAnalysisIndicesRoute(libs);
initLogEntriesSummaryRoute(libs);
initLogEntriesSummaryHighlightsRoute(libs);
initLogEntriesItemRoute(libs);
initMetricExplorerRoute(libs);
initMetadataRoute(libs);
initInventoryMetaRoute(libs);

View file

@ -8,7 +8,7 @@ import stringify from 'json-stable-stringify';
import { isArray, isPlainObject } from 'lodash';
import { JsonObject } from '../../../../common/typed_json';
import { InfraLogItemField } from '../../../graphql/types';
import { LogEntriesItemField } from '../../../../common/http_api';
const isJsonObject = (subject: any): subject is JsonObject => {
return isPlainObject(subject);
@ -24,8 +24,8 @@ const serializeValue = (value: any): string => {
export const convertDocumentSourceToLogItemFields = (
source: JsonObject,
path: string[] = [],
fields: InfraLogItemField[] = []
): InfraLogItemField[] => {
fields: LogEntriesItemField[] = []
): LogEntriesItemField[] => {
return Object.keys(source).reduce((acc, key) => {
const value = source[key];
const nextPath = [...path, key];

View file

@ -13,8 +13,9 @@ import { JsonObject } from '../../../../common/typed_json';
import {
LogEntriesSummaryBucket,
LogEntriesSummaryHighlightsBucket,
LogEntriesItem,
} from '../../../../common/http_api';
import { InfraLogEntry, InfraLogItem, InfraLogMessageSegment } from '../../../graphql/types';
import { InfraLogEntry, InfraLogMessageSegment } from '../../../graphql/types';
import {
InfraSourceConfiguration,
InfraSources,
@ -282,7 +283,7 @@ export class InfraLogEntriesDomain {
requestContext: RequestHandlerContext,
id: string,
sourceConfiguration: InfraSourceConfiguration
): Promise<InfraLogItem> {
): Promise<LogEntriesItem> {
const document = await this.adapter.getLogItem(requestContext, id, sourceConfiguration);
const defaultFields = [
{ field: '_index', value: document._index },

View file

@ -4,5 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
export * from './item';
export * from './summary';
export * from './summary_highlights';

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import Boom from 'boom';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { schema } from '@kbn/config-schema';
import { throwErrors } from '../../../common/runtime_types';
import { InfraBackendLibs } from '../../lib/infra_types';
import {
LOG_ENTRIES_ITEM_PATH,
logEntriesItemRequestRT,
logEntriesItemResponseRT,
} from '../../../common/http_api';
const escapeHatch = schema.object({}, { allowUnknowns: true });
export const initLogEntriesItemRoute = ({ framework, sources, logEntries }: InfraBackendLibs) => {
framework.registerRoute(
{
method: 'post',
path: LOG_ENTRIES_ITEM_PATH,
validate: { body: escapeHatch },
},
async (requestContext, request, response) => {
try {
const payload = pipe(
logEntriesItemRequestRT.decode(request.body),
fold(throwErrors(Boom.badRequest), identity)
);
const { id, sourceId } = payload;
const sourceConfiguration = (await sources.getSourceConfiguration(requestContext, sourceId))
.configuration;
const logEntry = await logEntries.getLogItem(requestContext, id, sourceConfiguration);
return response.ok({
body: logEntriesItemResponseRT.encode({
data: logEntry,
}),
});
} catch (error) {
return response.internalError({ body: error.message });
}
}
);
};

View file

@ -5,168 +5,148 @@
*/
import expect from '@kbn/expect';
import { flyoutItemQuery } from '../../../../legacy/plugins/infra/public/containers/logs/flyout_item.gql_query';
import { FlyoutItemQuery } from '../../../../legacy/plugins/infra/public/graphql/types';
import { FtrProviderContext } from '../../ftr_provider_context';
import {
LOG_ENTRIES_ITEM_PATH,
logEntriesItemRequestRT,
} from '../../../../legacy/plugins/infra/common/http_api';
const COMMON_HEADERS = {
'kbn-xsrf': 'some-xsrf-token',
};
export default function({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const client = getService('infraOpsGraphQLClient');
describe('Log Item GraphQL Endpoint', () => {
const supertest = getService('supertest');
describe('Log Item Endpoint', () => {
before(() => esArchiver.load('infra/metrics_and_logs'));
after(() => esArchiver.unload('infra/metrics_and_logs'));
it('should basically work', () => {
return client
.query<FlyoutItemQuery.Query>({
query: flyoutItemQuery,
variables: {
it('should basically work', async () => {
const { body } = await supertest
.post(LOG_ENTRIES_ITEM_PATH)
.set(COMMON_HEADERS)
.send(
logEntriesItemRequestRT.encode({
sourceId: 'default',
itemId: 'yT2Mg2YBh-opCxJv8Vqj',
},
})
.then(resp => {
expect(resp.data.source).to.have.property('logItem');
const { logItem } = resp.data.source;
if (!logItem) {
throw new Error('Log item should not be falsey');
}
expect(logItem).to.have.property('id', 'yT2Mg2YBh-opCxJv8Vqj');
expect(logItem).to.have.property('index', 'filebeat-7.0.0-alpha1-2018.10.17');
expect(logItem).to.have.property('fields');
expect(logItem.fields).to.eql([
{
field: '@timestamp',
value: '2018-10-17T19:42:22.000Z',
__typename: 'InfraLogItemField',
},
{
field: '_id',
value: 'yT2Mg2YBh-opCxJv8Vqj',
__typename: 'InfraLogItemField',
},
{
field: '_index',
value: 'filebeat-7.0.0-alpha1-2018.10.17',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.body_sent.bytes',
value: '1336',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.http_version',
value: '1.1',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.method',
value: 'GET',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.referrer',
value: '-',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.remote_ip',
value: '10.128.0.11',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.response_code',
value: '200',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.url',
value: '/a-fresh-start-will-put-you-on-your-way',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.user_agent.device',
value: 'Other',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.user_agent.name',
value: 'Other',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.user_agent.os',
value: 'Other',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.user_agent.os_name',
value: 'Other',
__typename: 'InfraLogItemField',
},
{
field: 'apache2.access.user_name',
value: '-',
__typename: 'InfraLogItemField',
},
{
field: 'beat.hostname',
value: 'demo-stack-apache-01',
__typename: 'InfraLogItemField',
},
{
field: 'beat.name',
value: 'demo-stack-apache-01',
__typename: 'InfraLogItemField',
},
{
field: 'beat.version',
value: '7.0.0-alpha1',
__typename: 'InfraLogItemField',
},
{
field: 'fileset.module',
value: 'apache2',
__typename: 'InfraLogItemField',
},
{
field: 'fileset.name',
value: 'access',
__typename: 'InfraLogItemField',
},
{
field: 'host.name',
value: 'demo-stack-apache-01',
__typename: 'InfraLogItemField',
},
{
field: 'input.type',
value: 'log',
__typename: 'InfraLogItemField',
},
{
field: 'offset',
value: '5497614',
__typename: 'InfraLogItemField',
},
{
field: 'prospector.type',
value: 'log',
__typename: 'InfraLogItemField',
},
{
field: 'read_timestamp',
value: '2018-10-17T19:42:23.160Z',
__typename: 'InfraLogItemField',
},
{
field: 'source',
value: '/var/log/apache2/access.log',
__typename: 'InfraLogItemField',
},
]);
});
id: 'yT2Mg2YBh-opCxJv8Vqj',
})
)
.expect(200);
const logItem = body.data;
expect(logItem).to.have.property('id', 'yT2Mg2YBh-opCxJv8Vqj');
expect(logItem).to.have.property('index', 'filebeat-7.0.0-alpha1-2018.10.17');
expect(logItem).to.have.property('fields');
expect(logItem.fields).to.eql([
{
field: '@timestamp',
value: '2018-10-17T19:42:22.000Z',
},
{
field: '_id',
value: 'yT2Mg2YBh-opCxJv8Vqj',
},
{
field: '_index',
value: 'filebeat-7.0.0-alpha1-2018.10.17',
},
{
field: 'apache2.access.body_sent.bytes',
value: '1336',
},
{
field: 'apache2.access.http_version',
value: '1.1',
},
{
field: 'apache2.access.method',
value: 'GET',
},
{
field: 'apache2.access.referrer',
value: '-',
},
{
field: 'apache2.access.remote_ip',
value: '10.128.0.11',
},
{
field: 'apache2.access.response_code',
value: '200',
},
{
field: 'apache2.access.url',
value: '/a-fresh-start-will-put-you-on-your-way',
},
{
field: 'apache2.access.user_agent.device',
value: 'Other',
},
{
field: 'apache2.access.user_agent.name',
value: 'Other',
},
{
field: 'apache2.access.user_agent.os',
value: 'Other',
},
{
field: 'apache2.access.user_agent.os_name',
value: 'Other',
},
{
field: 'apache2.access.user_name',
value: '-',
},
{
field: 'beat.hostname',
value: 'demo-stack-apache-01',
},
{
field: 'beat.name',
value: 'demo-stack-apache-01',
},
{
field: 'beat.version',
value: '7.0.0-alpha1',
},
{
field: 'fileset.module',
value: 'apache2',
},
{
field: 'fileset.name',
value: 'access',
},
{
field: 'host.name',
value: 'demo-stack-apache-01',
},
{
field: 'input.type',
value: 'log',
},
{
field: 'offset',
value: '5497614',
},
{
field: 'prospector.type',
value: 'log',
},
{
field: 'read_timestamp',
value: '2018-10-17T19:42:23.160Z',
},
{
field: 'source',
value: '/var/log/apache2/access.log',
},
]);
});
});
}