Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ interface Props {
nodeType: InventoryItemType;
filterQuery?: string;
filterQueryText?: string;
sourceId?: string;
sourceId: string;
alertOnNoData?: boolean;
};
alertInterval: string;
Expand Down Expand Up @@ -379,7 +379,7 @@ export const Expressions: React.FC<Props> = (props) => {
<AlertPreview
alertInterval={alertInterval}
alertType={METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID}
alertParams={pick(alertParams as any, 'criteria', 'nodeType', 'sourceId', 'filterQuery')}
alertParams={pick(alertParams, 'criteria', 'nodeType', 'sourceId', 'filterQuery')}
validate={validateMetricThreshold}
fetch={alertsContext.http.fetch}
groupByDisplayName={alertParams.nodeType}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('Expression', () => {
criteria: [],
groupBy: undefined,
filterQueryText: '',
sourceId: 'default',
};

const mocks = coreMock.createSetup();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ export const Expressions: React.FC<Props> = (props) => {
<AlertPreview
alertInterval={alertInterval}
alertType={METRIC_THRESHOLD_ALERT_TYPE_ID}
alertParams={pick(alertParams as any, 'criteria', 'groupBy', 'filterQuery', 'sourceId')}
alertParams={pick(alertParams, 'criteria', 'groupBy', 'filterQuery', 'sourceId')}
showNoDataResults={alertParams.alertOnNoData}
validate={validateMetricThreshold}
fetch={alertsContext.http.fetch}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,16 @@ export const ExpressionChart: React.FC<Props> = ({
};
const isDarkMode = context.uiSettings?.get('theme:darkMode') || false;
const dateFormatter = useMemo(() => {
const firstSeries = data ? first(data.series) : null;
return firstSeries && firstSeries.rows.length > 0
? niceTimeFormatter([
(first(firstSeries.rows) as any).timestamp,
(last(firstSeries.rows) as any).timestamp,
])
: (value: number) => `${value}`;
}, [data]);
const firstSeries = first(data?.series);
const firstTimestamp = first(firstSeries?.rows)?.timestamp;
const lastTimestamp = last(firstSeries?.rows)?.timestamp;

if (firstTimestamp == null || lastTimestamp == null) {
return (value: number) => `${value}`;
}

return niceTimeFormatter([firstTimestamp, lastTimestamp]);
}, [data?.series]);

/* eslint-disable-next-line react-hooks/exhaustive-deps */
const yAxisFormater = useCallback(createFormatterForMetric(metric), [expression]);
Expand Down Expand Up @@ -138,8 +140,8 @@ export const ExpressionChart: React.FC<Props> = ({
}),
};

const firstTimestamp = (first(firstSeries.rows) as any).timestamp;
const lastTimestamp = (last(firstSeries.rows) as any).timestamp;
const firstTimestamp = first(firstSeries.rows)!.timestamp;
const lastTimestamp = last(firstSeries.rows)!.timestamp;
const dataDomain = calculateDomain(series, [metric], false);
const domain = {
max: Math.max(dataDomain.max, last(thresholds) || dataDomain.max) * 1.1, // add 10% headroom.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export const transformMetricsExplorerData = (
data: MetricsExplorerResponse | null
) => {
const { criteria } = params;
if (criteria && data) {
const firstSeries = first(data.series) as any;
const series = firstSeries.rows.reduce((acc: any, row: any) => {
const firstSeries = first(data?.series);
if (criteria && firstSeries) {
const series = firstSeries.rows.reduce((acc, row) => {
const { timestamp } = row;
criteria.forEach((item, index) => {
if (!acc[index]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface AlertParams {
criteria: MetricExpression[];
groupBy?: string[];
filterQuery?: string;
sourceId?: string;
sourceId: string;
filterQueryText?: string;
alertOnNoData?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export const LegendControls = ({
fullWidth
label={
<SwatchLabel
color={first(paletteColors) as any}
color={first(paletteColors)!}
label={i18n.translate('xpack.infra.legendControls.minLabel', {
defaultMessage: 'Minimum',
})}
Expand Down Expand Up @@ -294,7 +294,7 @@ export const LegendControls = ({
display="columnCompressed"
label={
<SwatchLabel
color={last(paletteColors) as any}
color={last(paletteColors)!}
label={i18n.translate('xpack.infra.legendControls.maxLabel', {
defaultMessage: 'Maxium',
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ export const calculateSteppedGradientColor = (

// Since the stepped legend matches a range we need to ensure anything outside
// the max bounds get's the maximum color.
if (gte(normalizedValue, (last(rules) as any).value)) {
return (last(rules) as any).color;
const lastRule = last(rules);
if (lastRule && gte(normalizedValue, lastRule.value)) {
return lastRule.color;
}

return rules.reduce((color: string, rule) => {
Expand All @@ -79,7 +80,7 @@ export const calculateSteppedGradientColor = (
return rule.color;
}
return color;
}, (first(rules) as any).color || defaultColor);
}, first(rules)?.color ?? defaultColor);
};

export const calculateStepColor = (
Expand All @@ -106,7 +107,7 @@ export const calculateGradientColor = (
return defaultColor;
}
if (rules.length === 1) {
return (last(rules) as any).color;
return last(rules)!.color;
}
const { min, max } = bounds;
const sortedRules = sortBy(rules, 'value');
Expand All @@ -116,10 +117,8 @@ export const calculateGradientColor = (
return rule;
}
return acc;
}, first(sortedRules)) as any;
const endRule = sortedRules
.filter((r) => r !== startRule)
.find((r) => r.value >= normValue) as any;
}, first(sortedRules))!;
const endRule = sortedRules.filter((r) => r !== startRule).find((r) => r.value >= normValue);
if (!endRule) {
return startRule.color;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ function findOrCreateGroupWithNodes(
* look for the full id. Otherwise we need to find the parent group and
* then look for the group in it's sub groups.
*/
if (path.length === 2) {
const parentId = (first(path) as any).value;
const firstPath = first(path);
if (path.length === 2 && firstPath) {
const parentId = firstPath.value;
const existingParentGroup = groups.find((g) => g.id === parentId);
if (isWaffleMapGroupWithGroups(existingParentGroup)) {
const existingSubGroup = existingParentGroup.groups.find((g) => g.id === id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,13 @@ export const MetricsExplorerChart = ({
const [from, to] = x;
onTimeChange(moment(from).toISOString(), moment(to).toISOString());
};
const dateFormatter = useMemo(
() =>
series.rows.length > 0
? niceTimeFormatter([
(first(series.rows) as any).timestamp,
(last(series.rows) as any).timestamp,
])
: (value: number) => `${value}`,
[series.rows]
);
const dateFormatter = useMemo(() => {
const firstRow = first(series.rows);
const lastRow = last(series.rows);
return firstRow && lastRow
? niceTimeFormatter([firstRow.timestamp, lastRow.timestamp])
: (value: number) => `${value}`;
}, [series.rows]);
const tooltipProps = {
headerFormatter: useCallback(
(data: TooltipValue) => moment(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,14 @@ const getData = async (
if (!nodes.length) return { [UNGROUPED_FACTORY_KEY]: null }; // No Data state

return nodes.reduce((acc, n) => {
const nodePathItem = last(n.path) as any;
const { name: nodeName } = n;
const m = first(n.metrics);
if (m && m.value && m.timeseries) {
const { timeseries } = m;
const values = timeseries.rows.map((row) => row.metric_0) as Array<number | null>;
acc[nodePathItem.label] = values;
acc[nodeName] = values;
} else {
acc[nodePathItem.label] = m && m.value;
acc[nodeName] = m && m.value;
}
return acc;
}, {} as Record<string, number | Array<number | string | null | undefined> | undefined | null>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
alertOnNoData,
} = params as InventoryMetricThresholdParams;

if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');

const source = await libs.sources.getSourceConfiguration(
services.savedObjectsClient,
sourceId || 'default'
Expand All @@ -53,7 +55,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
)
);

const inventoryItems = Object.keys(first(results) as any);
const inventoryItems = Object.keys(first(results)!);
Copy link
Member

Choose a reason for hiding this comment

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

Is this a safe assertion? How do we know results is not empty?

Would

Suggested change
const inventoryItems = Object.keys(first(results)!);
const inventoryItems = Object.keys(first(results) ?? {});

be safer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

results always has one result for everything in criteria. I can add this to the top of the function:

if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');

for (const item of inventoryItems) {
const alertInstance = services.alertInstanceFactory(`${item}`);
// AND logic; all criteria must be across the threshold
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const previewInventoryMetricThresholdAlert = async ({
}: PreviewInventoryMetricThresholdAlertParams) => {
const { criteria, filterQuery, nodeType } = params as InventoryMetricThresholdParams;

if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');

const { timeSize, timeUnit } = criteria[0];
const bucketInterval = `${timeSize}${timeUnit}`;
const bucketIntervalInSeconds = getIntervalInSeconds(bucketInterval);
Expand All @@ -57,7 +59,7 @@ export const previewInventoryMetricThresholdAlert = async ({
)
);

const inventoryItems = Object.keys(first(results) as any);
const inventoryItems = Object.keys(first(results)!);
const previewResults = inventoryItems.map((item) => {
const numberOfResultBuckets = lookbackSize;
const numberOfExecutionBuckets = Math.floor(numberOfResultBuckets / alertResultsPerExecution);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
async function (options: AlertExecutorOptions) {
const { services, params } = options;
const { criteria } = params;
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');

const { sourceId, alertOnNoData } = params as {
sourceId?: string;
alertOnNoData: boolean;
Expand All @@ -34,8 +36,8 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
const config = source.configuration;
const alertResults = await evaluateAlert(services.callCluster, params, config);

// Because each alert result has the same group definitions, just grap the groups from the first one.
const groups = Object.keys(first(alertResults) as any);
// Because each alert result has the same group definitions, just grab the groups from the first one.
const groups = Object.keys(first(alertResults)!);
for (const group of groups) {
const alertInstance = services.alertInstanceFactory(`${group}`);

Expand All @@ -60,7 +62,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
let reason;
if (nextState === AlertStates.ALERT) {
reason = alertResults
.map((result) => buildFiredAlertReason(formatAlertResult(result[group]) as any))
.map((result) => buildFiredAlertReason(formatAlertResult(result[group])))
.join('\n');
}
if (alertOnNoData) {
Expand Down Expand Up @@ -121,11 +123,13 @@ const mapToConditionsLookup = (
{}
);

const formatAlertResult = (alertResult: {
metric: string;
currentValue: number;
threshold: number[];
}) => {
const formatAlertResult = <AlertResult>(
alertResult: {
metric: string;
currentValue: number;
threshold: number[];
} & AlertResult
) => {
const { metric, currentValue, threshold } = alertResult;
if (!metric.endsWith('.pct')) return alertResult;
const formatter = createFormatter('percent');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export const previewMetricThresholdAlert: (
iterations = 0,
precalculatedNumberOfGroups
) => {
if (params.criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');

// There are three different "intervals" we're dealing with here, so to disambiguate:
// - The lookback interval, which is how long of a period of time we want to examine to count
// how many times the alert fired
Expand All @@ -70,7 +72,7 @@ export const previewMetricThresholdAlert: (
// Get a date histogram using the bucket interval and the lookback interval
try {
const alertResults = await evaluateAlert(callCluster, params, config, timeframe);
const groups = Object.keys(first(alertResults) as any);
const groups = Object.keys(first(alertResults)!);

// Now determine how to interpolate this histogram based on the alert interval
const alertIntervalInSeconds = getIntervalInSeconds(alertInterval);
Expand All @@ -81,7 +83,7 @@ export const previewMetricThresholdAlert: (
// buckets would have fired the alert. If the alert interval and bucket interval are the same,
// this will be a 1:1 evaluation of the alert results. If these are different, the interpolation
// will skip some buckets or read some buckets more than once, depending on the differential
const numberOfResultBuckets = (first(alertResults) as any)[group].shouldFire.length;
const numberOfResultBuckets = first(alertResults)![group].shouldFire.length;
const numberOfExecutionBuckets = Math.floor(
numberOfResultBuckets / alertResultsPerExecution
);
Expand Down Expand Up @@ -120,8 +122,7 @@ export const previewMetricThresholdAlert: (
? await evaluateAlert(callCluster, params, config)
: [];
const numberOfGroups =
precalculatedNumberOfGroups ??
Math.max(Object.keys(first(currentAlertResults) as any).length, 1);
precalculatedNumberOfGroups ?? Math.max(Object.keys(first(currentAlertResults)!).length, 1);
const estimatedTotalBuckets =
(lookbackIntervalInSeconds / bucketIntervalInSeconds) * numberOfGroups;
// The minimum number of slices is 2. In case we underestimate the total number of buckets
Expand Down Expand Up @@ -152,14 +153,16 @@ export const previewMetricThresholdAlert: (
// `undefined` values occur if there is no data at all in a certain slice, and that slice
// returns an empty array. This is different from an error or no data state,
// so filter these results out entirely and only regard the resultA portion
.filter((value) => typeof value !== 'undefined')
.filter(
<Value>(value: Value): value is NonNullable<Value> => typeof value !== 'undefined'
)
.reduce((a, b) => {
if (!a) return b;
if (!b) return a;
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
})
);
return zippedResult as any;
return zippedResult;
} else throw e;
}
};
3 changes: 2 additions & 1 deletion x-pack/plugins/infra/server/lib/snapshot/response_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ export const getNodeMetrics = (
avg: null,
}));
}
const lastBucket = findLastFullBucket(nodeBuckets, options) as any;
const lastBucket = findLastFullBucket(nodeBuckets, options);
if (!lastBucket) return [];
return options.metrics.map((metric, index) => {
const metricResult: SnapshotNodeMetric = {
name: metric.type,
Expand Down
7 changes: 5 additions & 2 deletions x-pack/plugins/infra/server/lib/snapshot/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ import { InfraSnapshotRequestOptions } from './types';
import { createTimeRangeWithInterval } from './create_timerange_with_interval';
import { SnapshotNode } from '../../../common/http_api/snapshot_api';

type NamedSnapshotNode = SnapshotNode & { name: string };

export type ESSearchClient = <Hit = {}, Aggregation = undefined>(
options: CallWithRequestParams
) => Promise<InfraDatabaseSearchResponse<Hit, Aggregation>>;
export class InfraSnapshot {
public async getNodes(
client: ESSearchClient,
options: InfraSnapshotRequestOptions
): Promise<{ nodes: SnapshotNode[]; interval: string }> {
): Promise<{ nodes: NamedSnapshotNode[]; interval: string }> {
// Both requestGroupedNodes and requestNodeMetrics may send several requests to elasticsearch
// in order to page through the results of their respective composite aggregations.
// Both chains of requests are supposed to run in parallel, and their results be merged
Expand Down Expand Up @@ -184,11 +186,12 @@ const mergeNodeBuckets = (
nodeGroupByBuckets: InfraSnapshotNodeGroupByBucket[],
nodeMetricsBuckets: InfraSnapshotNodeMetricsBucket[],
options: InfraSnapshotRequestOptions
): SnapshotNode[] => {
): NamedSnapshotNode[] => {
const nodeMetricsForLookup = getNodeMetricsForLookup(nodeMetricsBuckets);

return nodeGroupByBuckets.map((node) => {
return {
name: node.key.name || node.key.id, // For type safety; name can be derived from getNodePath but not in a TS-friendly way
path: getNodePath(node, options),
metrics: getNodeMetrics(nodeMetricsForLookup[node.key.id], options),
};
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/infra/server/routes/ip_to_hostname.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const initIpToHostName = ({ framework }: InfraBackendLibs) => {
body: { message: 'Host with matching IP address not found.' },
});
}
const hostDoc = first(hits.hits) as any;
const hostDoc = first(hits.hits)!;
return response.ok({ body: { host: hostDoc._source.host.name } });
} catch ({ statusCode = 500, message = 'Unknown error occurred' }) {
return response.customError({
Expand Down
Loading