Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d264710
Define schemas for simple filter interface
nickpeihl Oct 29, 2025
5989e98
Add descriptions to schemas
nickpeihl Oct 29, 2025
f900770
Remove id and rename indexPattern to dataViewId
nickpeihl Oct 30, 2025
5037bbe
Merge remote-tracking branch 'upstream/main' into simple-filters-schema
nickpeihl Oct 31, 2025
fb0f138
Changes from node scripts/lint_ts_projects --fix
kibanamachine Oct 31, 2025
b49449a
Fix filter value schema arrays to be homogeneous
nickpeihl Nov 5, 2025
ab20892
Fix casing inconsistency
nickpeihl Nov 5, 2025
48689fc
Naming consistency
nickpeihl Nov 5, 2025
f13c024
Stricter discriminated filter conditions
nickpeihl Nov 5, 2025
539a159
Document controlledBy field
nickpeihl Nov 5, 2025
565848a
Remove duplicate stored filter schema
nickpeihl Nov 5, 2025
7f85963
Changes from node scripts/lint_ts_projects --fix
kibanamachine Nov 5, 2025
51c30b1
Rename path
nickpeihl Nov 5, 2025
a2979a9
Merge remote-tracking branch 'refs/remotes/origin/simple-filters-sche…
nickpeihl Nov 5, 2025
883b3b6
Rename stored filter schema to "storedFilterSchema" and "simpleFilter…
nickpeihl Nov 6, 2025
3cbf28b
Add constants package to avoid circular references
nickpeihl Nov 7, 2025
00bd945
Add isMultiIindex, filterType, and BWC legacy properties
nickpeihl Nov 7, 2025
8aa68b4
Changes from node scripts/generate codeowners
kibanamachine Nov 7, 2025
e72bb46
Merge branch 'main' into simple-filters-schema
nickpeihl Nov 7, 2025
6b5e025
Rename AsCodeFilter types
nickpeihl Nov 7, 2025
f94f926
Merge remote-tracking branch 'origin/simple-filters-schema' into simp…
nickpeihl Nov 7, 2025
d1c1ea2
As "asCode" prefix to exported schemas
nickpeihl Nov 7, 2025
27870ca
Don't export StoredFilter types
nickpeihl Nov 7, 2025
36da619
Use schema extend instead of spread
nickpeihl Nov 7, 2025
7702432
Remove unnecessary nested query object from DSL schema
nickpeihl Nov 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ src/platform/packages/shared/kbn-es @elastic/kibana-operations
src/platform/packages/shared/kbn-es-archiver @elastic/appex-qa
src/platform/packages/shared/kbn-es-errors @elastic/kibana-core
src/platform/packages/shared/kbn-es-query @elastic/kibana-data-discovery
src/platform/packages/shared/kbn-es-query-constants @elastic/kibana-data-discovery
src/platform/packages/shared/kbn-es-query-server @elastic/kibana-data-discovery
src/platform/packages/shared/kbn-es-types @elastic/kibana-core @elastic/obs-knowledge-team
src/platform/packages/shared/kbn-esql-ast @elastic/kibana-esql
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,7 @@
"@kbn/error-boundary-example-plugin": "link:examples/error_boundary",
"@kbn/es-errors": "link:src/platform/packages/shared/kbn-es-errors",
"@kbn/es-query": "link:src/platform/packages/shared/kbn-es-query",
"@kbn/es-query-constants": "link:src/platform/packages/shared/kbn-es-query-constants",
"@kbn/es-query-server": "link:src/platform/packages/shared/kbn-es-query-server",
"@kbn/es-types": "link:src/platform/packages/shared/kbn-es-types",
"@kbn/es-ui-shared-plugin": "link:src/platform/plugins/shared/es_ui_shared",
Expand Down
3 changes: 3 additions & 0 deletions src/platform/packages/shared/kbn-es-query-constants/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @kbn/es-query-constants

Constants that can be used by both `@kbn/es-query` and `@kbn/es-query-server` packages to avoid circular references.
10 changes: 10 additions & 0 deletions src/platform/packages/shared/kbn-es-query-constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export { FilterStateStore } from './src/constants';
14 changes: 14 additions & 0 deletions src/platform/packages/shared/kbn-es-query-constants/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

