[Infra UI] Update links for containers to use ID instead of name (#27088) (#27324)

* Adding id and name to metadata response

* Adding name to response

* update to types

* Adding support for displayNames to waffle map

* fixing a bug when _source is missing

* Fixing tests

* making the metadata response manditory

* Fixes from PR review

* Fixing typing errors related to displayName being required part of path

* Changing 'Loading data for xxx' to 'Loading data'

* Changing InfraNodePath.displayName to InfraNodePath.label

* Change groups to use the label instead of value

* Fixing merge changes
This commit is contained in:
Chris Cowan 2018-12-17 15:54:52 -07:00 committed by GitHub
parent 5425d289aa
commit 489119cbbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 334 additions and 138 deletions

View file

@ -18,7 +18,8 @@ interface Props {
metrics: InfraMetricData[]; metrics: InfraMetricData[];
layouts: InfraMetricLayout[]; layouts: InfraMetricLayout[];
loading: boolean; loading: boolean;
nodeName: string; nodeId: string;
label: string;
onChangeRangeTime?: (time: metricTimeActions.MetricRangeTimeState) => void; onChangeRangeTime?: (time: metricTimeActions.MetricRangeTimeState) => void;
intl: InjectedIntl; intl: InjectedIntl;
} }
@ -41,15 +42,10 @@ export const Metrics = injectI18n(
<InfraLoadingPanel <InfraLoadingPanel
height="100vh" height="100vh"
width="auto" width="auto"
text={intl.formatMessage( text={intl.formatMessage({
{ id: 'xpack.infra.metrics.loadingNodeDataText',
id: 'xpack.infra.metrics.loadingNodeDataText', defaultMessage: 'Loading data',
defaultMessage: 'Loading data for {nodeName}', })}
},
{
nodeName: this.props.nodeName,
}
)}
/> />
); );
} }

View file

