[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:
parent
8671db1559
commit
b98e2e4f3d
|
@ -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}
|
||||
|
|
|
@ -34,6 +34,7 @@ describe('Expression', () => {
|
|||
criteria: [],
|
||||
groupBy: undefined,
|
||||
filterQueryText: '',
|
||||
sourceId: 'default',
|
||||
};
|
||||
|
||||
const mocks = coreMock.createSetup();
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -55,7 +55,7 @@ export interface AlertParams {
|
|||
criteria: MetricExpression[];
|
||||
groupBy?: string[];
|
||||
filterQuery?: string;
|
||||
sourceId?: string;
|
||||
sourceId: string;
|
||||
filterQueryText?: string;
|
||||
alertOnNoData?: boolean;
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
})}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue