Skip to content

Commit

Permalink
Merge branch 'master' into actions/license-allowlist
Browse files Browse the repository at this point in the history
* master:
  Added `defaultActionMessage` to index threshold alert UI type definition (elastic#80936)
  [ILM] Migrate Delete phase and name field to Form Lib (elastic#82834)
  skip flaky suite (elastic#57426)
  [Alerting] adds an Run When field in the alert flyout to assign the action to an Action Group (elastic#82472)
  [APM] Expose APM event client as part of plugin contract (elastic#82724)
  [Logs UI] Fix errors during navigation (elastic#78319)
  Enable send to background in TSVB (elastic#82835)
  SavedObjects search_dsl: add match_phrase_prefix clauses when using prefix search (elastic#82693)
  [Ingest Manager] Unify install* under installPackage (elastic#82916)
  • Loading branch information
gmmorris committed Nov 9, 2020
2 parents 26faa76 + c78cf35 commit c2eaff4
Show file tree
Hide file tree
Showing 92 changed files with 2,737 additions and 2,347 deletions.
473 changes: 311 additions & 162 deletions src/core/server/saved_objects/service/lib/search_dsl/query_params.test.ts

Large diffs are not rendered by default.

189 changes: 148 additions & 41 deletions src/core/server/saved_objects/service/lib/search_dsl/query_params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,24 @@
import { esKuery } from '../../../es_query';
type KueryNode = any;

import { getRootPropertiesObjects, IndexMapping } from '../../../mappings';
import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry';
import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils';

/**
* Gets the types based on the type. Uses mappings to support
* null type (all types), a single type string or an array
*/
function getTypes(mappings: IndexMapping, type?: string | string[]) {
function getTypes(registry: ISavedObjectTypeRegistry, type?: string | string[]) {
if (!type) {
return Object.keys(getRootPropertiesObjects(mappings));
return registry.getAllTypes().map((registeredType) => registeredType.name);
}

if (Array.isArray(type)) {
return type;
}

return [type];
return Array.isArray(type) ? type : [type];
}

/**
* Get the field params based on the types, searchFields, and rootSearchFields
*/
function getFieldsForTypes(
function getSimpleQueryStringTypeFields(
types: string[],
searchFields: string[] = [],
rootSearchFields: string[] = []
Expand Down Expand Up @@ -130,7 +124,6 @@ export interface HasReferenceQueryParams {
export type SearchOperator = 'AND' | 'OR';

interface QueryParams {
mappings: IndexMapping;
registry: ISavedObjectTypeRegistry;
namespaces?: string[];
type?: string | string[];
Expand Down Expand Up @@ -188,11 +181,26 @@ export function getClauseForReference(reference: HasReferenceQueryParams) {
};
}

// A de-duplicated set of namespaces makes for a more efficient query.
//
// Additionally, we treat the `*` namespace as the `default` namespace.
// In the Default Distribution, the `*` is automatically expanded to include all available namespaces.
// However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*`
// to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`,
// since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place
// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard.
// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716
const normalizeNamespaces = (namespacesToNormalize?: string[]) =>
namespacesToNormalize
? Array.from(
new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x)))
)
: undefined;

/**
* Get the "query" related keys for the search body
*/
export function getQueryParams({
mappings,
registry,
namespaces,
type,
Expand All @@ -206,36 +214,18 @@ export function getQueryParams({
kueryNode,
}: QueryParams) {
const types = getTypes(
mappings,
registry,
typeToNamespacesMap ? Array.from(typeToNamespacesMap.keys()) : type
);

if (hasReference && !Array.isArray(hasReference)) {
hasReference = [hasReference];
}

// A de-duplicated set of namespaces makes for a more effecient query.
//
// Additonally, we treat the `*` namespace as the `default` namespace.
// In the Default Distribution, the `*` is automatically expanded to include all available namespaces.
// However, the OSS distribution (and certain configurations of the Default Distribution) can allow the `*`
// to pass through to the SO Repository, and eventually to this module. When this happens, we translate to `default`,
// since that is consistent with how a single-namespace search behaves in the OSS distribution. Leaving the wildcard in place
// would result in no results being returned, as the wildcard is treated as a literal, and not _actually_ as a wildcard.
// We had a good discussion around the tradeoffs here: https://github.com/elastic/kibana/pull/67644#discussion_r441055716
const normalizeNamespaces = (namespacesToNormalize?: string[]) =>
namespacesToNormalize
? Array.from(
new Set(namespacesToNormalize.map((x) => (x === '*' ? DEFAULT_NAMESPACE_STRING : x)))
)
: undefined;

const bool: any = {
filter: [
...(kueryNode != null ? [esKuery.toElasticsearchQuery(kueryNode)] : []),
...(hasReference && hasReference.length
? [getReferencesFilter(hasReference, hasReferenceOperator)]
: []),
...(hasReference?.length ? [getReferencesFilter(hasReference, hasReferenceOperator)] : []),
{
bool: {
should: types.map((shouldType) => {
Expand All @@ -251,16 +241,133 @@ export function getQueryParams({
};

if (search) {
bool.must = [
{
simple_query_string: {
query: search,
...getFieldsForTypes(types, searchFields, rootSearchFields),
...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}),
},
},
];
const useMatchPhrasePrefix = shouldUseMatchPhrasePrefix(search);
const simpleQueryStringClause = getSimpleQueryStringClause({
search,
types,
searchFields,
rootSearchFields,
defaultSearchOperator,
});

if (useMatchPhrasePrefix) {
bool.should = [
simpleQueryStringClause,
...getMatchPhrasePrefixClauses({ search, searchFields, types, registry }),
];
bool.minimum_should_match = 1;
} else {
bool.must = [simpleQueryStringClause];
}
}

return { query: { bool } };
}

// we only want to add match_phrase_prefix clauses
// if the search is a prefix search
const shouldUseMatchPhrasePrefix = (search: string): boolean => {
return search.trim().endsWith('*');
};

const getMatchPhrasePrefixClauses = ({
search,
searchFields,
registry,
types,
}: {
search: string;
searchFields?: string[];
types: string[];
registry: ISavedObjectTypeRegistry;
}) => {
// need to remove the prefix search operator
const query = search.replace(/[*]$/, '');
const mppFields = getMatchPhrasePrefixFields({ searchFields, types, registry });
return mppFields.map(({ field, boost }) => {
return {
match_phrase_prefix: {
[field]: {
query,
boost,
},
},
};
});
};

interface FieldWithBoost {
field: string;
boost?: number;
}

const getMatchPhrasePrefixFields = ({
searchFields = [],
types,
registry,
}: {
searchFields?: string[];
types: string[];
registry: ISavedObjectTypeRegistry;
}): FieldWithBoost[] => {
const output: FieldWithBoost[] = [];

searchFields = searchFields.filter((field) => field !== '*');
let fields: string[];
if (searchFields.length === 0) {
fields = types.reduce((typeFields, type) => {
const defaultSearchField = registry.getType(type)?.management?.defaultSearchField;
if (defaultSearchField) {
return [...typeFields, `${type}.${defaultSearchField}`];
}
return typeFields;
}, [] as string[]);
} else {
fields = [];
for (const field of searchFields) {
fields = fields.concat(types.map((type) => `${type}.${field}`));
}
}

fields.forEach((rawField) => {
const [field, rawBoost] = rawField.split('^');
let boost: number = 1;
if (rawBoost) {
try {
boost = parseInt(rawBoost, 10);
} catch (e) {
boost = 1;
}
}
if (isNaN(boost)) {
boost = 1;
}
output.push({
field,
boost,
});
});
return output;
};

const getSimpleQueryStringClause = ({
search,
types,
searchFields,
rootSearchFields,
defaultSearchOperator,
}: {
search: string;
types: string[];
searchFields?: string[];
rootSearchFields?: string[];
defaultSearchOperator?: SearchOperator;
}) => {
return {
simple_query_string: {
query: search,
...getSimpleQueryStringTypeFields(types, searchFields, rootSearchFields),
...(defaultSearchOperator ? { default_operator: defaultSearchOperator } : {}),
},
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ describe('getSearchDsl', () => {
getSearchDsl(mappings, registry, opts);
expect(getQueryParams).toHaveBeenCalledTimes(1);
expect(getQueryParams).toHaveBeenCalledWith({
mappings,
registry,
namespaces: opts.namespaces,
type: opts.type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export function getSearchDsl(

return {
...getQueryParams({
mappings,
registry,
namespaces,
type,
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/vis_type_timeseries/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
*/

import { TypeOf } from '@kbn/config-schema';
import { metricsItems, panel, seriesItems } from './vis_schema';
import { metricsItems, panel, seriesItems, visPayloadSchema } from './vis_schema';

export type SeriesItemsSchema = TypeOf<typeof seriesItems>;
export type MetricsItemsSchema = TypeOf<typeof metricsItems>;
export type PanelSchema = TypeOf<typeof panel>;
export type VisPayload = TypeOf<typeof visPayloadSchema>;
1 change: 1 addition & 0 deletions src/plugins/vis_type_timeseries/common/vis_schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,5 @@ export const visPayloadSchema = schema.object({
min: stringRequired,
max: stringRequired,
}),
sessionId: schema.maybe(schema.string()),
});
4 changes: 3 additions & 1 deletion src/plugins/vis_type_timeseries/public/request_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export const metricsRequestHandler = async ({
const config = getUISettings();
const timezone = getTimezone(config);
const uiStateObj = uiState.get(visParams.type, {});
const parsedTimeRange = getDataStart().query.timefilter.timefilter.calculateBounds(timeRange);
const dataSearch = getDataStart();
const parsedTimeRange = dataSearch.query.timefilter.timefilter.calculateBounds(timeRange);
const scaledDataFormat = config.get('dateFormat:scaled');
const dateFormat = config.get('dateFormat');

Expand All @@ -53,6 +54,7 @@ export const metricsRequestHandler = async ({
panels: [visParams],
state: uiStateObj,
savedObjectId: savedObjectId || 'unsaved',
sessionId: dataSearch.search.session.getSessionId(),
}),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('AbstractSearchStrategy', () => {
beforeEach(() => {
mockedFields = {};
req = {
payload: {},
pre: {
indexPatternsService: {
getFieldsForWildcard: jest.fn().mockReturnValue(mockedFields),
Expand Down Expand Up @@ -60,6 +61,9 @@ describe('AbstractSearchStrategy', () => {

const responses = await abstractSearchStrategy.search(
{
payload: {
sessionId: 1,
},
requestContext: {
search: { search: searchFn },
},
Expand All @@ -76,7 +80,9 @@ describe('AbstractSearchStrategy', () => {
},
indexType: undefined,
},
{}
{
sessionId: 1,
}
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,28 @@ import {
IUiSettingsClient,
SavedObjectsClientContract,
} from 'kibana/server';

import { Framework } from '../../../plugin';
import { IndexPatternsFetcher } from '../../../../../data/server';
import { VisPayload } from '../../../../common/types';

/**
* ReqFacade is a regular KibanaRequest object extended with additional service
* references to ensure backwards compatibility for existing integrations.
*
* This will be replaced by standard KibanaRequest and RequestContext objects in a later version.
*/
export type ReqFacade = FakeRequest & {
export interface ReqFacade<T = unknown> extends FakeRequest {
requestContext: RequestHandlerContext;
framework: Framework;
payload: unknown;
payload: T;
pre: {
indexPatternsService?: IndexPatternsFetcher;
};
getUiSettingsService: () => IUiSettingsClient;
getSavedObjectsClient: () => SavedObjectsClientContract;
getEsShardTimeout: () => Promise<number>;
};
}

export class AbstractSearchStrategy {
public indexType?: string;
Expand All @@ -53,8 +55,10 @@ export class AbstractSearchStrategy {
this.additionalParams = additionalParams;
}

async search(req: ReqFacade, bodies: any[], options = {}) {
async search(req: ReqFacade<VisPayload>, bodies: any[], options = {}) {
const requests: any[] = [];
const { sessionId } = req.payload;

bodies.forEach((body) => {
requests.push(
req.requestContext
Expand All @@ -67,6 +71,7 @@ export class AbstractSearchStrategy {
indexType: this.indexType,
},
{
sessionId,
...options,
}
)
Expand Down
Loading

0 comments on commit c2eaff4

Please sign in to comment.