[Metrics UI] Replace uses of any introduced by Lodash 4 (#75507)

Co-authored-by: Felix Stürmer <weltenwort@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Zacqary Adam Xeper 2020-08-27 10:06:55 -05:00 committed by GitHub
parent 8671db1559
commit b98e2e4f3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 82 additions and 64 deletions

View file

@ -85,7 +85,7 @@ interface Props {
nodeType: InventoryItemType;
filterQuery?: string;
filterQueryText?: string;
sourceId?: string;
sourceId: string;
alertOnNoData?: boolean;
};
alertInterval: string;
@ -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}

View file

@ -34,6 +34,7 @@ describe('Expression', () => {
criteria: [],
groupBy: undefined,
filterQueryText: '',
sourceId: 'default',
};
const mocks = coreMock.createSetup();

View file

@ -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}

View file

@ -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]);
@ -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.

View file

@ -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]) {

View file

@ -55,7 +55,7 @@ export interface AlertParams {
criteria: MetricExpression[];
groupBy?: string[];
filterQuery?: string;
sourceId?: string;
sourceId: string;
filterQueryText?: string;
alertOnNoData?: boolean;
}

View file

@ -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',
})}
@ -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',
})}

View file

@ -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) => {
@ -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 = (
@ -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');
@ -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;
}

View file

@ -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);

View file

@ -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'),

View file

@ -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>);

View file

@ -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'
@ -53,7 +55,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
)
);
const inventoryItems = Object.keys(first(results) as any);
const inventoryItems = Object.keys(first(results)!);
for (const item of inventoryItems) {
const alertInstance = services.alertInstanceFactory(`${item}`);
// AND logic; all criteria must be across the threshold

View file

@ -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);
@ -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);

View file

@ -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;
@ -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}`);
@ -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) {
@ -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');

View file

@ -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
@ -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);
@ -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
);
@ -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
@ -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;
}
};

View file

@ -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,

View file

@ -27,6 +27,8 @@ 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>>;
@ -34,7 +36,7 @@ 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
@ -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),
};

View file

@ -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({

View file

@ -10,11 +10,14 @@ import { InfraDatabaseSearchResponse } from '../lib/adapters/framework';
export const createAfterKeyHandler = (
optionsAfterKeyPath: string | string[],
afterKeySelector: (input: InfraDatabaseSearchResponse<any, any>) => any
) => <Options>(options: Options, response: InfraDatabaseSearchResponse<any, any>): Options => {
) => <Options extends object>(
options: Options,
response: InfraDatabaseSearchResponse<any, any>
): Options => {
if (!response.aggregations) {
return options;
}
const newOptions = { ...options } as any;
const newOptions = { ...options };
const afterKey = afterKeySelector(response);
set(newOptions, optionsAfterKeyPath, afterKey);
return newOptions;