@ -43,7 +43,7 @@ export const GroupOfNodes: React.SFC<Props> = ({
<Nodes> <Nodes>
{group.nodes.map(node => ( {group.nodes.map(node => (
<Node <Node
key={node.id} key={node.pathId}
options={options} options={options}
squareSize={group.squareSize} squareSize={group.squareSize}
node={node} node={node}

View file

@ -25,18 +25,18 @@ interface Props {
export const NodeContextMenu = injectI18n( export const NodeContextMenu = injectI18n(
({ options, timeRange, children, node, isPopoverOpen, closePopover, nodeType, intl }: Props) => { ({ options, timeRange, children, node, isPopoverOpen, closePopover, nodeType, intl }: Props) => {
const nodeName = node.path.length > 0 ? node.path[node.path.length - 1].value : undefined; const nodeId = node.path.length > 0 ? node.path[node.path.length - 1].value : undefined;
const nodeLogsUrl = nodeName const nodeLogsUrl = nodeId
? getNodeLogsUrl({ ? getNodeLogsUrl({
nodeType, nodeType,
nodeName, nodeId,
time: timeRange.to, time: timeRange.to,
}) })
: undefined; : undefined;
const nodeDetailUrl = nodeName const nodeDetailUrl = nodeId
? getNodeDetailUrl({ ? getNodeDetailUrl({
nodeType, nodeType,
nodeName, nodeId,
from: timeRange.from, from: timeRange.from,
to: timeRange.to, to: timeRange.to,
}) })
@ -76,7 +76,7 @@ export const NodeContextMenu = injectI18n(
return ( return (
<EuiPopover <EuiPopover
closePopover={closePopover} closePopover={closePopover}
id={`${node.id}-popover`} id={`${node.pathId}-popover`}
isOpen={isPopoverOpen} isOpen={isPopoverOpen}
button={children} button={children}
panelPaddingSize="none" panelPaddingSize="none"

View file

@ -10,9 +10,12 @@ export const metadataQuery = gql`
query MetadataQuery($sourceId: ID!, $nodeId: String!, $nodeType: InfraNodeType!) { query MetadataQuery($sourceId: ID!, $nodeId: String!, $nodeType: InfraNodeType!) {
source(id: $sourceId) { source(id: $sourceId) {
id id
metadataByNode(nodeName: $nodeId, nodeType: $nodeType) { metadataByNode(nodeId: $nodeId, nodeType: $nodeType) {
name name
source features {
name
source
}
} }
} }
} }

View file

@ -21,6 +21,7 @@ interface WithMetadataProps {
} }
interface WithMetadataArgs { interface WithMetadataArgs {
name: string;
filteredLayouts: InfraMetricLayout[]; filteredLayouts: InfraMetricLayout[];
error?: string | undefined; error?: string | undefined;
loading: boolean; loading: boolean;
@ -45,8 +46,9 @@ export const WithMetadata = ({
> >
{({ data, error, loading }) => { {({ data, error, loading }) => {
const metadata = data && data.source && data.source.metadataByNode; const metadata = data && data.source && data.source.metadataByNode;
const filteredLayouts = getFilteredLayouts(layouts, metadata); const filteredLayouts = (metadata && getFilteredLayouts(layouts, metadata.features)) || [];
return children({ return children({
name: (metadata && metadata.name) || '',
filteredLayouts, filteredLayouts,
error: error && error.message, error: error && error.message,
loading, loading,
@ -58,7 +60,7 @@ export const WithMetadata = ({
const getFilteredLayouts = ( const getFilteredLayouts = (
layouts: InfraMetricLayout[], layouts: InfraMetricLayout[],
metadata: Array<MetadataQuery.MetadataByNode | null> | undefined metadata: Array<MetadataQuery.Features | null> | undefined
): InfraMetricLayout[] => { ): InfraMetricLayout[] => {
if (!metadata) { if (!metadata) {
return layouts; return layouts;

View file

@ -51,7 +51,7 @@ function findOrCreateGroupWithNodes(
? i18n.translate('xpack.infra.nodesToWaffleMap.groupsWithNodes.allName', { ? i18n.translate('xpack.infra.nodesToWaffleMap.groupsWithNodes.allName', {
defaultMessage: 'All', defaultMessage: 'All',
}) })
: last(path).value, : last(path).label,
count: 0, count: 0,
width: 0, width: 0,
squareSize: 0, squareSize: 0,
@ -75,7 +75,7 @@ function findOrCreateGroupWithGroups(
? i18n.translate('xpack.infra.nodesToWaffleMap.groupsWithGroups.allName', { ? i18n.translate('xpack.infra.nodesToWaffleMap.groupsWithGroups.allName', {
defaultMessage: 'All', defaultMessage: 'All',
}) })
: last(path).value, : last(path).label,
count: 0, count: 0,
width: 0, width: 0,
squareSize: 0, squareSize: 0,
@ -84,10 +84,12 @@ function findOrCreateGroupWithGroups(
} }
function createWaffleMapNode(node: InfraNode): InfraWaffleMapNode { function createWaffleMapNode(node: InfraNode): InfraWaffleMapNode {
const nodePathItem = last(node.path);
return { return {
id: node.path.map(p => p.value).join('/'), pathId: node.path.map(p => p.value).join('/'),
path: node.path, path: node.path,
name: last(node.path).value, id: nodePathItem.value,
name: nodePathItem.label,
metric: node.metric, metric: node.metric,
}; };
} }

View file

@ -20,6 +20,7 @@ export const waffleNodesQuery = gql`
nodes(path: $path, metric: $metric) { nodes(path: $path, metric: $metric) {
path { path {
value value
label
} }
metric { metric {
name name

View file

@ -114,7 +114,7 @@
"description": "A hierarchy of metadata entries by node", "description": "A hierarchy of metadata entries by node",
"args": [ "args": [
{ {
"name": "nodeName", "name": "nodeId",
"description": "", "description": "",
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",
@ -137,11 +137,7 @@
"type": { "type": {
"kind": "NON_NULL", "kind": "NON_NULL",
"name": null, "name": null,
"ofType": { "ofType": { "kind": "OBJECT", "name": "InfraNodeMetadata", "ofType": null }
"kind": "LIST",
"name": null,
"ofType": { "kind": "OBJECT", "name": "InfraNodeMetadata", "ofType": null }
}
}, },
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
@ -775,6 +771,61 @@
"kind": "OBJECT", "kind": "OBJECT",
"name": "InfraNodeMetadata", "name": "InfraNodeMetadata",
"description": "One metadata entry for a node.", "description": "One metadata entry for a node.",
"fields": [
{
"name": "id",
"description": "",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "ID", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "name",
"description": "",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "features",
"description": "",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "OBJECT", "name": "InfraNodeFeature", "ofType": null }
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "InfraNodeFeature",
"description": "",
"fields": [ "fields": [
{ {
"name": "name", "name": "name",
@ -1542,6 +1593,18 @@
}, },
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "displayName",
"description": "",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"inputFields": null, "inputFields": null,

View file

@ -23,7 +23,7 @@ export interface InfraSource {
/** The status of the source */ /** The status of the source */
status: InfraSourceStatus; status: InfraSourceStatus;
/** A hierarchy of metadata entries by node */ /** A hierarchy of metadata entries by node */
metadataByNode: (InfraNodeMetadata | null)[]; metadataByNode: InfraNodeMetadata;
/** A consecutive span of log entries surrounding a point in time */ /** A consecutive span of log entries surrounding a point in time */
logEntriesAround: InfraLogEntryInterval; logEntriesAround: InfraLogEntryInterval;
/** A consecutive span of log entries within an interval */ /** A consecutive span of log entries within an interval */
@ -89,6 +89,14 @@ export interface InfraIndexField {
} }
/** One metadata entry for a node. */ /** One metadata entry for a node. */
export interface InfraNodeMetadata { export interface InfraNodeMetadata {
id: string;
name: string;
features: InfraNodeFeature[];
}
export interface InfraNodeFeature {
name: string; name: string;
source: string; source: string;
@ -175,6 +183,8 @@ export interface InfraNode {
export interface InfraNodePath { export interface InfraNodePath {
value: string; value: string;
label: string;
} }
export interface InfraNodeMetric { export interface InfraNodeMetric {
@ -252,7 +262,7 @@ export interface SourceQueryArgs {
id: string; id: string;
} }
export interface MetadataByNodeInfraSourceArgs { export interface MetadataByNodeInfraSourceArgs {
nodeName: string; nodeId: string;
nodeType: InfraNodeType; nodeType: InfraNodeType;
} }
@ -416,7 +426,7 @@ export namespace MetadataQuery {
id: string; id: string;
metadataByNode: (MetadataByNode | null)[]; metadataByNode: MetadataByNode;
}; };
export type MetadataByNode = { export type MetadataByNode = {
@ -424,6 +434,14 @@ export namespace MetadataQuery {
name: string; name: string;
features: Features[];
};
export type Features = {
__typename?: 'InfraNodeFeature';
name: string;
source: string; source: string;
}; };
} }
@ -517,6 +535,8 @@ export namespace WaffleNodesQuery {
__typename?: 'InfraNodePath'; __typename?: 'InfraNodePath';
value: string; value: string;
label: string;
}; };
export type Metric = { export type Metric = {

View file

@ -100,6 +100,7 @@ export interface InfraField {
export type InfraWaffleData = InfraWaffleMapGroup[]; export type InfraWaffleData = InfraWaffleMapGroup[];
export interface InfraWaffleMapNode { export interface InfraWaffleMapNode {
pathId: string;
id: string; id: string;
name: string; name: string;
path: InfraNodePath[]; path: InfraNodePath[];

View file

@ -21,11 +21,11 @@ export class LinkToPage extends React.Component<LinkToPageProps> {
return ( return (
<Switch> <Switch>
<Route <Route
path={`${match.url}/:nodeType(host|container|pod)-logs/:nodeName`} path={`${match.url}/:nodeType(host|container|pod)-logs/:nodeId`}
component={RedirectToNodeLogs} component={RedirectToNodeLogs}
/> />
<Route <Route
path={`${match.url}/:nodeType(host|container|pod)-detail/:nodeName`} path={`${match.url}/:nodeType(host|container|pod)-detail/:nodeId`}
component={RedirectToNodeDetail} component={RedirectToNodeDetail}
/> />
<Redirect to="/home" /> <Redirect to="/home" />

View file

@ -12,13 +12,13 @@ import { InfraNodeType } from '../../graphql/types';
import { getFromFromLocation, getToFromLocation } from './query_params'; import { getFromFromLocation, getToFromLocation } from './query_params';
type RedirectToNodeDetailProps = RouteComponentProps<{ type RedirectToNodeDetailProps = RouteComponentProps<{
nodeName: string; nodeId: string;
nodeType: InfraNodeType; nodeType: InfraNodeType;
}>; }>;
export const RedirectToNodeDetail = ({ export const RedirectToNodeDetail = ({
match: { match: {
params: { nodeName, nodeType }, params: { nodeId, nodeType },
}, },
location, location,
}: RedirectToNodeDetailProps) => { }: RedirectToNodeDetailProps) => {
@ -27,20 +27,20 @@ export const RedirectToNodeDetail = ({
getToFromLocation(location) getToFromLocation(location)
)(''); )('');
return <Redirect to={`/metrics/${nodeType}/${nodeName}?${searchString}`} />; return <Redirect to={`/metrics/${nodeType}/${nodeId}?${searchString}`} />;
}; };
export const getNodeDetailUrl = ({ export const getNodeDetailUrl = ({
nodeType, nodeType,
nodeName, nodeId,
to, to,
from, from,
}: { }: {
nodeType: InfraNodeType; nodeType: InfraNodeType;
nodeName: string; nodeId: string;
to?: number; to?: number;
from?: number; from?: number;
}) => { }) => {
const args = to && from ? `?to=${to}&from=${from}` : ''; const args = to && from ? `?to=${to}&from=${from}` : '';
return `#/link-to/${nodeType}-detail/${nodeName}${args}`; return `#/link-to/${nodeType}-detail/${nodeId}${args}`;
}; };

View file

@ -17,7 +17,7 @@ import { InfraNodeType } from '../../graphql/types';
import { getTimeFromLocation } from './query_params'; import { getTimeFromLocation } from './query_params';
type RedirectToNodeLogsType = RouteComponentProps<{ type RedirectToNodeLogsType = RouteComponentProps<{
nodeName: string; nodeId: string;
nodeType: InfraNodeType; nodeType: InfraNodeType;
}>; }>;
@ -28,7 +28,7 @@ interface RedirectToNodeLogsProps extends RedirectToNodeLogsType {
export const RedirectToNodeLogs = injectI18n( export const RedirectToNodeLogs = injectI18n(
({ ({
match: { match: {
params: { nodeName, nodeType }, params: { nodeId, nodeType },
}, },
location, location,
intl, intl,
@ -52,7 +52,7 @@ export const RedirectToNodeLogs = injectI18n(
} }
const searchString = compose( const searchString = compose(
replaceLogFilterInQueryString(`${configuredFields[nodeType]}: ${nodeName}`), replaceLogFilterInQueryString(`${configuredFields[nodeType]}: ${nodeId}`),
replaceLogPositionInQueryString(getTimeFromLocation(location)) replaceLogPositionInQueryString(getTimeFromLocation(location))
)(''); )('');
@ -63,11 +63,11 @@ export const RedirectToNodeLogs = injectI18n(
); );
export const getNodeLogsUrl = ({ export const getNodeLogsUrl = ({
nodeName, nodeId,
nodeType, nodeType,
time, time,
}: { }: {
nodeName: string; nodeId: string;
nodeType: InfraNodeType; nodeType: InfraNodeType;
time?: number; time?: number;
}) => [`#/link-to/${nodeType}-logs/`, nodeName, ...(time ? [`?time=${time}`] : [])].join(''); }) => [`#/link-to/${nodeType}-logs/`, nodeId, ...(time ? [`?time=${time}`] : [])].join('');

View file

@ -63,7 +63,7 @@ export const MetricDetail = withTheme(
public render() { public render() {
const { intl } = this.props; const { intl } = this.props;
const nodeName = this.props.match.params.node; const nodeId = this.props.match.params.node;
const nodeType = this.props.match.params.type as InfraNodeType; const nodeType = this.props.match.params.type as InfraNodeType;
const layoutCreator = layoutCreators[nodeType]; const layoutCreator = layoutCreators[nodeType];
if (!layoutCreator) { if (!layoutCreator) {
@ -82,40 +82,40 @@ export const MetricDetail = withTheme(
); );
} }
const layouts = layoutCreator(this.props.theme); const layouts = layoutCreator(this.props.theme);
const breadcrumbs = [{ text: nodeName }];
return ( return (
<ColumnarPage> <WithOptions>
<Header {({ sourceId }) => (
appendSections={<InfrastructureBetaBadgeHeaderSection />} <WithMetricsTime resetOnUnmount>
breadcrumbs={breadcrumbs} {({
/> currentTimeRange,
<WithMetricsTimeUrlState /> isAutoReloading,
<DetailPageContent> setRangeTime,
<WithOptions> startMetricsAutoReload,
{({ sourceId }) => ( stopMetricsAutoReload,
<WithMetricsTime resetOnUnmount> }) => (
{({ <WithMetadata
currentTimeRange, layouts={layouts}
isAutoReloading, sourceId={sourceId}
setRangeTime, nodeType={nodeType}
startMetricsAutoReload, nodeId={nodeId}
stopMetricsAutoReload, >
}) => ( {({ name, filteredLayouts, loading: metadataLoading }) => {
<WithMetadata const breadcrumbs = [{ text: name }];
layouts={layouts} return (
sourceId={sourceId} <ColumnarPage>
nodeType={nodeType} <Header
nodeId={nodeName} appendSections={<InfrastructureBetaBadgeHeaderSection />}
> breadcrumbs={breadcrumbs}
{({ filteredLayouts, loading: metadataLoading }) => { />
return ( <WithMetricsTimeUrlState />
<DetailPageContent>
<WithMetrics <WithMetrics
layouts={filteredLayouts} layouts={filteredLayouts}
sourceId={sourceId} sourceId={sourceId}
timerange={currentTimeRange as InfraTimerangeInput} timerange={currentTimeRange as InfraTimerangeInput}
nodeType={nodeType} nodeType={nodeType}
nodeId={nodeName} nodeId={nodeId}
> >
{({ metrics, error, loading }) => { {({ metrics, error, loading }) => {
if (error) { if (error) {
@ -126,7 +126,7 @@ export const MetricDetail = withTheme(
<MetricsSideNav <MetricsSideNav
layouts={filteredLayouts} layouts={filteredLayouts}
loading={metadataLoading} loading={metadataLoading}
nodeName={nodeName} nodeName={name}
handleClick={this.handleClick} handleClick={this.handleClick}
/> />
<AutoSizer content={false} bounds detectAnyWindowResize> <AutoSizer content={false} bounds detectAnyWindowResize>
@ -139,7 +139,7 @@ export const MetricDetail = withTheme(
<MetricsTitleTimeRangeContainer> <MetricsTitleTimeRangeContainer>
<EuiHideFor sizes={['xs', 's']}> <EuiHideFor sizes={['xs', 's']}>
<EuiTitle size="m"> <EuiTitle size="m">
<h1>{nodeName}</h1> <h1>{name}</h1>
</EuiTitle> </EuiTitle>
</EuiHideFor> </EuiHideFor>
<MetricsTimeControls <MetricsTimeControls
@ -155,7 +155,8 @@ export const MetricDetail = withTheme(
<EuiPageContentWithRelative> <EuiPageContentWithRelative>
<Metrics <Metrics
nodeName={nodeName} label={name}
nodeId={nodeId}
layouts={filteredLayouts} layouts={filteredLayouts}
metrics={metrics} metrics={metrics}
loading={ loading={
@ -175,15 +176,15 @@ export const MetricDetail = withTheme(
); );
}} }}
</WithMetrics> </WithMetrics>
); </DetailPageContent>
}} </ColumnarPage>
</WithMetadata> );
)} }}
</WithMetricsTime> </WithMetadata>
)} )}
</WithOptions> </WithMetricsTime>
</DetailPageContent> )}
</ColumnarPage> </WithOptions>
); );
} }

View file

@ -23,7 +23,7 @@ export const createMetadataResolvers = (libs: {
} => ({ } => ({
InfraSource: { InfraSource: {
async metadataByNode(source, args, { req }) { async metadataByNode(source, args, { req }) {
const result = await libs.metadata.getMetadata(req, source.id, args.nodeName, args.nodeType); const result = await libs.metadata.getMetadata(req, source.id, args.nodeId, args.nodeType);
return result; return result;
}, },
}, },

View file

@ -9,12 +9,18 @@ import gql from 'graphql-tag';
export const metadataSchema = gql` export const metadataSchema = gql`
"One metadata entry for a node." "One metadata entry for a node."
type InfraNodeMetadata { type InfraNodeMetadata {
id: ID!
name: String!
features: [InfraNodeFeature!]!
}
type InfraNodeFeature {
name: String! name: String!
source: String! source: String!
} }
extend type InfraSource { extend type InfraSource {
"A hierarchy of metadata entries by node" "A hierarchy of metadata entries by node"
metadataByNode(nodeName: String!, nodeType: InfraNodeType!): [InfraNodeMetadata]! metadataByNode(nodeId: String!, nodeType: InfraNodeType!): InfraNodeMetadata!
} }
`; `;

View file

@ -14,6 +14,7 @@ export const nodesSchema: any = gql`
type InfraNodePath { type InfraNodePath {
value: String! value: String!
label: String
} }
type InfraNode { type InfraNode {

View file

@ -51,7 +51,7 @@ export interface InfraSource {
/** The status of the source */ /** The status of the source */
status: InfraSourceStatus; status: InfraSourceStatus;
/** A hierarchy of metadata entries by node */ /** A hierarchy of metadata entries by node */
metadataByNode: (InfraNodeMetadata | null)[]; metadataByNode: InfraNodeMetadata;
/** A consecutive span of log entries surrounding a point in time */ /** A consecutive span of log entries surrounding a point in time */
logEntriesAround: InfraLogEntryInterval; logEntriesAround: InfraLogEntryInterval;
/** A consecutive span of log entries within an interval */ /** A consecutive span of log entries within an interval */
@ -117,6 +117,14 @@ export interface InfraIndexField {
} }
/** One metadata entry for a node. */ /** One metadata entry for a node. */
export interface InfraNodeMetadata { export interface InfraNodeMetadata {
id: string;
name: string;
features: InfraNodeFeature[];
}
export interface InfraNodeFeature {
name: string; name: string;
source: string; source: string;
@ -203,6 +211,8 @@ export interface InfraNode {
export interface InfraNodePath { export interface InfraNodePath {
value: string; value: string;
label: string;
} }
export interface InfraNodeMetric { export interface InfraNodeMetric {
@ -280,7 +290,7 @@ export interface SourceQueryArgs {
id: string; id: string;
} }
export interface MetadataByNodeInfraSourceArgs { export interface MetadataByNodeInfraSourceArgs {
nodeName: string; nodeId: string;
nodeType: InfraNodeType; nodeType: InfraNodeType;
} }
@ -461,7 +471,7 @@ export namespace InfraSourceResolvers {
/** The status of the source */ /** The status of the source */
status?: StatusResolver<InfraSourceStatus, TypeParent, Context>; status?: StatusResolver<InfraSourceStatus, TypeParent, Context>;
/** A hierarchy of metadata entries by node */ /** A hierarchy of metadata entries by node */
metadataByNode?: MetadataByNodeResolver<(InfraNodeMetadata | null)[], TypeParent, Context>; metadataByNode?: MetadataByNodeResolver<InfraNodeMetadata, TypeParent, Context>;
/** A consecutive span of log entries surrounding a point in time */ /** A consecutive span of log entries surrounding a point in time */
logEntriesAround?: LogEntriesAroundResolver<InfraLogEntryInterval, TypeParent, Context>; logEntriesAround?: LogEntriesAroundResolver<InfraLogEntryInterval, TypeParent, Context>;
/** A consecutive span of log entries within an interval */ /** A consecutive span of log entries within an interval */
@ -490,12 +500,12 @@ export namespace InfraSourceResolvers {
Context = InfraContext Context = InfraContext
> = Resolver<R, Parent, Context>; > = Resolver<R, Parent, Context>;
export type MetadataByNodeResolver< export type MetadataByNodeResolver<
R = (InfraNodeMetadata | null)[], R = InfraNodeMetadata,
Parent = InfraSource, Parent = InfraSource,
Context = InfraContext Context = InfraContext
> = Resolver<R, Parent, Context, MetadataByNodeArgs>; > = Resolver<R, Parent, Context, MetadataByNodeArgs>;
export interface MetadataByNodeArgs { export interface MetadataByNodeArgs {
nodeName: string; nodeId: string;
nodeType: InfraNodeType; nodeType: InfraNodeType;
} }
@ -746,6 +756,32 @@ export namespace InfraIndexFieldResolvers {
/** One metadata entry for a node. */ /** One metadata entry for a node. */
export namespace InfraNodeMetadataResolvers { export namespace InfraNodeMetadataResolvers {
export interface Resolvers<Context = InfraContext, TypeParent = InfraNodeMetadata> { export interface Resolvers<Context = InfraContext, TypeParent = InfraNodeMetadata> {
id?: IdResolver<string, TypeParent, Context>;
name?: NameResolver<string, TypeParent, Context>;
features?: FeaturesResolver<InfraNodeFeature[], TypeParent, Context>;
}
export type IdResolver<R = string, Parent = InfraNodeMetadata, Context = InfraContext> = Resolver<
R,
Parent,
Context
>;
export type NameResolver<
R = string,
Parent = InfraNodeMetadata,
Context = InfraContext
> = Resolver<R, Parent, Context>;
export type FeaturesResolver<
R = InfraNodeFeature[],
Parent = InfraNodeMetadata,
Context = InfraContext
> = Resolver<R, Parent, Context>;
}
export namespace InfraNodeFeatureResolvers {
export interface Resolvers<Context = InfraContext, TypeParent = InfraNodeFeature> {
name?: NameResolver<string, TypeParent, Context>; name?: NameResolver<string, TypeParent, Context>;
source?: SourceResolver<string, TypeParent, Context>; source?: SourceResolver<string, TypeParent, Context>;
@ -753,12 +789,12 @@ export namespace InfraNodeMetadataResolvers {
export type NameResolver< export type NameResolver<
R = string, R = string,
Parent = InfraNodeMetadata, Parent = InfraNodeFeature,
Context = InfraContext Context = InfraContext
> = Resolver<R, Parent, Context>; > = Resolver<R, Parent, Context>;
export type SourceResolver< export type SourceResolver<
R = string, R = string,
Parent = InfraNodeMetadata, Parent = InfraNodeFeature,
Context = InfraContext Context = InfraContext
> = Resolver<R, Parent, Context>; > = Resolver<R, Parent, Context>;
} }
@ -1012,6 +1048,8 @@ export namespace InfraNodeResolvers {
export namespace InfraNodePathResolvers { export namespace InfraNodePathResolvers {
export interface Resolvers<Context = InfraContext, TypeParent = InfraNodePath> { export interface Resolvers<Context = InfraContext, TypeParent = InfraNodePath> {
value?: ValueResolver<string, TypeParent, Context>; value?: ValueResolver<string, TypeParent, Context>;
label?: DisplayNameResolver<string, TypeParent, Context>;
} }
export type ValueResolver<R = string, Parent = InfraNodePath, Context = InfraContext> = Resolver< export type ValueResolver<R = string, Parent = InfraNodePath, Context = InfraContext> = Resolver<
@ -1019,6 +1057,11 @@ export namespace InfraNodePathResolvers {
Parent, Parent,
Context Context
>; >;
export type DisplayNameResolver<
R = string,
Parent = InfraNodePath,
Context = InfraContext
> = Resolver<R, Parent, Context>;
} }
export namespace InfraNodeMetricResolvers { export namespace InfraNodeMetricResolvers {

View file

@ -7,17 +7,23 @@
import { InfraSourceConfiguration } from '../../sources'; import { InfraSourceConfiguration } from '../../sources';
import { InfraFrameworkRequest, InfraMetadataAggregationBucket } from '../framework'; import { InfraFrameworkRequest, InfraMetadataAggregationBucket } from '../framework';
export interface InfraMetricsAdapterResponse {
id: string;
name?: string;
buckets: InfraMetadataAggregationBucket[];
}
export interface InfraMetadataAdapter { export interface InfraMetadataAdapter {
getMetricMetadata( getMetricMetadata(
req: InfraFrameworkRequest, req: InfraFrameworkRequest,
sourceConfiguration: InfraSourceConfiguration, sourceConfiguration: InfraSourceConfiguration,
nodeName: string, nodeId: string,
nodeType: string nodeType: string
): Promise<InfraMetadataAggregationBucket[]>; ): Promise<InfraMetricsAdapterResponse>;
getLogMetadata( getLogMetadata(
req: InfraFrameworkRequest, req: InfraFrameworkRequest,
sourceConfiguration: InfraSourceConfiguration, sourceConfiguration: InfraSourceConfiguration,
nodeName: string, nodeId: string,
nodeType: string nodeType: string
): Promise<InfraMetadataAggregationBucket[]>; ): Promise<InfraMetricsAdapterResponse>;
} }

View file

@ -4,14 +4,15 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { first, get } from 'lodash';
import { InfraSourceConfiguration } from '../../sources'; import { InfraSourceConfiguration } from '../../sources';
import { import {
InfraBackendFrameworkAdapter, InfraBackendFrameworkAdapter,
InfraFrameworkRequest, InfraFrameworkRequest,
InfraMetadataAggregationBucket,
InfraMetadataAggregationResponse, InfraMetadataAggregationResponse,
} from '../framework'; } from '../framework';
import { InfraMetadataAdapter } from './adapter_types'; import { NAME_FIELDS } from '../nodes/constants';
import { InfraMetadataAdapter, InfraMetricsAdapterResponse } from './adapter_types';
export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter { export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
private framework: InfraBackendFrameworkAdapter; private framework: InfraBackendFrameworkAdapter;
@ -22,9 +23,9 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
public async getMetricMetadata( public async getMetricMetadata(
req: InfraFrameworkRequest, req: InfraFrameworkRequest,
sourceConfiguration: InfraSourceConfiguration, sourceConfiguration: InfraSourceConfiguration,
nodeName: string, nodeId: string,
nodeType: 'host' | 'container' | 'pod' nodeType: 'host' | 'container' | 'pod'
): Promise<InfraMetadataAggregationBucket[]> { ): Promise<InfraMetricsAdapterResponse> {
const idFieldName = getIdFieldName(sourceConfiguration, nodeType); const idFieldName = getIdFieldName(sourceConfiguration, nodeType);
const metricQuery = { const metricQuery = {
index: sourceConfiguration.metricAlias, index: sourceConfiguration.metricAlias,
@ -32,11 +33,12 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
query: { query: {
bool: { bool: {
filter: { filter: {
term: { [idFieldName]: nodeName }, term: { [idFieldName]: nodeId },
}, },
}, },
}, },
size: 0, size: 1,
_source: [NAME_FIELDS[nodeType]],
aggs: { aggs: {
metrics: { metrics: {
terms: { terms: {
@ -61,17 +63,26 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
{ metrics?: InfraMetadataAggregationResponse } { metrics?: InfraMetadataAggregationResponse }
>(req, 'search', metricQuery); >(req, 'search', metricQuery);
return response.aggregations && response.aggregations.metrics const buckets =
? response.aggregations.metrics.buckets response.aggregations && response.aggregations.metrics
: []; ? response.aggregations.metrics.buckets
: [];
const sampleDoc = first(response.hits.hits);
return {
id: nodeId,
name: get(sampleDoc, `_source.${NAME_FIELDS[nodeType]}`),
buckets,
};
} }
public async getLogMetadata( public async getLogMetadata(
req: InfraFrameworkRequest, req: InfraFrameworkRequest,
sourceConfiguration: InfraSourceConfiguration, sourceConfiguration: InfraSourceConfiguration,
nodeName: string, nodeId: string,
nodeType: 'host' | 'container' | 'pod' nodeType: 'host' | 'container' | 'pod'
): Promise<InfraMetadataAggregationBucket[]> { ): Promise<InfraMetricsAdapterResponse> {
const idFieldName = getIdFieldName(sourceConfiguration, nodeType); const idFieldName = getIdFieldName(sourceConfiguration, nodeType);
const logQuery = { const logQuery = {
index: sourceConfiguration.logAlias, index: sourceConfiguration.logAlias,
@ -79,11 +90,12 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
query: { query: {
bool: { bool: {
filter: { filter: {
term: { [idFieldName]: nodeName }, term: { [idFieldName]: nodeId },
}, },
}, },
}, },
size: 0, size: 1,
_source: [NAME_FIELDS[nodeType]],
aggs: { aggs: {
metrics: { metrics: {
terms: { terms: {
@ -108,9 +120,18 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
{ metrics?: InfraMetadataAggregationResponse } { metrics?: InfraMetadataAggregationResponse }
>(req, 'search', logQuery); >(req, 'search', logQuery);
return response.aggregations && response.aggregations.metrics const buckets =
? response.aggregations.metrics.buckets response.aggregations && response.aggregations.metrics
: []; ? response.aggregations.metrics.buckets
: [];
const sampleDoc = first(response.hits.hits);
return {
id: nodeId,
name: get(sampleDoc, `_source.${NAME_FIELDS[nodeType]}`),
buckets,
};
} }
} }

View file

@ -4,6 +4,12 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { InfraNodeType } from '../../../graphql/types';
// TODO: Make NODE_REQUEST_PARTITION_SIZE configurable from kibana.yml // TODO: Make NODE_REQUEST_PARTITION_SIZE configurable from kibana.yml
export const NODE_REQUEST_PARTITION_SIZE = 75; export const NODE_REQUEST_PARTITION_SIZE = 75;
export const NODE_REQUEST_PARTITION_FACTOR = 1.2; export const NODE_REQUEST_PARTITION_FACTOR = 1.2;
export const NAME_FIELDS = {
[InfraNodeType.host]: 'host.name',
[InfraNodeType.pod]: 'kubernetes.pod.name',
[InfraNodeType.container]: 'docker.container.name',
};

View file

@ -4,12 +4,13 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { last } from 'lodash'; import { get, last } from 'lodash';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { InfraNode, InfraNodeMetric } from '../../../../graphql/types'; import { InfraNode, InfraNodeMetric } from '../../../../graphql/types';
import { InfraBucket, InfraNodeRequestOptions } from '../adapter_types'; import { InfraBucket, InfraNodeRequestOptions } from '../adapter_types';
import { NAME_FIELDS } from '../constants';
import { getBucketSizeInSeconds } from './get_bucket_size_in_seconds'; import { getBucketSizeInSeconds } from './get_bucket_size_in_seconds';
// TODO: Break these function into seperate files and expand beyond just documnet count // TODO: Break these function into seperate files and expand beyond just documnet count
@ -57,8 +58,9 @@ export function createNodeItem(
node: InfraBucket, node: InfraBucket,
bucket: InfraBucket bucket: InfraBucket
): InfraNode { ): InfraNode {
const nodeDoc = get(node, ['nodeDetails', 'hits', 'hits', 0]);
return { return {
metric: createNodeMetrics(options, node, bucket), metric: createNodeMetrics(options, node, bucket),
path: [{ value: node.key }], path: [{ value: node.key, label: get(nodeDoc, `_source.${NAME_FIELDS[options.nodeType]}`) }],
} as InfraNode; } as InfraNode;
} }

View file

@ -28,8 +28,8 @@ export function extractGroupPaths(
(b: InfraBucket): InfraNode => { (b: InfraBucket): InfraNode => {
const innerNode = createNodeItem(options, node, b); const innerNode = createNodeItem(options, node, b);
const nodePath = [ const nodePath = [
{ value: bucket.key.toString() }, { value: bucket.key.toString(), label: bucket.key.toString() },
{ value: b.key.toString() }, { value: b.key.toString(), label: b.key.toString() },
].concat(innerNode.path); ].concat(innerNode.path);
return { return {
...innerNode, ...innerNode,
@ -40,7 +40,7 @@ export function extractGroupPaths(
); );
} }
const nodeItem = createNodeItem(options, node, bucket); const nodeItem = createNodeItem(options, node, bucket);
const path = [{ value: key }].concat(nodeItem.path); const path = [{ value: key, label: key }].concat(nodeItem.path);
return acc.concat({ return acc.concat({
...nodeItem, ...nodeItem,
path, path,

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { cloneDeep, set } from 'lodash'; import { cloneDeep, get, set } from 'lodash';
import { InfraPathFilterInput, InfraPathInput } from '../../../../../graphql/types'; import { InfraPathFilterInput, InfraPathInput } from '../../../../../graphql/types';
import { import {
@ -18,7 +18,7 @@ export const groupByProcessor = (options: InfraProcesorRequestOptions) => {
return (doc: InfraESSearchBody) => { return (doc: InfraESSearchBody) => {
const result = cloneDeep(doc); const result = cloneDeep(doc);
const { groupBy } = options.nodeOptions; const { groupBy } = options.nodeOptions;
let aggs = {}; let aggs = get(result, 'aggs.waffle.aggs.nodes.aggs', {});
set(result, 'aggs.waffle.aggs.nodes.aggs', aggs); set(result, 'aggs.waffle.aggs.nodes.aggs', aggs);
groupBy.forEach((grouping: InfraPathInput, index: number) => { groupBy.forEach((grouping: InfraPathInput, index: number) => {
if (isGroupByTerms(grouping)) { if (isGroupByTerms(grouping)) {
@ -49,7 +49,7 @@ export const groupByProcessor = (options: InfraProcesorRequestOptions) => {
), ),
}, },
}; };
set(aggs, `${grouping.id}`, filtersAgg); set(aggs, `path_${index}`, filtersAgg);
aggs = filtersAgg.aggs; aggs = filtersAgg.aggs;
} }
}); });

View file

@ -7,7 +7,11 @@
import { cloneDeep, set } from 'lodash'; import { cloneDeep, set } from 'lodash';
import { InfraESSearchBody, InfraNodeType, InfraProcesorRequestOptions } from '../../adapter_types'; import { InfraESSearchBody, InfraNodeType, InfraProcesorRequestOptions } from '../../adapter_types';
import { NODE_REQUEST_PARTITION_FACTOR, NODE_REQUEST_PARTITION_SIZE } from '../../constants'; import {
NAME_FIELDS,
NODE_REQUEST_PARTITION_FACTOR,
NODE_REQUEST_PARTITION_SIZE,
} from '../../constants';
const nodeTypeToField = (options: InfraProcesorRequestOptions): string => { const nodeTypeToField = (options: InfraProcesorRequestOptions): string => {
const { fields } = options.nodeOptions.sourceConfiguration; const { fields } = options.nodeOptions.sourceConfiguration;
@ -22,6 +26,7 @@ const nodeTypeToField = (options: InfraProcesorRequestOptions): string => {
}; };
export const nodesProcessor = (options: InfraProcesorRequestOptions) => { export const nodesProcessor = (options: InfraProcesorRequestOptions) => {
const { fields } = options.nodeOptions.sourceConfiguration;
return (doc: InfraESSearchBody) => { return (doc: InfraESSearchBody) => {
const result = cloneDeep(doc); const result = cloneDeep(doc);
const field = nodeTypeToField(options); const field = nodeTypeToField(options);
@ -35,6 +40,16 @@ export const nodesProcessor = (options: InfraProcesorRequestOptions) => {
order: { _key: 'asc' }, order: { _key: 'asc' },
size: NODE_REQUEST_PARTITION_SIZE * NODE_REQUEST_PARTITION_FACTOR, size: NODE_REQUEST_PARTITION_SIZE * NODE_REQUEST_PARTITION_FACTOR,
}); });
set(result, 'aggs.waffle.aggs.nodes.aggs', {
nodeDetails: {
top_hits: {
size: 1,
_source: { includes: [NAME_FIELDS[options.nodeType]] },
sort: [{ [fields.timestamp]: { order: 'desc' } }],
},
},
});
return result; return result;
}; };
}; };

View file

@ -49,10 +49,10 @@ export class InfraConfigurationSourcesAdapter implements InfraSourcesAdapter {
} }
const DEFAULT_FIELDS = { const DEFAULT_FIELDS = {
container: 'docker.container.name', container: 'docker.container.id',
host: 'beat.hostname', host: 'host.name',
message: ['message', '@message'], message: ['message', '@message'],
pod: 'kubernetes.pod.name', pod: 'kubernetes.pod.uid',
tiebreaker: '_doc', tiebreaker: '_doc',
timestamp: '@timestamp', timestamp: '@timestamp',
}; };

View file

@ -17,30 +17,32 @@ export class InfraMetadataDomain {
public async getMetadata( public async getMetadata(
req: InfraFrameworkRequest, req: InfraFrameworkRequest,
sourceId: string, sourceId: string,
nodeName: string, nodeId: string,
nodeType: string nodeType: string
) { ) {
const sourceConfiguration = await this.libs.sources.getConfiguration(sourceId); const sourceConfiguration = await this.libs.sources.getConfiguration(sourceId);
const metricsPromise = this.adapter.getMetricMetadata( const metricsPromise = this.adapter.getMetricMetadata(
req, req,
sourceConfiguration, sourceConfiguration,
nodeName, nodeId,
nodeType nodeType
); );
const logsPromise = this.adapter.getLogMetadata(req, sourceConfiguration, nodeName, nodeType); const logsPromise = this.adapter.getLogMetadata(req, sourceConfiguration, nodeId, nodeType);
const metrics = await metricsPromise; const metrics = await metricsPromise;
const logs = await logsPromise; const logs = await logsPromise;
const metricMetadata = pickMetadata(metrics).map(entry => { const metricMetadata = pickMetadata(metrics.buckets).map(entry => {
return { name: entry, source: 'metrics' }; return { name: entry, source: 'metrics' };
}); });
const logMetadata = pickMetadata(logs).map(entry => { const logMetadata = pickMetadata(logs.buckets).map(entry => {
return { name: entry, source: 'logs' }; return { name: entry, source: 'logs' };
}); });
return metricMetadata.concat(logMetadata); const id = metrics.id || logs.id;
const name = metrics.name || logs.name || id;
return { id, name, features: metricMetadata.concat(logMetadata) };
} }
} }

View file

@ -30,7 +30,12 @@ const metadataTests: KbnTestProvider = ({ getService }) => {
}) })
.then(resp => { .then(resp => {
const metadata = resp.data.source.metadataByNode; const metadata = resp.data.source.metadataByNode;
expect(metadata.length).to.be(14); if (metadata) {
expect(metadata.features.length).to.be(14);
expect(metadata.name).to.equal('demo-stack-nginx-01');
} else {
throw new Error('Metadata should never be empty');
}
}); });
}); });
}); });

View file

@ -33,9 +33,9 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
// shipped default values // shipped default values
expect(sourceConfiguration.metricAlias).to.be('metricbeat-*'); expect(sourceConfiguration.metricAlias).to.be('metricbeat-*');
expect(sourceConfiguration.logAlias).to.be('filebeat-*'); expect(sourceConfiguration.logAlias).to.be('filebeat-*');
expect(sourceConfiguration.fields.container).to.be('docker.container.name'); expect(sourceConfiguration.fields.container).to.be('docker.container.id');
expect(sourceConfiguration.fields.host).to.be('beat.hostname'); expect(sourceConfiguration.fields.host).to.be('host.name');
expect(sourceConfiguration.fields.pod).to.be('kubernetes.pod.name'); expect(sourceConfiguration.fields.pod).to.be('kubernetes.pod.uid');
// test data in x-pack/test/functional/es_archives/infra/data.json.gz // test data in x-pack/test/functional/es_archives/infra/data.json.gz
expect(sourceStatus.indexFields.length).to.be(1765); expect(sourceStatus.indexFields.length).to.be(1765);