[ML] Refactor imports using 'elasticsearch' to '@elastic/elasticsearch'. Extend 'isPopulatedOjbect()'. (#95651)

- Gets rid of imports from 'elasticsearch' and replaces them with '@elastic/elasticsearch'.
- Extends isPopulatedObject() to allow an optional array of attributes to check if they exist. Allows us to get rid of the manual and inconsistent usages of hasOwnProperty().
This commit is contained in:
Walter Rafelsberger 2021-04-01 11:46:17 +02:00 committed by GitHub
parent 07a3f9eb8d
commit a1c36e7a06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 383 additions and 300 deletions

View file

@ -5,9 +5,11 @@
* 2.0.
*/
export { HitsTotalRelation, SearchResponse7, HITS_TOTAL_RELATION } from './types/es_client';
export { ES_CLIENT_TOTAL_HITS_RELATION } from './types/es_client';
export { ChartData } from './types/field_histograms';
export { ANOMALY_SEVERITY, ANOMALY_THRESHOLD, SEVERITY_COLORS } from './constants/anomalies';
export { getSeverityColor, getSeverityType } from './util/anomaly_utils';
export { isPopulatedObject } from './util/object_utils';
export { isRuntimeMappings } from './util/runtime_field_utils';
export { composeValidators, patternValidator } from './util/validators';
export { extractErrorMessage } from './util/errors';

View file

@ -5,33 +5,24 @@
* 2.0.
*/
import type { SearchResponse, ShardsResponse } from 'elasticsearch';
import { estypes } from '@elastic/elasticsearch';
import { buildEsQuery } from '../../../../../src/plugins/data/common/es_query/es_query';
import type { DslQuery } from '../../../../../src/plugins/data/common/es_query/kuery';
import type { JsonObject } from '../../../../../src/plugins/kibana_utils/common';
export const HITS_TOTAL_RELATION = {
import { isPopulatedObject } from '../util/object_utils';
export function isMultiBucketAggregate(arg: unknown): arg is estypes.MultiBucketAggregate {
return isPopulatedObject(arg, ['buckets']);
}
export const ES_CLIENT_TOTAL_HITS_RELATION: Record<
Uppercase<estypes.TotalHitsRelation>,
estypes.TotalHitsRelation
> = {
EQ: 'eq',
GTE: 'gte',
} as const;
export type HitsTotalRelation = typeof HITS_TOTAL_RELATION[keyof typeof HITS_TOTAL_RELATION];
// The types specified in `@types/elasticsearch` are out of date and still have `total: number`.
interface SearchResponse7Hits<T> {
hits: SearchResponse<T>['hits']['hits'];
max_score: number;
total: {
value: number;
relation: HitsTotalRelation;
};
}
export interface SearchResponse7<T = any> {
took: number;
timed_out: boolean;
_scroll_id?: string;
_shards: ShardsResponse;
hits: SearchResponse7Hits<T>;
aggregations?: any;
}
export type InfluencersFilterQuery = ReturnType<typeof buildEsQuery> | DslQuery | JsonObject;

View file

@ -88,15 +88,11 @@ export function isRegressionTotalFeatureImportance(
export function isClassificationFeatureImportanceBaseline(
baselineData: any
): baselineData is ClassificationFeatureImportanceBaseline {
return (
isPopulatedObject(baselineData) &&
baselineData.hasOwnProperty('classes') &&
Array.isArray(baselineData.classes)
);
return isPopulatedObject(baselineData, ['classes']) && Array.isArray(baselineData.classes);
}
export function isRegressionFeatureImportanceBaseline(
baselineData: any
): baselineData is RegressionFeatureImportanceBaseline {
return isPopulatedObject(baselineData) && baselineData.hasOwnProperty('baseline');
return isPopulatedObject(baselineData, ['baseline']);
}

View file

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { isPopulatedObject } from './object_utils';
describe('object_utils', () => {
describe('isPopulatedObject()', () => {
it('does not allow numbers', () => {
expect(isPopulatedObject(0)).toBe(false);
});
it('does not allow strings', () => {
expect(isPopulatedObject('')).toBe(false);
});
it('does not allow null', () => {
expect(isPopulatedObject(null)).toBe(false);
});
it('does not allow an empty object', () => {
expect(isPopulatedObject({})).toBe(false);
});
it('allows an object with an attribute', () => {
expect(isPopulatedObject({ attribute: 'value' })).toBe(true);
});
it('does not allow an object with a non-existing required attribute', () => {
expect(isPopulatedObject({ attribute: 'value' }, ['otherAttribute'])).toBe(false);
});
it('allows an object with an existing required attribute', () => {
expect(isPopulatedObject({ attribute: 'value' }, ['attribute'])).toBe(true);
});
it('allows an object with two existing required attributes', () => {
expect(
isPopulatedObject({ attribute1: 'value1', attribute2: 'value2' }, [
'attribute1',
'attribute2',
])
).toBe(true);
});
it('does not allow an object with two required attributes where one does not exist', () => {
expect(
isPopulatedObject({ attribute1: 'value1', attribute2: 'value2' }, [
'attribute1',
'otherAttribute',
])
).toBe(false);
});
});
});

View file

@ -5,6 +5,32 @@
* 2.0.
*/
export const isPopulatedObject = <T = Record<string, any>>(arg: any): arg is T => {
return typeof arg === 'object' && arg !== null && Object.keys(arg).length > 0;
/*
* A type guard to check record like object structures.
*
* Examples:
* - `isPopulatedObject({...})`
* Limits type to Record<string, unknown>
*
* - `isPopulatedObject({...}, ['attribute'])`
* Limits type to Record<'attribute', unknown>
*
* - `isPopulatedObject<keyof MyInterface>({...})`
* Limits type to a record with keys of the given interface.
* Note that you might want to add keys from the interface to the
* array of requiredAttributes to satisfy runtime requirements.
* Otherwise you'd just satisfy TS requirements but might still
* run into runtime issues.
*/
export const isPopulatedObject = <U extends string = string>(
arg: unknown,
requiredAttributes: U[] = []
): arg is Record<U, unknown> => {
return (
typeof arg === 'object' &&
arg !== null &&
Object.keys(arg).length > 0 &&
(requiredAttributes.length === 0 ||
requiredAttributes.every((d) => ({}.hasOwnProperty.call(arg, d))))
);
};

View file

@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { isRuntimeField, isRuntimeMappings } from './runtime_field_utils';
describe('ML runtime field utils', () => {
describe('isRuntimeField()', () => {
it('does not allow numbers', () => {
expect(isRuntimeField(1)).toBe(false);
});
it('does not allow null', () => {
expect(isRuntimeField(null)).toBe(false);
});
it('does not allow arrays', () => {
expect(isRuntimeField([])).toBe(false);
});
it('does not allow empty objects', () => {
expect(isRuntimeField({})).toBe(false);
});
it('does not allow objects with non-matching attributes', () => {
expect(isRuntimeField({ someAttribute: 'someValue' })).toBe(false);
expect(isRuntimeField({ type: 'wrong-type' })).toBe(false);
expect(isRuntimeField({ type: 'keyword', someAttribute: 'some value' })).toBe(false);
});
it('allows objects with type attribute only', () => {
expect(isRuntimeField({ type: 'keyword' })).toBe(true);
});
it('allows objects with both type and script attributes', () => {
expect(isRuntimeField({ type: 'keyword', script: 'some script' })).toBe(true);
});
});
describe('isRuntimeMappings()', () => {
it('does not allow numbers', () => {
expect(isRuntimeMappings(1)).toBe(false);
});
it('does not allow null', () => {
expect(isRuntimeMappings(null)).toBe(false);
});
it('does not allow arrays', () => {
expect(isRuntimeMappings([])).toBe(false);
});
it('does not allow empty objects', () => {
expect(isRuntimeMappings({})).toBe(false);
});
it('does not allow objects with non-object inner structure', () => {
expect(isRuntimeMappings({ someAttribute: 'someValue' })).toBe(false);
});
it('does not allow objects with objects with unsupported inner structure', () => {
expect(isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: 'someValue' })).toBe(
false
);
expect(
isRuntimeMappings({
fieldName1: { type: 'keyword' },
fieldName2: { type: 'keyword', someAttribute: 'some value' },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: 1234 },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { someAttribute: 'some value' } },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { source: 1234 } },
})
).toBe(false);
});
it('allows object with most basic runtime mapping', () => {
expect(isRuntimeMappings({ fieldName: { type: 'keyword' } })).toBe(true);
});
it('allows object with multiple most basic runtime mappings', () => {
expect(
isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: { type: 'keyword' } })
).toBe(true);
});
it('allows object with runtime mappings including scripts', () => {
expect(
isRuntimeMappings({
fieldName1: { type: 'keyword' },
fieldName2: { type: 'keyword', script: 'some script as script' },
})
).toBe(true);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { source: 'some script as source' } },
})
).toBe(true);
});
});
});

