Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d321e37
Use discriminated union for control type + ES|QL controls
Heenawter Mar 25, 2026
e4844c9
Merge branch 'main' of github.com:elastic/kibana into controls-discri…
Heenawter Mar 27, 2026
07146d5
More type fixes
Heenawter Mar 30, 2026
37553e6
Fix more types
Heenawter Mar 31, 2026
5167302
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Mar 31, 2026
ce5c83a
Merge branch 'controls-discriminated-union_2026-03-25' of github.com:…
Heenawter Mar 31, 2026
89abf9d
Changes from node scripts/lint_ts_projects --fix
kibanamachine Mar 31, 2026
1dbfd14
Changes from node scripts/regenerate_moon_projects.js --update
kibanamachine Mar 31, 2026
042fed0
Fix duplicated exports
Heenawter Mar 31, 2026
038f8c6
Fix test
Heenawter Mar 31, 2026
3ec52ba
Fix more types
Heenawter Mar 31, 2026
084f120
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Mar 31, 2026
7cd460f
Fix missed type
Heenawter Apr 1, 2026
235e1b8
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Apr 1, 2026
360f110
Fix Jest tests
Heenawter Apr 1, 2026
453e857
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Apr 1, 2026
b75e1f1
Do some cleanup
Heenawter Apr 1, 2026
c176f0c
Fix more types
Heenawter Apr 1, 2026
e77322c
A little more cleanup
Heenawter Apr 1, 2026
64aad9f
Change `extends` type
Heenawter Apr 1, 2026
26b378a
Address feedback
Heenawter Apr 1, 2026
377a309
Address more feedback
Heenawter Apr 1, 2026
a902e1f
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Apr 1, 2026
6881cdc
Fix types
Heenawter Apr 2, 2026
3c644eb
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Apr 2, 2026
6cc2f5a
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Apr 2, 2026
59fa709
Address feedback
Heenawter Apr 3, 2026
ca8a593
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Apr 3, 2026
6311027
Fix types
Heenawter Apr 4, 2026
a7a8c3b
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Apr 4, 2026
d6747ba
Merge branch 'main' into controls-discriminated-union_2026-03-25
Heenawter Apr 7, 2026
9143a2f
Changes from node scripts/eslint_all_files --no-cache --fix
kibanamachine Apr 7, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
*/

// Do not change constant values - part of public REST APIs
export const TIME_SLIDER_CONTROL = 'time_slider_control';
export const RANGE_SLIDER_CONTROL = 'range_slider_control';
export const OPTIONS_LIST_CONTROL = 'options_list_control';
export const ESQL_CONTROL = 'esql_control';
export const OPTIONS_LIST_CONTROL = 'options_list_control';
export const RANGE_SLIDER_CONTROL = 'range_slider_control';
export const TIME_SLIDER_CONTROL = 'time_slider_control';

