[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; nodeType: InventoryItemType;
filterQuery?: string; filterQuery?: string;
filterQueryText?: string; filterQueryText?: string;
sourceId?: string; sourceId: string;
alertOnNoData?: boolean; alertOnNoData?: boolean;
}; };
alertInterval: string; alertInterval: string;
@ -379,7 +379,7 @@ export const Expressions: React.FC<Props> = (props) => {
<AlertPreview <AlertPreview
alertInterval={alertInterval} alertInterval={alertInterval}
alertType={METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID} 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} validate={validateMetricThreshold}
fetch={alertsContext.http.fetch} fetch={alertsContext.http.fetch}
groupByDisplayName={alertParams.nodeType} groupByDisplayName={alertParams.nodeType}

View file

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

View file

@ -400,7 +400,7 @@ export const Expressions: React.FC<Props> = (props) => {
<AlertPreview <AlertPreview
alertInterval={alertInterval} alertInterval={alertInterval}
alertType={METRIC_THRESHOLD_ALERT_TYPE_ID} 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} showNoDataResults={alertParams.alertOnNoData}
validate={validateMetricThreshold} validate={validateMetricThreshold}
fetch={alertsContext.http.fetch} 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 isDarkMode = context.uiSettings?.get('theme:darkMode') || false;
const dateFormatter = useMemo(() => { const dateFormatter = useMemo(() => {
const firstSeries = data ? first(data.series) : null; const firstSeries = first(data?.series);
return firstSeries && firstSeries.rows.length > 0 const firstTimestamp = first(firstSeries?.rows)?.timestamp;
? niceTimeFormatter([ const lastTimestamp = last(firstSeries?.rows)?.timestamp;
(first(firstSeries.rows) as any).timestamp,
(last(firstSeries.rows) as any).timestamp, if (firstTimestamp == null || lastTimestamp == null) {
]) return (value: number) => `${value}`;
: (value: number) => `${value}`; }
}, [data]);
return niceTimeFormatter([firstTimestamp, lastTimestamp]);
}, [data?.series]);
/* eslint-disable-next-line react-hooks/exhaustive-deps */ /* eslint-disable-next-line react-hooks/exhaustive-deps */
const yAxisFormater = useCallback(createFormatterForMetric(metric), [expression]); 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 firstTimestamp = first(firstSeries.rows)!.timestamp;
const lastTimestamp = (last(firstSeries.rows) as any).timestamp; const lastTimestamp = last(firstSeries.rows)!.timestamp;
const dataDomain = calculateDomain(series, [metric], false); const dataDomain = calculateDomain(series, [metric], false);
const domain = { const domain = {
max: Math.max(dataDomain.max, last(thresholds) || dataDomain.max) * 1.1, // add 10% headroom. 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 data: MetricsExplorerResponse | null
) => { ) => {
const { criteria } = params; const { criteria } = params;
if (criteria && data) { const firstSeries = first(data?.series);
const firstSeries = first(data.series) as any; if (criteria && firstSeries) {
const series = firstSeries.rows.reduce((acc: any, row: any) => { const series = firstSeries.rows.reduce((acc, row) => {
const { timestamp } = row; const { timestamp } = row;
criteria.forEach((item, index) => { criteria.forEach((item, index) => {
if (!acc[index]) { if (!acc[index]) {

View file

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

View file

@ -266,7 +266,7 @@ export const LegendControls = ({
fullWidth fullWidth
label={ label={
<SwatchLabel <SwatchLabel
color={first(paletteColors) as any} color={first(paletteColors)!}
label={i18n.translate('xpack.infra.legendControls.minLabel', { label={i18n.translate('xpack.infra.legendControls.minLabel', {
defaultMessage: 'Minimum', defaultMessage: 'Minimum',
})} })}
@ -294,7 +294,7 @@ export const LegendControls = ({
display="columnCompressed" display="columnCompressed"
label={ label={
<SwatchLabel <SwatchLabel
color={last(paletteColors) as any} color={last(paletteColors)!}
label={i18n.translate('xpack.infra.legendControls.maxLabel', { label={i18n.translate('xpack.infra.legendControls.maxLabel', {
defaultMessage: 'Maxium', defaultMessage: 'Maxium',
})} })}

View file

@ -68,8 +68,9 @@ export const calculateSteppedGradientColor = (
// Since the stepped legend matches a range we need to ensure anything outside // Since the stepped legend matches a range we need to ensure anything outside
// the max bounds get's the maximum color. // the max bounds get's the maximum color.
if (gte(normalizedValue, (last(rules) as any).value)) { const lastRule = last(rules);
return (last(rules) as any).color; if (lastRule && gte(normalizedValue, lastRule.value)) {
return lastRule.color;
} }
return rules.reduce((color: string, rule) => { return rules.reduce((color: string, rule) => {
@ -79,7 +80,7 @@ export const calculateSteppedGradientColor = (
return rule.color; return rule.color;
} }
return color; return color;
}, (first(rules) as any).color || defaultColor); }, first(rules)?.color ?? defaultColor);
}; };
export const calculateStepColor = ( export const calculateStepColor = (
@ -106,7 +107,7 @@ export const calculateGradientColor = (
return defaultColor; return defaultColor;
} }
if (rules.length === 1) { if (rules.length === 1) {
return (last(rules) as any).color; return last(rules)!.color;
} }
const { min, max } = bounds; const { min, max } = bounds;
const sortedRules = sortBy(rules, 'value'); const sortedRules = sortBy(rules, 'value');
@ -116,10 +117,8 @@ export const calculateGradientColor = (
return rule; return rule;
} }
return acc; return acc;
}, first(sortedRules)) as any; }, first(sortedRules))!;
const endRule = sortedRules const endRule = sortedRules.filter((r) => r !== startRule).find((r) => r.value >= normValue);
.filter((r) => r !== startRule)
.find((r) => r.value >= normValue) as any;
if (!endRule) { if (!endRule) {
return startRule.color; 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 * look for the full id. Otherwise we need to find the parent group and
* then look for the group in it's sub groups. * then look for the group in it's sub groups.
*/ */
if (path.length === 2) { const firstPath = first(path);
const parentId = (first(path) as any).value; if (path.length === 2 && firstPath) {
const parentId = firstPath.value;
const existingParentGroup = groups.find((g) => g.id === parentId); const existingParentGroup = groups.find((g) => g.id === parentId);
if (isWaffleMapGroupWithGroups(existingParentGroup)) { if (isWaffleMapGroupWithGroups(existingParentGroup)) {
const existingSubGroup = existingParentGroup.groups.find((g) => g.id === id); const existingSubGroup = existingParentGroup.groups.find((g) => g.id === id);

View file

@ -74,16 +74,13 @@ export const MetricsExplorerChart = ({
const [from, to] = x; const [from, to] = x;
onTimeChange(moment(from).toISOString(), moment(to).toISOString()); onTimeChange(moment(from).toISOString(), moment(to).toISOString());
}; };
const dateFormatter = useMemo( const dateFormatter = useMemo(() => {
() => const firstRow = first(series.rows);
series.rows.length > 0 const lastRow = last(series.rows);
? niceTimeFormatter([ return firstRow && lastRow
(first(series.rows) as any).timestamp, ? niceTimeFormatter([firstRow.timestamp, lastRow.timestamp])
(last(series.rows) as any).timestamp, : (value: number) => `${value}`;
]) }, [series.rows]);
: (value: number) => `${value}`,
[series.rows]
);
const tooltipProps = { const tooltipProps = {
headerFormatter: useCallback( headerFormatter: useCallback(
(data: TooltipValue) => moment(data.value).format(dateFormat || 'Y-MM-DD HH:mm:ss.SSS'), (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 if (!nodes.length) return { [UNGROUPED_FACTORY_KEY]: null }; // No Data state
return nodes.reduce((acc, n) => { return nodes.reduce((acc, n) => {
const nodePathItem = last(n.path) as any; const { name: nodeName } = n;
const m = first(n.metrics); const m = first(n.metrics);
if (m && m.value && m.timeseries) { if (m && m.value && m.timeseries) {
const { timeseries } = m; const { timeseries } = m;
const values = timeseries.rows.map((row) => row.metric_0) as Array<number | null>; const values = timeseries.rows.map((row) => row.metric_0) as Array<number | null>;
acc[nodePathItem.label] = values; acc[nodeName] = values;
} else { } else {
acc[nodePathItem.label] = m && m.value; acc[nodeName] = m && m.value;
} }
return acc; return acc;
}, {} as Record<string, number | Array<number | string | null | undefined> | undefined | null>); }, {} as Record<string, number | Array<number | string | null | undefined> | undefined | null>);

View file

@ -42,6 +42,8 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =
alertOnNoData, alertOnNoData,
} = params as InventoryMetricThresholdParams; } = params as InventoryMetricThresholdParams;
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
const source = await libs.sources.getSourceConfiguration( const source = await libs.sources.getSourceConfiguration(
services.savedObjectsClient, services.savedObjectsClient,
sourceId || 'default' 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) { for (const item of inventoryItems) {
const alertInstance = services.alertInstanceFactory(`${item}`); const alertInstance = services.alertInstanceFactory(`${item}`);
// AND logic; all criteria must be across the threshold // AND logic; all criteria must be across the threshold

View file

@ -40,6 +40,8 @@ export const previewInventoryMetricThresholdAlert = async ({
}: PreviewInventoryMetricThresholdAlertParams) => { }: PreviewInventoryMetricThresholdAlertParams) => {
const { criteria, filterQuery, nodeType } = params as InventoryMetricThresholdParams; 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 { timeSize, timeUnit } = criteria[0];
const bucketInterval = `${timeSize}${timeUnit}`; const bucketInterval = `${timeSize}${timeUnit}`;
const bucketIntervalInSeconds = getIntervalInSeconds(bucketInterval); 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 previewResults = inventoryItems.map((item) => {
const numberOfResultBuckets = lookbackSize; const numberOfResultBuckets = lookbackSize;
const numberOfExecutionBuckets = Math.floor(numberOfResultBuckets / alertResultsPerExecution); const numberOfExecutionBuckets = Math.floor(numberOfResultBuckets / alertResultsPerExecution);

View file

@ -22,6 +22,8 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
async function (options: AlertExecutorOptions) { async function (options: AlertExecutorOptions) {
const { services, params } = options; const { services, params } = options;
const { criteria } = params; const { criteria } = params;
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
const { sourceId, alertOnNoData } = params as { const { sourceId, alertOnNoData } = params as {
sourceId?: string; sourceId?: string;
alertOnNoData: boolean; alertOnNoData: boolean;
@ -34,8 +36,8 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
const config = source.configuration; const config = source.configuration;
const alertResults = await evaluateAlert(services.callCluster, params, config); 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. // Because each alert result has the same group definitions, just grab the groups from the first one.
const groups = Object.keys(first(alertResults) as any); const groups = Object.keys(first(alertResults)!);
for (const group of groups) { for (const group of groups) {
const alertInstance = services.alertInstanceFactory(`${group}`); const alertInstance = services.alertInstanceFactory(`${group}`);
@ -60,7 +62,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) =>
let reason; let reason;
if (nextState === AlertStates.ALERT) { if (nextState === AlertStates.ALERT) {
reason = alertResults reason = alertResults
.map((result) => buildFiredAlertReason(formatAlertResult(result[group]) as any)) .map((result) => buildFiredAlertReason(formatAlertResult(result[group])))
.join('\n'); .join('\n');
} }
if (alertOnNoData) { if (alertOnNoData) {
@ -121,11 +123,13 @@ const mapToConditionsLookup = (
{} {}
); );
const formatAlertResult = (alertResult: { const formatAlertResult = <AlertResult>(
metric: string; alertResult: {
currentValue: number; metric: string;
threshold: number[]; currentValue: number;
}) => { threshold: number[];
} & AlertResult
) => {
const { metric, currentValue, threshold } = alertResult; const { metric, currentValue, threshold } = alertResult;
if (!metric.endsWith('.pct')) return alertResult; if (!metric.endsWith('.pct')) return alertResult;
const formatter = createFormatter('percent'); const formatter = createFormatter('percent');

View file

@ -49,6 +49,8 @@ export const previewMetricThresholdAlert: (
iterations = 0, iterations = 0,
precalculatedNumberOfGroups 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: // 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 // - The lookback interval, which is how long of a period of time we want to examine to count
// how many times the alert fired // 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 // Get a date histogram using the bucket interval and the lookback interval
try { try {
const alertResults = await evaluateAlert(callCluster, params, config, timeframe); 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 // Now determine how to interpolate this histogram based on the alert interval
const alertIntervalInSeconds = getIntervalInSeconds(alertInterval); 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, // 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 // 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 // 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( const numberOfExecutionBuckets = Math.floor(
numberOfResultBuckets / alertResultsPerExecution numberOfResultBuckets / alertResultsPerExecution
); );
@ -120,8 +122,7 @@ export const previewMetricThresholdAlert: (
? await evaluateAlert(callCluster, params, config) ? await evaluateAlert(callCluster, params, config)
: []; : [];
const numberOfGroups = const numberOfGroups =
precalculatedNumberOfGroups ?? precalculatedNumberOfGroups ?? Math.max(Object.keys(first(currentAlertResults)!).length, 1);
Math.max(Object.keys(first(currentAlertResults) as any).length, 1);
const estimatedTotalBuckets = const estimatedTotalBuckets =
(lookbackIntervalInSeconds / bucketIntervalInSeconds) * numberOfGroups; (lookbackIntervalInSeconds / bucketIntervalInSeconds) * numberOfGroups;
// The minimum number of slices is 2. In case we underestimate the total number of buckets // 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 // `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, // 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 // 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) => { .reduce((a, b) => {
if (!a) return b; if (!a) return b;
if (!b) return a; if (!b) return a;
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
}) })
); );
return zippedResult as any; return zippedResult;
} else throw e; } else throw e;
} }
}; };

View file

@ -127,7 +127,8 @@ export const getNodeMetrics = (
avg: null, avg: null,
})); }));
} }
const lastBucket = findLastFullBucket(nodeBuckets, options) as any; const lastBucket = findLastFullBucket(nodeBuckets, options);
if (!lastBucket) return [];
return options.metrics.map((metric, index) => { return options.metrics.map((metric, index) => {
const metricResult: SnapshotNodeMetric = { const metricResult: SnapshotNodeMetric = {
name: metric.type, name: metric.type,

View file

@ -27,6 +27,8 @@ import { InfraSnapshotRequestOptions } from './types';
import { createTimeRangeWithInterval } from './create_timerange_with_interval'; import { createTimeRangeWithInterval } from './create_timerange_with_interval';
import { SnapshotNode } from '../../../common/http_api/snapshot_api'; import { SnapshotNode } from '../../../common/http_api/snapshot_api';
type NamedSnapshotNode = SnapshotNode & { name: string };
export type ESSearchClient = <Hit = {}, Aggregation = undefined>( export type ESSearchClient = <Hit = {}, Aggregation = undefined>(
options: CallWithRequestParams options: CallWithRequestParams
) => Promise<InfraDatabaseSearchResponse<Hit, Aggregation>>; ) => Promise<InfraDatabaseSearchResponse<Hit, Aggregation>>;
@ -34,7 +36,7 @@ export class InfraSnapshot {
public async getNodes( public async getNodes(
client: ESSearchClient, client: ESSearchClient,
options: InfraSnapshotRequestOptions options: InfraSnapshotRequestOptions
): Promise<{ nodes: SnapshotNode[]; interval: string }> { ): Promise<{ nodes: NamedSnapshotNode[]; interval: string }> {
// Both requestGroupedNodes and requestNodeMetrics may send several requests to elasticsearch // Both requestGroupedNodes and requestNodeMetrics may send several requests to elasticsearch
// in order to page through the results of their respective composite aggregations. // 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 // Both chains of requests are supposed to run in parallel, and their results be merged
@ -184,11 +186,12 @@ const mergeNodeBuckets = (
nodeGroupByBuckets: InfraSnapshotNodeGroupByBucket[], nodeGroupByBuckets: InfraSnapshotNodeGroupByBucket[],
nodeMetricsBuckets: InfraSnapshotNodeMetricsBucket[], nodeMetricsBuckets: InfraSnapshotNodeMetricsBucket[],
options: InfraSnapshotRequestOptions options: InfraSnapshotRequestOptions
): SnapshotNode[] => { ): NamedSnapshotNode[] => {
const nodeMetricsForLookup = getNodeMetricsForLookup(nodeMetricsBuckets); const nodeMetricsForLookup = getNodeMetricsForLookup(nodeMetricsBuckets);
return nodeGroupByBuckets.map((node) => { return nodeGroupByBuckets.map((node) => {
return { 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), path: getNodePath(node, options),
metrics: getNodeMetrics(nodeMetricsForLookup[node.key.id], 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.' }, 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 } }); return response.ok({ body: { host: hostDoc._source.host.name } });
} catch ({ statusCode = 500, message = 'Unknown error occurred' }) { } catch ({ statusCode = 500, message = 'Unknown error occurred' }) {
return response.customError({ return response.customError({

View file

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