View file

@ -9,19 +9,18 @@ import { isPopulatedObject } from './object_utils';
import { RUNTIME_FIELD_TYPES } from '../../../../../src/plugins/data/common';
import type { RuntimeField, RuntimeMappings } from '../types/fields';
type RuntimeType = typeof RUNTIME_FIELD_TYPES[number];
export function isRuntimeField(arg: unknown): arg is RuntimeField {
return (
isPopulatedObject(arg) &&
((Object.keys(arg).length === 1 && arg.hasOwnProperty('type')) ||
(Object.keys(arg).length === 2 &&
arg.hasOwnProperty('type') &&
arg.hasOwnProperty('script') &&
((isPopulatedObject(arg, ['type']) && Object.keys(arg).length === 1) ||
(isPopulatedObject(arg, ['type', 'script']) &&
Object.keys(arg).length === 2 &&
(typeof arg.script === 'string' ||
(isPopulatedObject(arg.script) &&
(isPopulatedObject(arg.script, ['source']) &&
Object.keys(arg.script).length === 1 &&
arg.script.hasOwnProperty('source') &&
typeof arg.script.source === 'string')))) &&
RUNTIME_FIELD_TYPES.includes(arg.type)
RUNTIME_FIELD_TYPES.includes(arg.type as RuntimeType)
);
}

View file

@ -7,6 +7,7 @@
import { Dispatch, SetStateAction } from 'react';
import { estypes } from '@elastic/elasticsearch';
import {
EuiDataGridCellValueElementProps,
EuiDataGridPaginationProps,
@ -15,7 +16,6 @@ import {
} from '@elastic/eui';
import { Dictionary } from '../../../../common/types/common';
import { HitsTotalRelation } from '../../../../common/types/es_client';
import { ChartData } from '../../../../common/types/field_histograms';
import { INDEX_STATUS } from '../../data_frame_analytics/common/analytics';
@ -27,7 +27,7 @@ export type DataGridItem = Record<string, any>;
// `undefined` is used to indicate a non-initialized state.
export type ChartsVisible = boolean | undefined;
export type RowCountRelation = HitsTotalRelation | undefined;
export type RowCountRelation = estypes.TotalHitsRelation | undefined;
export type IndexPagination = Pick<EuiDataGridPaginationProps, 'pageIndex' | 'pageSize'>;

View file

@ -9,7 +9,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { EuiDataGridSorting, EuiDataGridColumn } from '@elastic/eui';
import { HITS_TOTAL_RELATION } from '../../../../common/types/es_client';
import { ES_CLIENT_TOTAL_HITS_RELATION } from '../../../../common/types/es_client';
import { ChartData } from '../../../../common/types/field_histograms';
import { INDEX_STATUS } from '../../data_frame_analytics/common';
@ -146,7 +146,7 @@ export const useDataGrid = (
if (chartsVisible === undefined && rowCount > 0 && rowCountRelation !== undefined) {
setChartsVisible(
rowCount <= COLUMN_CHART_DEFAULT_VISIBILITY_ROWS_THRESHOLED &&
rowCountRelation !== HITS_TOTAL_RELATION.GTE
rowCountRelation !== ES_CLIENT_TOTAL_HITS_RELATION.GTE
);
}
}, [chartsVisible, rowCount, rowCountRelation]);

View file

@ -7,6 +7,8 @@
import React, { useMemo, useEffect, useState, FC } from 'react';
import { estypes } from '@elastic/elasticsearch';
import {
EuiCallOut,
EuiComboBox,
@ -24,7 +26,6 @@ import { i18n } from '@kbn/i18n';
import { extractErrorMessage } from '../../../../common';
import { stringHash } from '../../../../common/util/string_utils';
import type { SearchResponse7 } from '../../../../common/types/es_client';
import type { ResultsSearchQuery } from '../../data_frame_analytics/common/analytics';
import { useMlApiContext } from '../../contexts/kibana';
@ -184,7 +185,7 @@ export const ScatterplotMatrix: FC<ScatterplotMatrixProps> = ({
}
: searchQuery;
const resp: SearchResponse7 = await esSearch({
const resp: estypes.SearchResponse = await esSearch({
index,
body: {
fields: queryFields,
@ -198,7 +199,7 @@ export const ScatterplotMatrix: FC<ScatterplotMatrixProps> = ({
if (!options.didCancel) {
const items = resp.hits.hits
.map((d) =>
getProcessedFields(d.fields, (key: string) =>
getProcessedFields(d.fields ?? {}, (key: string) =>
key.startsWith(`${resultsField}.feature_importance`)
)
)

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { SearchResponse7 } from '../../../../common/types/es_client';
import type { estypes } from '@elastic/elasticsearch';
import { extractErrorMessage } from '../../../../common/util/errors';
import { EsSorting, UseDataGridReturnType, getProcessedFields } from '../../components/data_grid';
@ -51,7 +51,7 @@ export const getIndexData = async (
const { pageIndex, pageSize } = pagination;
// TODO: remove results_field from `fields` when possible
const resp: SearchResponse7 = await ml.esSearch({
const resp: estypes.SearchResponse = await ml.esSearch({
index: jobConfig.dest.index,
body: {
fields: ['*'],
@ -64,11 +64,15 @@ export const getIndexData = async (
});
if (!options.didCancel) {
setRowCount(resp.hits.total.value);
setRowCountRelation(resp.hits.total.relation);
setRowCount(typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total.value);
setRowCountRelation(
typeof resp.hits.total === 'number'
? ('eq' as estypes.TotalHitsRelation)
: resp.hits.total.relation
);
setTableItems(
resp.hits.hits.map((d) =>
getProcessedFields(d.fields, (key: string) =>
getProcessedFields(d.fields ?? {}, (key: string) =>
key.startsWith(`${jobConfig.dest.results_field}.feature_importance`)
)
)

View file

@ -7,6 +7,7 @@
import { useEffect, useMemo } from 'react';
import { estypes } from '@elastic/elasticsearch';
import { EuiDataGridColumn } from '@elastic/eui';
import { CoreSetup } from 'src/core/public';
@ -26,13 +27,12 @@ import {
UseIndexDataReturnType,
getProcessedFields,
} from '../../../../components/data_grid';
import type { SearchResponse7 } from '../../../../../../common/types/es_client';
import { extractErrorMessage } from '../../../../../../common/util/errors';
import { INDEX_STATUS } from '../../../common/analytics';
import { ml } from '../../../../services/ml_api_service';
import { getRuntimeFieldsMapping } from '../../../../components/data_grid/common';
type IndexSearchResponse = SearchResponse7;
type IndexSearchResponse = estypes.SearchResponse;
export const useIndexData = (
indexPattern: IndexPattern,
@ -95,9 +95,13 @@ export const useIndexData = (
try {
const resp: IndexSearchResponse = await ml.esSearch(esSearchRequest);
const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields));
setRowCount(resp.hits.total.value);
setRowCountRelation(resp.hits.total.relation);
const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields ?? {}));
setRowCount(typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total.value);
setRowCountRelation(
typeof resp.hits.total === 'number'
? ('eq' as estypes.TotalHitsRelation)
: resp.hits.total.relation
);
setTableItems(docs);
setStatus(INDEX_STATUS.LOADED);
} catch (e) {

View file

@ -18,7 +18,7 @@ import {
isClassificationAnalysis,
isRegressionAnalysis,
} from '../../../../../../../common/util/analytics_utils';
import { HITS_TOTAL_RELATION } from '../../../../../../../common/types/es_client';
import { ES_CLIENT_TOTAL_HITS_RELATION } from '../../../../../../../common/types/es_client';
import { getToastNotifications } from '../../../../../util/dependency_cache';
import { useColorRange, ColorRangeLegend } from '../../../../../components/color_range_legend';
@ -77,7 +77,7 @@ const getResultsSectionHeaderItems = (
defaultMessage="Total docs"
/>
),
value: `${rowCountRelation === HITS_TOTAL_RELATION.GTE ? '>' : ''}${rowCount}`,
value: `${rowCountRelation === ES_CLIENT_TOTAL_HITS_RELATION.GTE ? '>' : ''}${rowCount}`,
},
...(colorRange !== undefined
? [

View file

@ -22,8 +22,10 @@ interface Response {
export function filterRuntimeMappings(job: Job, datafeed: Datafeed): Response {
if (
datafeed.runtime_mappings === undefined ||
isPopulatedObject(datafeed.runtime_mappings) === false
!(
isPopulatedObject(datafeed, ['runtime_mappings']) &&
isPopulatedObject(datafeed.runtime_mappings)
)
) {
return {
runtime_mappings: {},
@ -83,7 +85,7 @@ function findFieldsInJob(job: Job, datafeed: Datafeed) {
return [...usedFields];
}
function findFieldsInAgg(obj: Record<string, object>) {
function findFieldsInAgg(obj: Record<string, unknown>) {
const fields: string[] = [];
Object.entries(obj).forEach(([key, val]) => {
if (isPopulatedObject(val)) {

View file

@ -22,6 +22,7 @@ import { MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor';
import { mlJobService } from '../../../../../../services/job_service';
import { ML_DATA_PREVIEW_COUNT } from '../../../../../../../../common/util/job_utils';
import { isPopulatedObject } from '../../../../../../../../common/util/object_utils';
import { isMultiBucketAggregate } from '../../../../../../../../common/types/es_client';
export const DatafeedPreview: FC<{
combinedJob: CombinedJob | null;
@ -67,7 +68,10 @@ export const DatafeedPreview: FC<{
// the first item under aggregations can be any name
if (isPopulatedObject(resp.aggregations)) {
const accessor = Object.keys(resp.aggregations)[0];
data = resp.aggregations[accessor].buckets.slice(0, ML_DATA_PREVIEW_COUNT);
const aggregate = resp.aggregations[accessor];
if (isMultiBucketAggregate(aggregate)) {
data = aggregate.buckets.slice(0, ML_DATA_PREVIEW_COUNT);
}
}
setPreviewJsonString(JSON.stringify(data, null, 2));

View file

@ -90,11 +90,7 @@ export interface SeriesConfigWithMetadata extends SeriesConfig {
}
export const isSeriesConfigWithMetadata = (arg: unknown): arg is SeriesConfigWithMetadata => {
return (
isPopulatedObject(arg) &&
{}.hasOwnProperty.call(arg, 'bucketSpanSeconds') &&
{}.hasOwnProperty.call(arg, 'detectorLabel')
);
return isPopulatedObject(arg, ['bucketSpanSeconds', 'detectorLabel']);
};
interface ChartRange {

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import { SearchResponse } from 'elasticsearch';
import { estypes } from '@elastic/elasticsearch';
import { TimeRange } from 'src/plugins/data/common/query/timefilter/types';
import { CombinedJob, Datafeed, Job } from '../../../common/types/anomaly_detection_jobs';
import { Calendar } from '../../../common/types/calendars';
@ -40,7 +41,7 @@ declare interface JobService {
): Promise<any>;
createResultsUrl(jobId: string[], start: number, end: number, location: string): string;
getJobAndGroupIds(): Promise<ExistingJobsAndGroups>;
searchPreview(job: CombinedJob): Promise<SearchResponse<any>>;
searchPreview(job: CombinedJob): Promise<estypes.SearchResponse<any>>;
getJob(jobId: string): CombinedJob;
loadJobsWrapper(): Promise<CombinedJob[]>;
}

View file

@ -81,8 +81,8 @@ export interface SwimLaneDrilldownContext extends EditSwimlanePanelContext {
export function isSwimLaneEmbeddable(arg: unknown): arg is SwimLaneDrilldownContext {
return (
isPopulatedObject(arg) &&
arg.hasOwnProperty('embeddable') &&
isPopulatedObject(arg, ['embeddable']) &&
isPopulatedObject(arg.embeddable, ['type']) &&
arg.embeddable.type === ANOMALY_SWIMLANE_EMBEDDABLE_TYPE
);
}
@ -130,8 +130,8 @@ export function isAnomalyExplorerEmbeddable(
arg: unknown
): arg is AnomalyChartsFieldSelectionContext {
return (
isPopulatedObject(arg) &&
arg.hasOwnProperty('embeddable') &&
isPopulatedObject(arg, ['embeddable']) &&
isPopulatedObject(arg.embeddable, ['type']) &&
arg.embeddable.type === ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE
);
}

View file

@ -50,7 +50,7 @@ export {
getSeverityType,
getFormattedSeverityScore,
} from '../common/util/anomaly_utils';
export { HITS_TOTAL_RELATION } from '../common/types/es_client';
export { ES_CLIENT_TOTAL_HITS_RELATION } from '../common/types/es_client';
export { ANOMALY_SEVERITY } from '../common';
export { useMlHref, ML_PAGES, MlUrlGenerator } from './ml_url_generator';

View file

@ -25,7 +25,6 @@ import {
isClassificationAnalysis,
} from '../../../common/util/analytics_utils';
import { extractErrorMessage } from '../../../common/util/errors';
import { SearchResponse7 } from '../../../common';
import {
AnalysisConfig,
DataFrameAnalyticsConfig,
@ -42,7 +41,7 @@ interface CardinalityAgg {
};
}
type ValidationSearchResult = Omit<SearchResponse7, 'aggregations'> & {
type ValidationSearchResult = Omit<estypes.SearchResponse, 'aggregations'> & {
aggregations: MissingAgg | CardinalityAgg;
};

View file

@ -5,9 +5,10 @@
* 2.0.
*/
import type { estypes } from '@elastic/elasticsearch';
import { IScopedClusterClient } from 'kibana/server';
import { chunk } from 'lodash';
import { SearchResponse } from 'elasticsearch';
import { CATEGORY_EXAMPLES_SAMPLE_SIZE } from '../../../../../common/constants/categorization_job';
import {
Token,
@ -61,7 +62,7 @@ export function categorizationExamplesProvider({
}
}
}
const { body } = await asCurrentUser.search<SearchResponse<{ [id: string]: string }>>({
const { body } = await asCurrentUser.search<estypes.SearchResponse<{ [id: string]: string }>>({
index: indexPatternTitle,
size,
body: {

View file

@ -5,13 +5,14 @@
* 2.0.
*/
import { SearchResponse } from 'elasticsearch';
import type { estypes } from '@elastic/elasticsearch';
import { CategoryId, Category } from '../../../../../common/types/categories';
import type { MlClient } from '../../../../lib/ml_client';
export function topCategoriesProvider(mlClient: MlClient) {
async function getTotalCategories(jobId: string): Promise<number> {
const { body } = await mlClient.anomalySearch<SearchResponse<any>>(
const { body } = await mlClient.anomalySearch<estypes.SearchResponse<any>>(
{
size: 0,
body: {
@ -35,12 +36,11 @@ export function topCategoriesProvider(mlClient: MlClient) {
},
[]
);
// @ts-ignore total is an object here
return body?.hits?.total?.value ?? 0;
return typeof body.hits.total === 'number' ? body.hits.total : body.hits.total.value;
}
async function getTopCategoryCounts(jobId: string, numberOfCategories: number) {
const { body } = await mlClient.anomalySearch<SearchResponse<any>>(
const { body } = await mlClient.anomalySearch<estypes.SearchResponse<any>>(
{
size: 0,
body: {

View file

@ -8,13 +8,15 @@
import { IScopedClusterClient } from 'kibana/server';
import { validateJob, ValidateJobPayload } from './job_validation';
import { HITS_TOTAL_RELATION } from '../../../common/types/es_client';
import { ES_CLIENT_TOTAL_HITS_RELATION } from '../../../common/types/es_client';
import type { MlClient } from '../../lib/ml_client';
const callAs = {
fieldCaps: () => Promise.resolve({ body: { fields: [] } }),
search: () =>
Promise.resolve({ body: { hits: { total: { value: 1, relation: HITS_TOTAL_RELATION.EQ } } } }),
Promise.resolve({
body: { hits: { total: { value: 1, relation: ES_CLIENT_TOTAL_HITS_RELATION.EQ } } },
}),
};
const mlClusterClient = ({

View file

@ -5,8 +5,9 @@
* 2.0.
*/
import type { estypes } from '@elastic/elasticsearch';
import { KibanaRequest, SavedObjectsClientContract } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import { MlLicense } from '../../../common/license';
import { CloudSetup } from '../../../../cloud/server';
import { spacesUtilsProvider } from '../../lib/spaces_utils';
@ -23,7 +24,7 @@ export interface MlSystemProvider {
): {
mlCapabilities(): Promise<MlCapabilitiesResponse>;
mlInfo(): Promise<MlInfoResponse>;
mlAnomalySearch<T>(searchParams: any, jobIds: string[]): Promise<SearchResponse<T>>;
mlAnomalySearch<T>(searchParams: any, jobIds: string[]): Promise<estypes.SearchResponse<T>>;
};
}
@ -69,7 +70,10 @@ export function getMlSystemProvider(
};
});
},
async mlAnomalySearch<T>(searchParams: any, jobIds: string[]): Promise<SearchResponse<T>> {
async mlAnomalySearch<T>(
searchParams: any,
jobIds: string[]
): Promise<estypes.SearchResponse<T>> {
return await getGuards(request, savedObjectsClient)
.isFullLicense()
.hasMlCapabilities(['canAccessML'])

View file

@ -5,10 +5,10 @@
* 2.0.
*/
import type { SearchResponse7 } from '../../../ml/common';
import type { estypes } from '@elastic/elasticsearch';
import type { EsIndex } from '../types/es_index';
import { isPopulatedObject } from '../utils/object_utils';
import { isPopulatedObject } from '../shared_imports';
// To be able to use the type guards on the client side, we need to make sure we don't import
// the code of '@kbn/config-schema' but just its types, otherwise the client side code will
@ -28,20 +28,13 @@ import type { GetTransformsStatsResponseSchema } from './transforms_stats';
import type { PostTransformsUpdateResponseSchema } from './update_transforms';
const isGenericResponseSchema = <T>(arg: any): arg is T => {
return (
isPopulatedObject(arg) &&
{}.hasOwnProperty.call(arg, 'count') &&
{}.hasOwnProperty.call(arg, 'transforms') &&
Array.isArray(arg.transforms)
);
return isPopulatedObject(arg, ['count', 'transforms']) && Array.isArray(arg.transforms);
};
export const isGetTransformNodesResponseSchema = (
arg: unknown
): arg is GetTransformNodesResponseSchema => {
return (
isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'count') && typeof arg.count === 'number'
);
return isPopulatedObject(arg, ['count']) && typeof arg.count === 'number';
};
export const isGetTransformsResponseSchema = (arg: unknown): arg is GetTransformsResponseSchema => {
@ -59,7 +52,7 @@ export const isDeleteTransformsResponseSchema = (
): arg is DeleteTransformsResponseSchema => {
return (
isPopulatedObject(arg) &&
Object.values(arg).every((d) => ({}.hasOwnProperty.call(d, 'transformDeleted')))
Object.values(arg).every((d) => isPopulatedObject(d, ['transformDeleted']))
);
};
@ -67,8 +60,22 @@ export const isEsIndices = (arg: unknown): arg is EsIndex[] => {
return Array.isArray(arg);
};
export const isEsSearchResponse = (arg: unknown): arg is SearchResponse7 => {
return isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'hits');
export const isEsSearchResponse = (arg: unknown): arg is estypes.SearchResponse => {
return isPopulatedObject(arg, ['hits']);
};
type SearchResponseWithAggregations = Required<Pick<estypes.SearchResponse, 'aggregations'>> &
estypes.SearchResponse;
export const isEsSearchResponseWithAggregations = (
arg: unknown
): arg is SearchResponseWithAggregations => {
return isEsSearchResponse(arg) && {}.hasOwnProperty.call(arg, 'aggregations');
};
export const isMultiBucketAggregate = <TBucket = unknown>(
arg: unknown
): arg is estypes.MultiBucketAggregate<TBucket> => {
return isPopulatedObject(arg, ['buckets']);
};
export const isFieldHistogramsResponseSchema = (
@ -87,9 +94,7 @@ export const isPostTransformsPreviewResponseSchema = (
arg: unknown
): arg is PostTransformsPreviewResponseSchema => {
return (
isPopulatedObject(arg) &&
{}.hasOwnProperty.call(arg, 'generated_dest_index') &&
{}.hasOwnProperty.call(arg, 'preview') &&
isPopulatedObject(arg, ['generated_dest_index', 'preview']) &&
typeof arg.generated_dest_index !== undefined &&
Array.isArray(arg.preview)
);
@ -98,21 +103,19 @@ export const isPostTransformsPreviewResponseSchema = (
export const isPostTransformsUpdateResponseSchema = (
arg: unknown
): arg is PostTransformsUpdateResponseSchema => {
return isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'id') && typeof arg.id === 'string';
return isPopulatedObject(arg, ['id']) && typeof arg.id === 'string';
};
export const isPutTransformsResponseSchema = (arg: unknown): arg is PutTransformsResponseSchema => {
return (
isPopulatedObject(arg) &&
{}.hasOwnProperty.call(arg, 'transformsCreated') &&
{}.hasOwnProperty.call(arg, 'errors') &&
isPopulatedObject(arg, ['transformsCreated', 'errors']) &&
Array.isArray(arg.transformsCreated) &&
Array.isArray(arg.errors)
);
};
const isGenericSuccessResponseSchema = (arg: unknown) =>
isPopulatedObject(arg) && Object.values(arg).every((d) => ({}.hasOwnProperty.call(d, 'success')));
isPopulatedObject(arg) && Object.values(arg).every((d) => isPopulatedObject(d, ['success']));
export const isStartTransformsResponseSchema = (
arg: unknown

View file

@ -5,10 +5,10 @@
* 2.0.
*/
export type { HitsTotalRelation, SearchResponse7 } from '../../ml/common';
export {
composeValidators,
isPopulatedObject,
isRuntimeMappings,
patternValidator,
ChartData,
HITS_TOTAL_RELATION,
} from '../../ml/common';

View file

@ -7,17 +7,17 @@
import type { IndexPattern } from '../../../../../src/plugins/data/common';
import { isPopulatedObject } from '../utils/object_utils';
import { isPopulatedObject } from '../shared_imports';
// Custom minimal type guard for IndexPattern to check against the attributes used in transforms code.
export function isIndexPattern(arg: any): arg is IndexPattern {
return (
isPopulatedObject(arg) &&
'getComputedFields' in arg &&
typeof arg.getComputedFields === 'function' &&
{}.hasOwnProperty.call(arg, 'title') &&
isPopulatedObject(arg, ['title', 'fields']) &&
// `getComputedFields` is inherited, so it's not possible to
// check with `hasOwnProperty` which is used by isPopulatedObject()
'getComputedFields' in (arg as IndexPattern) &&
typeof (arg as IndexPattern).getComputedFields === 'function' &&
typeof arg.title === 'string' &&
{}.hasOwnProperty.call(arg, 'fields') &&
Array.isArray(arg.fields)
);
}

View file

@ -7,7 +7,7 @@
import { EuiComboBoxOptionOption } from '@elastic/eui/src/components/combo_box/types';
import type { LatestFunctionConfig, PutTransformsRequestSchema } from '../api_schemas/transforms';
import { isPopulatedObject } from '../utils/object_utils';
import { isPopulatedObject } from '../shared_imports';
import { PivotGroupByDict } from './pivot_group_by';
import { PivotAggDict } from './pivot_aggs';
@ -46,11 +46,11 @@ export type TransformLatestConfig = Omit<TransformBaseConfig, 'pivot'> & {
export type TransformConfigUnion = TransformPivotConfig | TransformLatestConfig;
export function isPivotTransform(transform: unknown): transform is TransformPivotConfig {
return isPopulatedObject(transform) && transform.hasOwnProperty('pivot');
return isPopulatedObject(transform, ['pivot']);
}
export function isLatestTransform(transform: unknown): transform is TransformLatestConfig {
return isPopulatedObject(transform) && transform.hasOwnProperty('latest');
return isPopulatedObject(transform, ['latest']);
}
export interface LatestFunctionConfigUI {

View file

@ -6,7 +6,7 @@
*/
import { TransformState, TRANSFORM_STATE } from '../constants';
import { isPopulatedObject } from '../utils/object_utils';
import { isPopulatedObject } from '../shared_imports';
import { TransformId } from './transform';
export interface TransformStats {
@ -61,7 +61,5 @@ function isTransformState(arg: unknown): arg is TransformState {
}
export function isTransformStats(arg: unknown): arg is TransformStats {
return (
isPopulatedObject(arg) && {}.hasOwnProperty.call(arg, 'state') && isTransformState(arg.state)
);
return isPopulatedObject(arg, ['state']) && isTransformState(arg.state);
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { isPopulatedObject } from './object_utils';
import { isPopulatedObject } from '../shared_imports';
export interface ErrorResponse {
body: {
@ -18,7 +18,11 @@ export interface ErrorResponse {
}
export function isErrorResponse(arg: unknown): arg is ErrorResponse {
return isPopulatedObject(arg) && isPopulatedObject(arg.body) && arg?.body?.message !== undefined;
return (
isPopulatedObject(arg, ['body']) &&
isPopulatedObject(arg.body, ['message']) &&
arg.body.message !== undefined
);
}
export function getErrorMessage(error: unknown) {
@ -26,7 +30,7 @@ export function getErrorMessage(error: unknown) {
return `${error.body.error}: ${error.body.message}`;
}
if (isPopulatedObject(error) && typeof error.message === 'string') {
if (isPopulatedObject(error, ['message']) && typeof error.message === 'string') {
return error.message;
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { getNestedProperty, isPopulatedObject } from './object_utils';
import { getNestedProperty } from './object_utils';
describe('object_utils', () => {
test('getNestedProperty()', () => {
@ -68,12 +68,4 @@ describe('object_utils', () => {
expect(typeof test11).toBe('number');
expect(test11).toBe(0);
});
test('isPopulatedObject()', () => {
expect(isPopulatedObject(0)).toBe(false);
expect(isPopulatedObject('')).toBe(false);
expect(isPopulatedObject(null)).toBe(false);
expect(isPopulatedObject({})).toBe(false);
expect(isPopulatedObject({ attribute: 'value' })).toBe(true);
});
});

View file

@ -51,7 +51,3 @@ export const setNestedProperty = (obj: Record<string, any>, accessor: string, va
return obj;
};
export const isPopulatedObject = <T = Record<string, unknown>>(arg: unknown): arg is T => {
return typeof arg === 'object' && arg !== null && Object.keys(arg).length > 0;
};

View file

@ -16,4 +16,4 @@ export const useRequest = jest.fn(() => ({
export const createSavedSearchesLoader = jest.fn();
// just passing through the reimports
export { getMlSharedImports, HITS_TOTAL_RELATION } from '../../../ml/public';
export { getMlSharedImports, ES_CLIENT_TOTAL_HITS_RELATION } from '../../../ml/public';

View file

@ -14,7 +14,7 @@ import type { Dictionary } from '../../../common/types/common';
import type { EsFieldName } from '../../../common/types/fields';
import type { PivotAgg, PivotSupportedAggs } from '../../../common/types/pivot_aggs';
import { PIVOT_SUPPORTED_AGGS } from '../../../common/types/pivot_aggs';
import { isPopulatedObject } from '../../../common/utils/object_utils';
import { isPopulatedObject } from '../../../common/shared_imports';
import { getAggFormConfig } from '../sections/create_transform/components/step_define/common/get_agg_form_config';
import { PivotAggsConfigFilter } from '../sections/create_transform/components/step_define/common/filter_agg/types';
@ -166,11 +166,7 @@ export type PivotAggsConfigWithUiSupport =
export function isPivotAggsConfigWithUiSupport(arg: unknown): arg is PivotAggsConfigWithUiSupport {
return (
isPopulatedObject(arg) &&
arg.hasOwnProperty('agg') &&
arg.hasOwnProperty('aggName') &&
arg.hasOwnProperty('dropDownName') &&
arg.hasOwnProperty('field') &&
isPopulatedObject(arg, ['agg', 'aggName', 'dropDownName', 'field']) &&
isPivotSupportedAggs(arg.agg)
);
}
@ -181,15 +177,12 @@ export function isPivotAggsConfigWithUiSupport(arg: unknown): arg is PivotAggsCo
type PivotAggsConfigWithExtendedForm = PivotAggsConfigFilter;
export function isPivotAggsWithExtendedForm(arg: unknown): arg is PivotAggsConfigWithExtendedForm {
return isPopulatedObject(arg) && arg.hasOwnProperty('AggFormComponent');
return isPopulatedObject(arg, ['AggFormComponent']);
}
export function isPivotAggsConfigPercentiles(arg: unknown): arg is PivotAggsConfigPercentiles {
return (
isPopulatedObject(arg) &&
arg.hasOwnProperty('agg') &&
arg.hasOwnProperty('field') &&
arg.hasOwnProperty('percents') &&
isPopulatedObject(arg, ['agg', 'field', 'percents']) &&
arg.agg === PIVOT_SUPPORTED_AGGS.PERCENTILES
);
}

View file

@ -9,7 +9,7 @@ import { AggName } from '../../../common/types/aggregations';
import { Dictionary } from '../../../common/types/common';
import { EsFieldName } from '../../../common/types/fields';
import { GenericAgg } from '../../../common/types/pivot_group_by';
import { isPopulatedObject } from '../../../common/utils/object_utils';
import { isPopulatedObject } from '../../../common/shared_imports';
import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/common';
import { PivotAggsConfigWithUiSupport } from './pivot_aggs';
@ -84,30 +84,21 @@ export type PivotGroupByConfigDict = Dictionary<PivotGroupByConfig>;
export function isGroupByDateHistogram(arg: unknown): arg is GroupByDateHistogram {
return (
isPopulatedObject(arg) &&
arg.hasOwnProperty('agg') &&
arg.hasOwnProperty('field') &&
arg.hasOwnProperty('calendar_interval') &&
isPopulatedObject(arg, ['agg', 'field', 'calendar_interval']) &&
arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.DATE_HISTOGRAM
);
}
export function isGroupByHistogram(arg: unknown): arg is GroupByHistogram {
return (
isPopulatedObject(arg) &&
arg.hasOwnProperty('agg') &&
arg.hasOwnProperty('field') &&
arg.hasOwnProperty('interval') &&
isPopulatedObject(arg, ['agg', 'field', 'interval']) &&
arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.HISTOGRAM
);
}
export function isGroupByTerms(arg: unknown): arg is GroupByTerms {
return (
isPopulatedObject(arg) &&
arg.hasOwnProperty('agg') &&
arg.hasOwnProperty('field') &&
arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS
isPopulatedObject(arg, ['agg', 'field']) && arg.agg === PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS
);
}
@ -124,5 +115,5 @@ export function getEsAggFromGroupByConfig(groupByConfig: GroupByConfigBase): Gen
}
export function isPivotAggConfigWithUiSupport(arg: unknown): arg is PivotAggsConfigWithUiSupport {
return isPopulatedObject(arg) && arg.hasOwnProperty('agg') && arg.hasOwnProperty('field');
return isPopulatedObject(arg, ['agg', 'field']);
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { DefaultOperator } from 'elasticsearch';
import type { estypes } from '@elastic/elasticsearch';
import { HttpFetchError } from '../../../../../../src/core/public';
import type { IndexPattern } from '../../../../../../src/plugins/data/public';
@ -17,7 +17,7 @@ import type {
PutTransformsPivotRequestSchema,
PutTransformsRequestSchema,
} from '../../../common/api_schemas/transforms';
import { isPopulatedObject } from '../../../common/utils/object_utils';
import { isPopulatedObject } from '../../../common/shared_imports';
import { DateHistogramAgg, HistogramAgg, TermsAgg } from '../../../common/types/pivot_group_by';
import { isIndexPattern } from '../../../common/types/index_pattern';
@ -39,7 +39,7 @@ import {
export interface SimpleQuery {
query_string: {
query: string;
default_operator?: DefaultOperator;
default_operator?: estypes.DefaultOperator;
};
}
@ -59,14 +59,13 @@ export function getPivotQuery(search: string | SavedSearchQuery): PivotQuery {
}
export function isSimpleQuery(arg: unknown): arg is SimpleQuery {
return isPopulatedObject(arg) && arg.hasOwnProperty('query_string');
return isPopulatedObject(arg, ['query_string']);
}
export const matchAllQuery = { match_all: {} };
export function isMatchAllQuery(query: unknown): boolean {
return (
isPopulatedObject(query) &&
query.hasOwnProperty('match_all') &&
isPopulatedObject(query, ['match_all']) &&
typeof query.match_all === 'object' &&
query.match_all !== null &&
Object.keys(query.match_all).length === 0
@ -101,7 +100,7 @@ export function getCombinedRuntimeMappings(
combinedRuntimeMappings = { ...combinedRuntimeMappings, ...runtimeMappings };
}
if (isPopulatedObject<StepDefineExposedState['runtimeMappings']>(combinedRuntimeMappings)) {
if (isPopulatedObject<keyof StepDefineExposedState['runtimeMappings']>(combinedRuntimeMappings)) {
return combinedRuntimeMappings;
}
return undefined;

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import { estypes } from '@elastic/elasticsearch';
import { HttpFetchError } from 'kibana/public';
import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public';
@ -37,7 +39,6 @@ import type {
PostTransformsUpdateResponseSchema,
} from '../../../../common/api_schemas/update_transforms';
import type { SearchResponse7 } from '../../../../common/shared_imports';
import { EsIndex } from '../../../../common/types/es_index';
import type { SavedSearchQuery } from '../use_search_items';
@ -134,7 +135,7 @@ const apiFactory = () => ({
): Promise<GetTransformsAuditMessagesResponseSchema | HttpFetchError> {
return Promise.resolve([]);
},
async esSearch(payload: any): Promise<SearchResponse7 | HttpFetchError> {
async esSearch(payload: any): Promise<estypes.SearchResponse | HttpFetchError> {
return Promise.resolve({
hits: {
hits: [],

View file

@ -7,6 +7,8 @@
import { useMemo } from 'react';
import { estypes } from '@elastic/elasticsearch';
import { HttpFetchError } from 'kibana/public';
import { KBN_FIELD_TYPES } from '../../../../../../src/plugins/data/public';
@ -44,7 +46,6 @@ import type { GetTransformsStatsResponseSchema } from '../../../common/api_schem
import { TransformId } from '../../../common/types/transform';
import { API_BASE_PATH } from '../../../common/constants';
import { EsIndex } from '../../../common/types/es_index';
import type { SearchResponse7 } from '../../../common/shared_imports';
import { useAppDependencies } from '../app_dependencies';
@ -187,7 +188,7 @@ export const useApi = () => {
return e;
}
},
async esSearch(payload: any): Promise<SearchResponse7 | HttpFetchError> {
async esSearch(payload: any): Promise<estypes.SearchResponse | HttpFetchError> {
try {
return await http.post(`${API_BASE_PATH}es_search`, { body: JSON.stringify(payload) });
} catch (e) {

View file

@ -7,7 +7,8 @@
import { useEffect, useMemo } from 'react';
import { EuiDataGridColumn } from '@elastic/eui';
import type { estypes } from '@elastic/elasticsearch';
import type { EuiDataGridColumn } from '@elastic/eui';
import {
isEsSearchResponse,
@ -133,10 +134,14 @@ export const useIndexData = (
return;
}
const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields));
const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields ?? {}));
setRowCount(resp.hits.total.value);
setRowCountRelation(resp.hits.total.relation);
setRowCount(typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total.value);
setRowCountRelation(
typeof resp.hits.total === 'number'
? ('eq' as estypes.TotalHitsRelation)
: resp.hits.total.relation
);
setTableItems(docs);
setStatus(INDEX_STATUS.LOADED);
};

View file

@ -18,7 +18,11 @@ import type { PreviewMappingsProperties } from '../../../common/api_schemas/tran
import { isPostTransformsPreviewResponseSchema } from '../../../common/api_schemas/type_guards';
import { getNestedProperty } from '../../../common/utils/object_utils';
import { RenderCellValue, UseIndexDataReturnType, HITS_TOTAL_RELATION } from '../../shared_imports';
import {
RenderCellValue,
UseIndexDataReturnType,
ES_CLIENT_TOTAL_HITS_RELATION,
} from '../../shared_imports';
import { getErrorMessage } from '../../../common/utils/errors';
import { useAppDependencies } from '../app_dependencies';
@ -128,7 +132,7 @@ export const usePivotData = (
if (!validationStatus.isValid) {
setTableItems([]);
setRowCount(0);
setRowCountRelation(HITS_TOTAL_RELATION.EQ);
setRowCountRelation(ES_CLIENT_TOTAL_HITS_RELATION.EQ);
setNoDataMessage(validationStatus.errorMessage!);
return;
}
@ -149,7 +153,7 @@ export const usePivotData = (
setErrorMessage(getErrorMessage(resp));
setTableItems([]);
setRowCount(0);
setRowCountRelation(HITS_TOTAL_RELATION.EQ);
setRowCountRelation(ES_CLIENT_TOTAL_HITS_RELATION.EQ);
setPreviewMappingsProperties({});
setStatus(INDEX_STATUS.ERROR);
return;
@ -157,7 +161,7 @@ export const usePivotData = (
setTableItems(resp.preview);
setRowCount(resp.preview.length);
setRowCountRelation(HITS_TOTAL_RELATION.EQ);
setRowCountRelation(ES_CLIENT_TOTAL_HITS_RELATION.EQ);
setPreviewMappingsProperties(resp.generated_dest_index.mappings.properties);
setStatus(INDEX_STATUS.LOADED);

View file

@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
import { Privileges } from '../../../../../common/types/privileges';
import { isPopulatedObject } from '../../../../../common/utils/object_utils';
import { isPopulatedObject } from '../../../../../common/shared_imports';
export interface Capabilities {
canGetTransform: boolean;
@ -22,10 +22,8 @@ export type Privilege = [string, string];
function isPrivileges(arg: unknown): arg is Privileges {
return (
isPopulatedObject(arg) &&
arg.hasOwnProperty('hasAllPrivileges') &&
isPopulatedObject(arg, ['hasAllPrivileges', 'missingPrivileges']) &&
typeof arg.hasAllPrivileges === 'boolean' &&
arg.hasOwnProperty('missingPrivileges') &&
typeof arg.missingPrivileges === 'object' &&
arg.missingPrivileges !== null
);

View file

@ -12,8 +12,9 @@ import { EuiCodeEditor } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isRuntimeMappings } from '../../../../../../common/shared_imports';
import { StepDefineFormHook } from '../step_define';
import { isRuntimeMappings } from '../step_define/common/types';
export const AdvancedRuntimeMappingsEditor: FC<StepDefineFormHook['runtimeMappingsEditor']> = memo(
({

View file

@ -47,7 +47,7 @@ import {
PutTransformsPivotRequestSchema,
} from '../../../../../../common/api_schemas/transforms';
import type { RuntimeField } from '../../../../../../../../../src/plugins/data/common/index_patterns';
import { isPopulatedObject } from '../../../../../../common/utils/object_utils';
import { isPopulatedObject } from '../../../../../../common/shared_imports';
import { isLatestTransform } from '../../../../../../common/types/transform';
export interface StepDetailsExposedState {

View file

@ -16,7 +16,7 @@ import { getFilterAggTypeConfig } from '../config';
import type { FilterAggType, PivotAggsConfigFilter } from '../types';
import type { RuntimeMappings } from '../../types';
import { getKibanaFieldTypeFromEsType } from '../../get_pivot_dropdown_options';
import { isPopulatedObject } from '../../../../../../../../../common/utils/object_utils';
import { isPopulatedObject } from '../../../../../../../../../common/shared_imports';
/**
* Resolves supported filters for provided field.

View file

@ -6,12 +6,16 @@
*/
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { EuiComboBox, EuiFormRow } from '@elastic/eui';
import { estypes } from '@elastic/elasticsearch';
import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { debounce } from 'lodash';
import useUpdateEffect from 'react-use/lib/useUpdateEffect';
import { i18n } from '@kbn/i18n';
import { isEsSearchResponse } from '../../../../../../../../../common/api_schemas/type_guards';
import {
isEsSearchResponseWithAggregations,
isMultiBucketAggregate,
} from '../../../../../../../../../common/api_schemas/type_guards';
import { useApi } from '../../../../../../../hooks';
import { CreateTransformWizardContext } from '../../../../wizard/wizard';
import { FilterAggConfigTerm } from '../types';
@ -29,7 +33,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm
const { indexPattern, runtimeMappings } = useContext(CreateTransformWizardContext);
const toastNotifications = useToastNotifications();
const [options, setOptions] = useState([]);
const [options, setOptions] = useState<EuiComboBoxOptionOption[]>([]);
const [isLoading, setIsLoading] = useState(true);
/* eslint-disable-next-line react-hooks/exhaustive-deps */
@ -62,7 +66,12 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm
setIsLoading(false);
if (!isEsSearchResponse(response)) {
if (
!(
isEsSearchResponseWithAggregations(response) &&
isMultiBucketAggregate<estypes.KeyedBucketKeys>(response.aggregations.field_values)
)
) {
toastNotifications.addWarning(
i18n.translate('xpack.transform.agg.popoverForm.filerAgg.term.errorFetchSuggestions', {
defaultMessage: 'Unable to fetch suggestions',
@ -72,9 +81,7 @@ export const FilterTermForm: FilterAggConfigTerm['aggTypeConfig']['FilterAggForm
}
setOptions(
response.aggregations.field_values.buckets.map(
(value: { key: string; doc_count: number }) => ({ label: value.key })
)
response.aggregations.field_values.buckets.map((value) => ({ label: value.key + '' }))
);
}, 600),
[selectedField]

View file

@ -13,6 +13,7 @@ import {
} from '../../../../../../../../../../src/plugins/data/public';
import { getNestedProperty } from '../../../../../../../common/utils/object_utils';
import { isRuntimeMappings } from '../../../../../../../common/shared_imports';
import {
DropDownLabel,
@ -26,7 +27,6 @@ import {
import { getDefaultAggregationConfig } from './get_default_aggregation_config';
import { getDefaultGroupByConfig } from './get_default_group_by_config';
import type { Field, StepDefineExposedState } from './types';
import { isRuntimeMappings } from './types';
const illegalEsAggNameChars = /[[\]>]/g;

View file

@ -1,71 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { isRuntimeField, isRuntimeMappings } from './types';
describe('Transform: step_define type guards', () => {
it('isRuntimeField()', () => {
expect(isRuntimeField(1)).toBe(false);
expect(isRuntimeField(null)).toBe(false);
expect(isRuntimeField([])).toBe(false);
expect(isRuntimeField({})).toBe(false);
expect(isRuntimeField({ someAttribute: 'someValue' })).toBe(false);
expect(isRuntimeField({ type: 'wrong-type' })).toBe(false);
expect(isRuntimeField({ type: 'keyword', someAttribute: 'some value' })).toBe(false);
expect(isRuntimeField({ type: 'keyword' })).toBe(true);
expect(isRuntimeField({ type: 'keyword', script: 'some script' })).toBe(true);
});
it('isRuntimeMappings()', () => {
expect(isRuntimeMappings(1)).toBe(false);
expect(isRuntimeMappings(null)).toBe(false);
expect(isRuntimeMappings([])).toBe(false);
expect(isRuntimeMappings({})).toBe(false);
expect(isRuntimeMappings({ someAttribute: 'someValue' })).toBe(false);
expect(isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: 'someValue' })).toBe(
false
);
expect(
isRuntimeMappings({
fieldName1: { type: 'keyword' },
fieldName2: { type: 'keyword', someAttribute: 'some value' },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: 1234 },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { someAttribute: 'some value' } },
})
).toBe(false);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { source: 1234 } },
})
).toBe(false);
expect(isRuntimeMappings({ fieldName: { type: 'keyword' } })).toBe(true);
expect(
isRuntimeMappings({ fieldName1: { type: 'keyword' }, fieldName2: { type: 'keyword' } })
).toBe(true);
expect(
isRuntimeMappings({
fieldName1: { type: 'keyword' },
fieldName2: { type: 'keyword', script: 'some script as script' },
})
).toBe(true);
expect(
isRuntimeMappings({
fieldName: { type: 'long', script: { source: 'some script as source' } },
})
).toBe(true);
});
});

View file

@ -24,7 +24,7 @@ import {
} from '../../../../../../../common/types/transform';
import { LatestFunctionConfig } from '../../../../../../../common/api_schemas/transforms';
import { isPopulatedObject } from '../../../../../../../common/utils/object_utils';
import { isPopulatedObject } from '../../../../../../../common/shared_imports';
export interface ErrorMessage {
query: string;
@ -72,30 +72,10 @@ export interface StepDefineExposedState {
isRuntimeMappingsEditorEnabled: boolean;
}
export function isRuntimeField(arg: unknown): arg is RuntimeField {
return (
isPopulatedObject(arg) &&
((Object.keys(arg).length === 1 && arg.hasOwnProperty('type')) ||
(Object.keys(arg).length === 2 &&
arg.hasOwnProperty('type') &&
arg.hasOwnProperty('script') &&
(typeof arg.script === 'string' ||
(isPopulatedObject(arg.script) &&
Object.keys(arg.script).length === 1 &&
arg.script.hasOwnProperty('source') &&
typeof arg.script.source === 'string')))) &&
RUNTIME_FIELD_TYPES.includes(arg.type as RuntimeType)
);
}
export function isRuntimeMappings(arg: unknown): arg is RuntimeMappings {
return isPopulatedObject(arg) && Object.values(arg).every((d) => isRuntimeField(d));
}
export function isPivotPartialRequest(arg: unknown): arg is { pivot: PivotConfigDefinition } {
return isPopulatedObject(arg) && arg.hasOwnProperty('pivot');
return isPopulatedObject(arg, ['pivot']);
}
export function isLatestPartialRequest(arg: unknown): arg is { latest: LatestFunctionConfig } {
return isPopulatedObject(arg) && arg.hasOwnProperty('latest');
return isPopulatedObject(arg, ['latest']);
}

View file

@ -15,7 +15,7 @@ export {
UseIndexDataReturnType,
EsSorting,
RenderCellValue,
HITS_TOTAL_RELATION,
ES_CLIENT_TOTAL_HITS_RELATION,
} from '../../ml/public';
import { XJson } from '../../../../src/plugins/es_ui_shared/public';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { isPopulatedObject } from '../../../common/utils/object_utils';
import { isPopulatedObject } from '../../../common/shared_imports';
import { RouteDependencies } from '../../types';
@ -24,10 +24,7 @@ export const isNodes = (arg: unknown): arg is Nodes => {
return (
isPopulatedObject(arg) &&
Object.values(arg).every(
(node) =>
isPopulatedObject(node) &&
{}.hasOwnProperty.call(node, NODE_ROLES) &&
Array.isArray(node.roles)
(node) => isPopulatedObject(node, [NODE_ROLES]) && Array.isArray(node.roles)
)
);
};