module.exports = {
preset: '@kbn/test/jest_node',
rootDir: '../../../../..',
roots: ['<rootDir>/src/platform/packages/shared/kbn-es-query-constants'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "shared-common",
"id": "@kbn/es-query-constants",
"owner": "@elastic/kibana-data-discovery",
"group": "platform",
"visibility": "shared"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/es-query-constants",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0",
"sideEffects": false

Probably worth disabling side effects for this package even if it's tiny atm.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

/**
Filter,
* An enum to denote whether a filter is specific to an application's context or whether it should be applied globally.
* @public
*/
export enum FilterStateStore {
APP_STATE = 'appState',
GLOBAL_STATE = 'globalState',
}
17 changes: 17 additions & 0 deletions src/platform/packages/shared/kbn-es-query-constants/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node"
]
},
"include": [
"**/*.ts",
],
"exclude": [
"target/**/*"
],
"kbn_references": []
}
6 changes: 3 additions & 3 deletions src/platform/packages/shared/kbn-es-query-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

export { timeRangeSchema } from './src/time_range';
export { querySchema } from './src/query';
export { filterSchema } from './src/filter';
export { asCodeFilterSchema } from './src/filter';
export { storedFilterSchema } from './src/stored_filter';

export type {
TimeRange,
AbsoluteTimeRange,
RelativeTimeRange,
Filter,
FilterMeta,
AggregateQuery,
Query,
AsCodeFilter,
} from './src/types';
257 changes: 216 additions & 41 deletions src/platform/packages/shared/kbn-es-query-server/src/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,231 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

/**
* Validation Schemas for Simplified Filter Interface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Validation Schemas for "As code" Filter Interface

*
* These schemas are used for server validation of API requests and responses
* in * as Code APIs.
*/

import { schema } from '@kbn/config-schema';
import { FilterStateStore } from '@kbn/es-query';