export const DEFAULT_DATA_CONTROL_STATE = {
use_global_filters: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export { controlsGroupSchema } from './src/controls_group_schema';
export { getControlsGroupSchema } from './src/controls_group_schema';

export type {
ControlsGroupState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@
import { schema } from '@kbn/config-schema';
import { DEFAULT_DATA_CONTROL_STATE } from '@kbn/controls-constants';

export const controlSchema = schema.object(
{
title: schema.maybe(
schema.string({ meta: { description: 'A human-readable title for the control' } })
),
},
{ unknowns: 'allow' }
);
export const controlTitleSchema = schema.object({
title: schema.maybe(
schema.string({ meta: { description: 'A human-readable title for the control' } })
),
});

export const dataControlSchema = controlSchema.extends({
export const dataControlSchema = schema.object({
...controlTitleSchema.getPropSchemas(),
data_view_id: schema.string({
meta: { description: 'The ID of the data view that the control is tied to' }, // this will generate a reference
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,67 +43,67 @@ export const pinnedControlSchema = schema.object({
}),
});

export const controlsGroupSchema = schema.arrayOf(
// order will be determined by the array
schema.oneOf([
schema
.allOf(
[
schema.object({ type: schema.literal(OPTIONS_LIST_CONTROL) }),
schema.object({ config: optionsListDSLControlSchema }),
pinnedControlSchema,
],
export const getControlsGroupSchema = () => {
const pinnedControl = pinnedControlSchema.getPropSchemas();
return schema.arrayOf(
/**
* - keep types in alphabetical order for the sake of documentation
* - control order will be determined by the array
*/
schema.discriminatedUnion('type', [
schema.object(
{
type: schema.literal(ESQL_CONTROL),
config: optionsListESQLControlSchema,
...pinnedControl,
},
{
meta: {
title: ESQL_CONTROL,
},
}
),
schema.object(
{
type: schema.literal(OPTIONS_LIST_CONTROL),
config: optionsListDSLControlSchema,
...pinnedControl,
},
{
meta: {
title: OPTIONS_LIST_CONTROL,
},
}
)
.extendsDeep({ unknowns: 'allow' }), // allows for legacy unknowns such as `parentField` and `enhancements`
schema
.allOf(
[
schema.object({ type: schema.literal(RANGE_SLIDER_CONTROL) }),
schema.object({ config: rangeSliderControlSchema }),
pinnedControlSchema,
],
),
schema.object(
{
type: schema.literal(RANGE_SLIDER_CONTROL),
config: rangeSliderControlSchema,
...pinnedControl,
},
{
meta: {
title: RANGE_SLIDER_CONTROL,
},
}
)
.extendsDeep({ unknowns: 'allow' }),
schema
.allOf(
[
schema.object({ type: schema.literal(TIME_SLIDER_CONTROL) }),
schema.object({ config: timeSliderControlSchema }),
pinnedControlSchema,
],
),
schema.object(
{
type: schema.literal(TIME_SLIDER_CONTROL),
config: timeSliderControlSchema,
...pinnedControl,
},
{
meta: {
title: TIME_SLIDER_CONTROL,
},
}
)
.extendsDeep({ unknowns: 'allow' }), // allows for legacy unknowns such as `useGlobalFilters`
schema.allOf(
[
schema.object({ type: schema.literal(ESQL_CONTROL) }),
schema.object({ config: optionsListESQLControlSchema }),
pinnedControlSchema,
],
{
meta: {
title: ESQL_CONTROL,
},
}
), // variable controls do not need `unknowns: 'allow'` because they have no legacy values
]),
{
defaultValue: [],
maxSize: 100,
meta: { description: 'An array of control panels and their state in the control group.' },
}
);
),
]),
{
defaultValue: [],
maxSize: 100,
meta: { description: 'An array of control panels and their state in the control group.' },
}
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
DEFAULT_ESQL_OPTIONS_LIST_STATE,
MAX_OPTIONS_LIST_REQUEST_SIZE,
} from '@kbn/controls-constants';
import { controlSchema, dataControlSchema } from './control_schema';
import { controlTitleSchema, dataControlSchema } from './control_schema';

const SELECTIONS_MAX = 10000;

Expand All @@ -40,48 +40,53 @@ export const optionsListSortSchema = schema.object(

export const optionsListSelectionSchema = schema.oneOf([schema.string(), schema.number()]);

const optionsListControlBaseParameters = {
const optionsListControlBaseParameters = schema.object({
display_settings: schema.maybe(optionsListDisplaySettingsSchema),
};
});

export const optionsListDSLControlSchema = dataControlSchema
.extends(optionsListControlBaseParameters)
.extends({
exclude: schema.boolean({ defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.exclude }),
exists_selected: schema.boolean({
defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.exists_selected,
}),
run_past_timeout: schema.boolean({
defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.run_past_timeout,
}),
search_technique: optionsListSearchTechniqueSchema,
selected_options: schema.arrayOf(optionsListSelectionSchema, {
defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.selected_options,
maxSize: SELECTIONS_MAX,
}),
single_select: schema.boolean({ defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.single_select }),
sort: optionsListSortSchema,
});
export const optionsListDSLControlSchema = schema.object({
...optionsListControlBaseParameters.getPropSchemas(),
...dataControlSchema.getPropSchemas(),
exclude: schema.boolean({ defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.exclude }),
exists_selected: schema.boolean({
defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.exists_selected,
}),
run_past_timeout: schema.boolean({
defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.run_past_timeout,
}),
search_technique: optionsListSearchTechniqueSchema,
selected_options: schema.arrayOf(optionsListSelectionSchema, {
defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.selected_options,
maxSize: SELECTIONS_MAX,
}),
single_select: schema.boolean({ defaultValue: DEFAULT_DSL_OPTIONS_LIST_STATE.single_select }),
sort: optionsListSortSchema,
});

const baseEsqlControl = {
...controlTitleSchema.getPropSchemas(),
...optionsListControlBaseParameters.getPropSchemas(),
selected_options: schema.arrayOf(schema.string(), { maxSize: SELECTIONS_MAX }),
single_select: schema.boolean({ defaultValue: DEFAULT_ESQL_OPTIONS_LIST_STATE.single_select }),
variable_name: schema.string(),
variable_type: schema.oneOf([
schema.literal('fields'),
schema.literal('values'),
schema.literal('functions'),
schema.literal('time_literal'),
schema.literal('multi_values'),
]),
};

export const optionsListESQLControlSchema = controlSchema
.extends(optionsListControlBaseParameters)
.extends({
selected_options: schema.arrayOf(schema.string(), { maxSize: SELECTIONS_MAX }),
single_select: schema.boolean({ defaultValue: DEFAULT_ESQL_OPTIONS_LIST_STATE.single_select }),
variable_name: schema.string(),
variable_type: schema.oneOf([
schema.literal('fields'),
schema.literal('values'),
schema.literal('functions'),
schema.literal('time_literal'),
schema.literal('multi_values'),
]),
export const optionsListESQLControlSchema = schema.discriminatedUnion('control_type', [
schema.object({
...baseEsqlControl,
control_type: schema.literal('STATIC_VALUES'),
available_options: schema.arrayOf(schema.string(), { maxSize: MAX_OPTIONS_LIST_REQUEST_SIZE }),
}),
schema.object({
...baseEsqlControl,
control_type: schema.literal('VALUES_FROM_QUERY'),
esql_query: schema.string(),
control_type: schema.oneOf([
schema.literal('STATIC_VALUES'),
schema.literal('VALUES_FROM_QUERY'),
]),
available_options: schema.maybe(
schema.arrayOf(schema.string(), { maxSize: MAX_OPTIONS_LIST_REQUEST_SIZE })
),
});
}),
]);
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { dataControlSchema } from './control_schema';

export const rangeValueSchema = schema.arrayOf(schema.string(), { minSize: 2, maxSize: 2 });

export const rangeSliderControlSchema = dataControlSchema.extends({
export const rangeSliderControlSchema = schema.object({
...dataControlSchema.getPropSchemas(),
value: schema.maybe(rangeValueSchema),
step: schema.number({ defaultValue: DEFAULT_RANGE_SLIDER_STATE.step, min: 0 }),
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
import type React from 'react';

import type { TypeOf } from '@kbn/config-schema';
import type { controlSchema, dataControlSchema } from './control_schema';
import type { controlTitleSchema, dataControlSchema } from './control_schema';
import type {
controlsGroupSchema,
getControlsGroupSchema,
controlWidthSchema,
pinnedControlSchema,
} from './controls_group_schema';
Expand All @@ -27,14 +27,15 @@ import type {
import type { rangeSliderControlSchema, rangeValueSchema } from './range_slider_schema';
import type { timeSliderControlSchema } from './time_slider_schema';

export type ControlsGroupState = TypeOf<typeof controlsGroupSchema>;
export type ControlsGroupState = TypeOf<ReturnType<typeof getControlsGroupSchema>>;
export type PinnedControlState = ControlsGroupState[number];
export type PinnedControlLayoutState = TypeOf<typeof pinnedControlSchema> & {
order: number;
type: string;
};

export type ControlWidth = TypeOf<typeof controlWidthSchema>;
export type ControlState = TypeOf<typeof controlSchema>;
export type ControlState = TypeOf<typeof controlTitleSchema>;

export type DataControlState = TypeOf<typeof dataControlSchema>;

Expand Down
4 changes: 4 additions & 0 deletions src/platform/packages/shared/kbn-esql-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ export {
type ESQLControlVariable,
type PublishesESQLVariable,
type PublishesESQLVariables,
type StaticESQLControl,
type QueryESQLControl,
apiPublishesESQLVariable,
apiPublishesESQLVariables,
controlHasVariableName,
isStaticESQLControl,
isQueryESQLControl,
} from './src/variables_types';

export {
Expand Down
1 change: 1 addition & 0 deletions src/platform/packages/shared/kbn-esql-types/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependsOn:
- '@kbn/licensing-types'
- '@kbn/core-pricing-common'
- '@kbn/projects-solutions-groups'
- '@kbn/controls-schemas'
tags:
- shared-common
- package
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import type { BehaviorSubject } from 'rxjs';
import type { OptionsListESQLControlState } from '@kbn/controls-schemas';

type PublishingSubject<T extends unknown = unknown> = Omit<BehaviorSubject<T>, 'next'>;

Expand All @@ -34,6 +35,28 @@ export enum EsqlControlType {
VALUES_FROM_QUERY = 'VALUES_FROM_QUERY',
}

export type StaticESQLControl = Extract<
OptionsListESQLControlState,
{ control_type: 'STATIC_VALUES' }
>;
export const isStaticESQLControl = (control?: object): control is StaticESQLControl => {
return Boolean(
control && 'control_type' in control && control.control_type === EsqlControlType.STATIC_VALUES
);
};

export type QueryESQLControl = Extract<
OptionsListESQLControlState,
{ control_type: 'VALUES_FROM_QUERY' }
>;
export const isQueryESQLControl = (control?: object): control is QueryESQLControl => {
return Boolean(
control &&
'control_type' in control &&
control.control_type === EsqlControlType.VALUES_FROM_QUERY
);
};

export interface ESQLControlVariable {
key: string;
value: string | number | (string | number)[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@kbn/licensing-types",
"@kbn/core-pricing-common",
"@kbn/projects-solutions-groups",
"@kbn/controls-schemas",
],
"exclude": ["target/**/*"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const createControlState = (variableName: string): OptionsListESQLControlState =
selected_options: ['option-1'],
variable_name: variableName,
variable_type: ESQLVariableType.VALUES,
esql_query: 'FROM index',
control_type: EsqlControlType.STATIC_VALUES,
single_select: true,
available_options: ['option-1', 'option-2'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,3 @@ export type {
OptionsListSuccessResponse,
OptionsListSuggestions,
} from './types';
export { isOptionsListESQLControlState } from './types';
Loading
Loading