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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -114,7 +114,7 @@
"description": "A hierarchy of metadata entries by node",
"args": [
{
"name": "nodeName",
"name": "nodeId",
"description": "",
"type": {
"kind": "NON_NULL",
@ -137,11 +137,7 @@
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": { "kind": "OBJECT", "name": "InfraNodeMetadata", "ofType": null }
}
"ofType": { "kind": "OBJECT", "name": "InfraNodeMetadata", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
@ -775,6 +771,61 @@
"kind": "OBJECT",
"name": "InfraNodeMetadata",
"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": [
{
"name": "name",
@ -1542,6 +1593,18 @@
},
"isDeprecated": false,
"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,

View file

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

View file

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

View file

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

View file

@ -12,13 +12,13 @@ import { InfraNodeType } from '../../graphql/types';
import { getFromFromLocation, getToFromLocation } from './query_params';
type RedirectToNodeDetailProps = RouteComponentProps<{
nodeName: string;
nodeId: string;
nodeType: InfraNodeType;
}>;
export const RedirectToNodeDetail = ({
match: {
params: { nodeName, nodeType },
params: { nodeId, nodeType },
},
location,
}: RedirectToNodeDetailProps) => {
@ -27,20 +27,20 @@ export const RedirectToNodeDetail = ({
getToFromLocation(location)
)('');
return <Redirect to={`/metrics/${nodeType}/${nodeName}?${searchString}`} />;
return <Redirect to={`/metrics/${nodeType}/${nodeId}?${searchString}`} />;
};
export const getNodeDetailUrl = ({
nodeType,
nodeName,
nodeId,
to,
from,
}: {
nodeType: InfraNodeType;
nodeName: string;
nodeId: string;
to?: number;
from?: number;
}) => {
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';
type RedirectToNodeLogsType = RouteComponentProps<{
nodeName: string;
nodeId: string;
nodeType: InfraNodeType;
}>;
@ -28,7 +28,7 @@ interface RedirectToNodeLogsProps extends RedirectToNodeLogsType {
export const RedirectToNodeLogs = injectI18n(
({
match: {
params: { nodeName, nodeType },
params: { nodeId, nodeType },
},
location,
intl,
@ -52,7 +52,7 @@ export const RedirectToNodeLogs = injectI18n(
}
const searchString = compose(
replaceLogFilterInQueryString(`${configuredFields[nodeType]}: ${nodeName}`),
replaceLogFilterInQueryString(`${configuredFields[nodeType]}: ${nodeId}`),
replaceLogPositionInQueryString(getTimeFromLocation(location))
)('');
@ -63,11 +63,11 @@ export const RedirectToNodeLogs = injectI18n(
);
export const getNodeLogsUrl = ({
nodeName,
nodeId,
nodeType,
time,
}: {
nodeName: string;
nodeId: string;
nodeType: InfraNodeType;
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() {
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 layoutCreator = layoutCreators[nodeType];
if (!layoutCreator) {
@ -82,40 +82,40 @@ export const MetricDetail = withTheme(
);
}
const layouts = layoutCreator(this.props.theme);
const breadcrumbs = [{ text: nodeName }];
return (
<ColumnarPage>
<Header
appendSections={<InfrastructureBetaBadgeHeaderSection />}
breadcrumbs={breadcrumbs}
/>
<WithMetricsTimeUrlState />
<DetailPageContent>
<WithOptions>
{({ sourceId }) => (
<WithMetricsTime resetOnUnmount>
{({
currentTimeRange,
isAutoReloading,
setRangeTime,
startMetricsAutoReload,
stopMetricsAutoReload,
}) => (
<WithMetadata
layouts={layouts}
sourceId={sourceId}
nodeType={nodeType}
nodeId={nodeName}
>
{({ filteredLayouts, loading: metadataLoading }) => {
return (
<WithOptions>
{({ sourceId }) => (
<WithMetricsTime resetOnUnmount>
{({
currentTimeRange,
isAutoReloading,
setRangeTime,
startMetricsAutoReload,
stopMetricsAutoReload,
}) => (
<WithMetadata
layouts={layouts}
sourceId={sourceId}
nodeType={nodeType}
nodeId={nodeId}
>
{({ name, filteredLayouts, loading: metadataLoading }) => {
const breadcrumbs = [{ text: name }];
return (
<ColumnarPage>
<Header
appendSections={<InfrastructureBetaBadgeHeaderSection />}
breadcrumbs={breadcrumbs}
/>
<WithMetricsTimeUrlState />
<DetailPageContent>
<WithMetrics
layouts={filteredLayouts}
sourceId={sourceId}
timerange={currentTimeRange as InfraTimerangeInput}
nodeType={nodeType}
nodeId={nodeName}
nodeId={nodeId}
>
{({ metrics, error, loading }) => {
if (error) {
@ -126,7 +126,7 @@ export const MetricDetail = withTheme(
<MetricsSideNav
layouts={filteredLayouts}
loading={metadataLoading}
nodeName={nodeName}
nodeName={name}
handleClick={this.handleClick}
/>
<AutoSizer content={false} bounds detectAnyWindowResize>
@ -139,7 +139,7 @@ export const MetricDetail = withTheme(
<MetricsTitleTimeRangeContainer>
<EuiHideFor sizes={['xs', 's']}>
<EuiTitle size="m">
<h1>{nodeName}</h1>
<h1>{name}</h1>
</EuiTitle>
</EuiHideFor>
<MetricsTimeControls
@ -155,7 +155,8 @@ export const MetricDetail = withTheme(
<EuiPageContentWithRelative>
<Metrics
nodeName={nodeName}
label={name}
nodeId={nodeId}
layouts={filteredLayouts}
metrics={metrics}
loading={
@ -175,15 +176,15 @@ export const MetricDetail = withTheme(
);
}}
</WithMetrics>
);
}}
</WithMetadata>
)}
</WithMetricsTime>
</DetailPageContent>
</ColumnarPage>
);
}}
</WithMetadata>
)}
</WithOptions>
</DetailPageContent>
</ColumnarPage>
</WithMetricsTime>
)}
</WithOptions>
);
}

View file

@ -23,7 +23,7 @@ export const createMetadataResolvers = (libs: {
} => ({
InfraSource: {
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;
},
},

View file

@ -9,12 +9,18 @@ import gql from 'graphql-tag';
export const metadataSchema = gql`
"One metadata entry for a node."
type InfraNodeMetadata {
id: ID!
name: String!
features: [InfraNodeFeature!]!
}
type InfraNodeFeature {
name: String!
source: String!
}
extend type InfraSource {
"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 {
value: String!
label: String
}
type InfraNode {

View file

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

View file

@ -7,17 +7,23 @@
import { InfraSourceConfiguration } from '../../sources';
import { InfraFrameworkRequest, InfraMetadataAggregationBucket } from '../framework';
export interface InfraMetricsAdapterResponse {
id: string;
name?: string;
buckets: InfraMetadataAggregationBucket[];
}
export interface InfraMetadataAdapter {
getMetricMetadata(
req: InfraFrameworkRequest,
sourceConfiguration: InfraSourceConfiguration,
nodeName: string,
nodeId: string,
nodeType: string
): Promise<InfraMetadataAggregationBucket[]>;
): Promise<InfraMetricsAdapterResponse>;
getLogMetadata(
req: InfraFrameworkRequest,
sourceConfiguration: InfraSourceConfiguration,
nodeName: string,
nodeId: 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.
*/
import { first, get } from 'lodash';
import { InfraSourceConfiguration } from '../../sources';
import {
InfraBackendFrameworkAdapter,
InfraFrameworkRequest,
InfraMetadataAggregationBucket,
InfraMetadataAggregationResponse,
} from '../framework';
import { InfraMetadataAdapter } from './adapter_types';
import { NAME_FIELDS } from '../nodes/constants';
import { InfraMetadataAdapter, InfraMetricsAdapterResponse } from './adapter_types';
export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
private framework: InfraBackendFrameworkAdapter;
@ -22,9 +23,9 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
public async getMetricMetadata(
req: InfraFrameworkRequest,
sourceConfiguration: InfraSourceConfiguration,
nodeName: string,
nodeId: string,
nodeType: 'host' | 'container' | 'pod'
): Promise<InfraMetadataAggregationBucket[]> {
): Promise<InfraMetricsAdapterResponse> {
const idFieldName = getIdFieldName(sourceConfiguration, nodeType);
const metricQuery = {
index: sourceConfiguration.metricAlias,
@ -32,11 +33,12 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
query: {
bool: {
filter: {
term: { [idFieldName]: nodeName },
term: { [idFieldName]: nodeId },
},
},
},
size: 0,
size: 1,
_source: [NAME_FIELDS[nodeType]],
aggs: {
metrics: {
terms: {
@ -61,17 +63,26 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
{ metrics?: InfraMetadataAggregationResponse }
>(req, 'search', metricQuery);
return response.aggregations && response.aggregations.metrics
? response.aggregations.metrics.buckets
: [];
const 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(
req: InfraFrameworkRequest,
sourceConfiguration: InfraSourceConfiguration,
nodeName: string,
nodeId: string,
nodeType: 'host' | 'container' | 'pod'
): Promise<InfraMetadataAggregationBucket[]> {
): Promise<InfraMetricsAdapterResponse> {
const idFieldName = getIdFieldName(sourceConfiguration, nodeType);
const logQuery = {
index: sourceConfiguration.logAlias,
@ -79,11 +90,12 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
query: {
bool: {
filter: {
term: { [idFieldName]: nodeName },
term: { [idFieldName]: nodeId },
},
},
},
size: 0,
size: 1,
_source: [NAME_FIELDS[nodeType]],
aggs: {
metrics: {
terms: {
@ -108,9 +120,18 @@ export class ElasticsearchMetadataAdapter implements InfraMetadataAdapter {
{ metrics?: InfraMetadataAggregationResponse }
>(req, 'search', logQuery);
return response.aggregations && response.aggregations.metrics
? response.aggregations.metrics.buckets
: [];
const 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.
*/
import { InfraNodeType } from '../../../graphql/types';
// TODO: Make NODE_REQUEST_PARTITION_SIZE configurable from kibana.yml
export const NODE_REQUEST_PARTITION_SIZE = 75;
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.
*/
import { last } from 'lodash';
import { get, last } from 'lodash';
import { isNumber } from 'lodash';
import moment from 'moment';
import { InfraNode, InfraNodeMetric } from '../../../../graphql/types';
import { InfraBucket, InfraNodeRequestOptions } from '../adapter_types';
import { NAME_FIELDS } from '../constants';
import { getBucketSizeInSeconds } from './get_bucket_size_in_seconds';
// TODO: Break these function into seperate files and expand beyond just documnet count
@ -57,8 +58,9 @@ export function createNodeItem(
node: InfraBucket,
bucket: InfraBucket
): InfraNode {
const nodeDoc = get(node, ['nodeDetails', 'hits', 'hits', 0]);
return {
metric: createNodeMetrics(options, node, bucket),
path: [{ value: node.key }],
path: [{ value: node.key, label: get(nodeDoc, `_source.${NAME_FIELDS[options.nodeType]}`) }],
} as InfraNode;
}

View file

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

View file

@ -4,7 +4,7 @@
* 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 {
@ -18,7 +18,7 @@ export const groupByProcessor = (options: InfraProcesorRequestOptions) => {
return (doc: InfraESSearchBody) => {
const result = cloneDeep(doc);
const { groupBy } = options.nodeOptions;
let aggs = {};
let aggs = get(result, 'aggs.waffle.aggs.nodes.aggs', {});
set(result, 'aggs.waffle.aggs.nodes.aggs', aggs);
groupBy.forEach((grouping: InfraPathInput, index: number) => {
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;
}
});

View file

@ -7,7 +7,11 @@
import { cloneDeep, set } from 'lodash';
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 { fields } = options.nodeOptions.sourceConfiguration;
@ -22,6 +26,7 @@ const nodeTypeToField = (options: InfraProcesorRequestOptions): string => {
};
export const nodesProcessor = (options: InfraProcesorRequestOptions) => {
const { fields } = options.nodeOptions.sourceConfiguration;
return (doc: InfraESSearchBody) => {
const result = cloneDeep(doc);
const field = nodeTypeToField(options);
@ -35,6 +40,16 @@ export const nodesProcessor = (options: InfraProcesorRequestOptions) => {
order: { _key: 'asc' },
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;
};
};

View file

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

View file

@ -17,30 +17,32 @@ export class InfraMetadataDomain {
public async getMetadata(
req: InfraFrameworkRequest,
sourceId: string,
nodeName: string,
nodeId: string,
nodeType: string
) {
const sourceConfiguration = await this.libs.sources.getConfiguration(sourceId);
const metricsPromise = this.adapter.getMetricMetadata(
req,
sourceConfiguration,
nodeName,
nodeId,
nodeType
);
const logsPromise = this.adapter.getLogMetadata(req, sourceConfiguration, nodeName, nodeType);
const logsPromise = this.adapter.getLogMetadata(req, sourceConfiguration, nodeId, nodeType);
const metrics = await metricsPromise;
const logs = await logsPromise;
const metricMetadata = pickMetadata(metrics).map(entry => {
const metricMetadata = pickMetadata(metrics.buckets).map(entry => {
return { name: entry, source: 'metrics' };
});
const logMetadata = pickMetadata(logs).map(entry => {
const logMetadata = pickMetadata(logs.buckets).map(entry => {
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 => {
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
expect(sourceConfiguration.metricAlias).to.be('metricbeat-*');
expect(sourceConfiguration.logAlias).to.be('filebeat-*');
expect(sourceConfiguration.fields.container).to.be('docker.container.name');
expect(sourceConfiguration.fields.host).to.be('beat.hostname');
expect(sourceConfiguration.fields.pod).to.be('kubernetes.pod.name');
expect(sourceConfiguration.fields.container).to.be('docker.container.id');
expect(sourceConfiguration.fields.host).to.be('host.name');
expect(sourceConfiguration.fields.pod).to.be('kubernetes.pod.uid');
// test data in x-pack/test/functional/es_archives/infra/data.json.gz
expect(sourceStatus.indexFields.length).to.be(1765);