const filterStateStoreSchema = schema.oneOf(
[schema.literal(FilterStateStore.APP_STATE), schema.literal(FilterStateStore.GLOBAL_STATE)],
{
meta: {
description:
"Denote whether a filter is specific to an application's context (e.g. 'appState') or whether it should be applied globally (e.g. 'globalState').",
},
}
// ====================================================================
// CORE FILTER OPERATOR AND VALUE SCHEMAS
// ====================================================================

/**
* Schema for range values used in numeric and date filters
*/
const rangeSchema = schema.object({
gte: schema.maybe(
schema.oneOf([schema.number(), schema.string()], {
meta: { description: 'Greater than or equal to' },
})
),
lte: schema.maybe(
schema.oneOf([schema.number(), schema.string()], {
meta: { description: 'Less than or equal to' },
})
),
gt: schema.maybe(
schema.oneOf([schema.number(), schema.string()], {
meta: { description: 'Greater than' },
})
),
lt: schema.maybe(
schema.oneOf([schema.number(), schema.string()], {
meta: { description: 'Less than' },
})
),
});

// ====================================================================
// BASE PROPERTIES (SHARED BY ALL FILTERS)
// ====================================================================

/**
* Base properties shared by all simplified filters
*/
const basePropertiesSchema = schema.object({
pinned: schema.maybe(
schema.boolean({
meta: { description: 'Whether the filter is pinned' },
})
),
disabled: schema.maybe(
schema.boolean({
meta: { description: 'Whether the filter is disabled' },
})
),
controlledBy: schema.maybe(
schema.string({
meta: {
description: 'Optional identifier for the component/plugin managing this filter',
},
})
),
dataViewId: schema.maybe(
schema.string({
meta: { description: 'Data view ID that this filter applies to' },
})
),
negate: schema.maybe(
schema.boolean({
meta: { description: 'Whether to negate the filter condition' },
})
),
label: schema.maybe(
schema.string({
meta: { description: 'Human-readable label for the filter' },
})
),
isMultiIndex: schema.maybe(
schema.boolean({
meta: { description: 'Whether this filter can be applied to multiple indices' },
})
),
filterType: schema.maybe(
schema.string({
meta: {
description:
'Filter type from legacy filters (e.g., "spatial_filter", "query_string") for backwards compatibility',
},
})
),
key: schema.maybe(
schema.string({
meta: {
description: 'Field name metadata from legacy filters for backwards compatibility',
},
})
),
value: schema.maybe(
schema.string({
meta: {
description: 'Value metadata from legacy filters for backwards compatibility',
},
})
),
Comment on lines +94 to +115
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these all related to the spatial filter?

});

// ====================================================================
// FILTER CONDITION SCHEMAS
// ====================================================================

/**
* Common field property for all filter conditions
*/
const conditionFieldSchema = schema.object({
field: schema.string({ meta: { description: 'Field the filter applies to' } }),
});

/**
* Schema for 'is' and 'is_not' operators with single value
*/
const singleConditionSchema = conditionFieldSchema.extends({
operator: schema.oneOf([schema.literal('is'), schema.literal('is_not')], {
meta: { description: 'Single value comparison operators' },
}),
value: schema.oneOf([schema.string(), schema.number(), schema.boolean()], {
meta: { description: 'Single value for comparison' },
}),
});

/**
* Schema for 'is_one_of' and 'is_not_one_of' operators with array values
*/
const oneOfConditionSchema = conditionFieldSchema.extends({
operator: schema.oneOf([schema.literal('is_one_of'), schema.literal('is_not_one_of')], {
meta: { description: 'Array value comparison operators' },
}),
value: schema.oneOf(
[
schema.arrayOf(schema.string()),
schema.arrayOf(schema.number()),
schema.arrayOf(schema.boolean()),
],
{ meta: { description: 'Homogeneous array of values' } }
),
});

/**
* Schema for 'range' operator with range value
*/
const rangeConditionSchema = conditionFieldSchema.extends({
operator: schema.literal('range'),
value: rangeSchema,
});

/**
* Schema for 'exists' and 'not_exists' operators without value
*/
const existsConditionSchema = conditionFieldSchema.extends({
operator: schema.oneOf([schema.literal('exists'), schema.literal('not_exists')], {
meta: { description: 'Field existence check operators' },
}),
// value is intentionally omitted for exists/not_exists operators
});

/**
* Discriminated union schema for simple filter conditions with proper operator/value type combinations
*/
const conditionSchema = schema.oneOf(
[singleConditionSchema, oneOfConditionSchema, rangeConditionSchema, existsConditionSchema],
{ meta: { description: 'A filter condition with strict operator/value type matching' } }
);

export const filterMetaSchema = schema.object(
// ====================================================================
// FILTER DISCRIMINATED UNION SCHEMA
// ====================================================================

/**
* Schema for condition filters
*/
export const asCodeConditionFilterSchema = basePropertiesSchema.extends(
{
alias: schema.maybe(schema.nullable(schema.string())),
disabled: schema.maybe(schema.boolean()),
negate: schema.maybe(schema.boolean()),
controlledBy: schema.maybe(
schema.string({ meta: { description: 'Identifies the owner the filter.' } })
),
group: schema.maybe(
schema.string({ meta: { description: 'The group to which this filter belongs.' } })
),
// field is missing from the Filter type, but is stored in SerializedSearchSourceFields
// see the todo in src/platform/packages/shared/kbn-es-query/src/filters/helpers/update_filter.ts
field: schema.maybe(schema.string()),
index: schema.maybe(schema.string()),
isMultiIndex: schema.maybe(schema.boolean()),
type: schema.maybe(schema.string()),
key: schema.maybe(schema.string()),
// We could consider creating FilterMetaParams as a schema to match the concrete Filter type.
// However, this is difficult because FilterMetaParams can be a `filterSchema` which is defined below.
// This would require a more complex schema definition that can handle recursive types.
// For now, we use `schema.any()` to allow flexibility in the params field.
params: schema.maybe(schema.any()),
value: schema.maybe(schema.string()),
condition: conditionSchema,
},
{ unknowns: 'allow' }
{ meta: { description: 'Condition filter' } }
);

export const filterSchema = schema.object(
/**
* Schema for logical filter groups with recursive structure
* Uses lazy schema to handle recursive references
*/
const GROUP_FILTER_ID = '@kbn/es-query-server_groupFilter'; // package prefix for global uniqueness in OAS specs
export const asCodeGroupFilterSchema = basePropertiesSchema.extends(
{
meta: filterMetaSchema,
query: schema.maybe(schema.recordOf(schema.string(), schema.any())),
$state: schema.maybe(
schema.object({
store: filterStateStoreSchema,
})
group: schema.object(
{
type: schema.oneOf([schema.literal('and'), schema.literal('or')]),
conditions: schema.arrayOf(
schema.oneOf([
conditionSchema,
schema.lazy(GROUP_FILTER_ID), // Recursive reference for nested groups
])
),
},
{ meta: { description: 'Condition or nested group filter', id: GROUP_FILTER_ID } }
),
},
{ meta: { id: 'kbn-es-query-server-filterSchema' } }
{ meta: { description: 'Grouped condition filter' } }
);

/**
* Schema for DSL filters
*/
export const asCodeDSLFilterSchema = basePropertiesSchema.extends({
dsl: schema.recordOf(schema.string(), schema.any(), {
meta: { description: 'Elasticsearch Query DSL object' },
}),
});

/**
* Main discriminated union schema for Filter
* Ensures exactly one of: condition, group, or dsl is present
*/
export const asCodeFilterSchema = schema.oneOf(
[asCodeConditionFilterSchema, asCodeGroupFilterSchema, asCodeDSLFilterSchema],
{ meta: { description: 'A filter which can be a condition, group, or raw DSL' } }
);
Loading