Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -192,6 +192,14 @@ export const optionsListESQLControlSchema = schema.discriminatedUnion('control_t
description: 'A fixed list of option strings displayed in the control.',
},
}),
source_esql_query: schema.maybe(
schema.string({
meta: {
description:
'The ES|QL query the control was created from. Not evaluated at runtime; persisted only so the available identifiers list (e.g. fields) can be repopulated when the control is edited.',
},
})
),
},
Comment on lines +195 to 203
Copy link
Copy Markdown
Contributor

@Heenawter Heenawter May 8, 2026

Choose a reason for hiding this comment

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

I'm not sure this is the right approach. By adding it to the user-facing API schema like this, it is up to the user who makes the control to copy paste the query from some other ES|QL query into this and then we somehow have to keep these values up-to-date... Not good!

Instead, I wonder if we could use the related panels logic once that merges in order to determine which query to use at runtime? Especially with my suggested approach - if we move the related panels logic into the ES|QL control like I suggested, we could pretty easily know which ES|QL query is using the field control variable at runtime, which we could then use to populate the suggested options dropdown. What do you think @stratoula?

cc @Zacqary here, too

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah I see what you say and I hear it. I am fine with fixing it the way you suggest Hannah. I am going to close this PR but can you please handle it? It is a bad regression and I dont like it 🙏

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Created an issue to track: #268695

{
meta: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import type {
OptionsListSelection,
} from '@kbn/controls-schemas';
import type { TimeRange } from '@kbn/es-query';
import type { ESQLControlVariable, QueryESQLControl } from '@kbn/esql-types';
import type { ESQLControlVariable, QueryESQLControl, StaticESQLControl } from '@kbn/esql-types';
import {
EsqlControlType,
ESQLVariableType,
Expand Down Expand Up @@ -66,7 +66,7 @@ type ComparatorsReturnType<ControlType = ESQLOptionsListRuntimeState['control_ty
| 'available_options'
> &
(ControlType extends EsqlControlType.STATIC_VALUES
? {}
? Pick<StaticESQLControl, 'source_esql_query'>
: Pick<QueryESQLControl, 'esql_query'>)
>;

Expand All @@ -83,7 +83,7 @@ export const getSelectionComparators = <Type = ESQLOptionsListRuntimeState['cont
available_options: 'deepEquality',
...(controlType === EsqlControlType.VALUES_FROM_QUERY
? { esql_query: 'referenceEquality' }
: {}),
: { source_esql_query: 'referenceEquality' }),
} as ComparatorsReturnType<Type>;
};

Expand All @@ -109,6 +109,9 @@ export function initializeESQLControlManager(
initialState.variable_type as ESQLVariableType
);
const esqlQuery$ = new BehaviorSubject<string>(isEsqlQueryControl ? initialState.esql_query : '');
const sourceEsqlQuery$ = new BehaviorSubject<string | undefined>(
isStaticControl ? initialState.source_esql_query : undefined
);
let valuesColumnType: string | undefined;
const totalCardinality$ = new BehaviorSubject<number>(
isStaticControl ? initialState.available_options.length : 0
Expand Down Expand Up @@ -317,7 +320,8 @@ export function initializeESQLControlManager(
variableName$,
singleSelect$,
variableType$,
esqlQuery$
esqlQuery$,
sourceEsqlQuery$
).pipe(map(() => undefined)),
reinitializeState: (lastSaved?: ESQLOptionsListRuntimeState) => {
setSelectedOptions(lastSaved?.selected_options ?? []);
Expand All @@ -331,6 +335,9 @@ export function initializeESQLControlManager(
hasInitialFetch = false;
availableOptions$.next(lastSaved?.available_options ?? []);
esqlQuery$.next(isQueryESQLControl(lastSaved) ? lastSaved.esql_query : '');
sourceEsqlQuery$.next(
isStaticESQLControl(lastSaved) ? lastSaved.source_esql_query : undefined
);
},
getLatestState: (): OptionsListESQLControlState => {
return {
Expand All @@ -343,6 +350,9 @@ export function initializeESQLControlManager(
? {
control_type: EsqlControlType.STATIC_VALUES,
available_options: availableOptions$.getValue(),
...(sourceEsqlQuery$.getValue() !== undefined && {
source_esql_query: sourceEsqlQuery$.getValue(),
}),
}
: { control_type: EsqlControlType.VALUES_FROM_QUERY, esql_query: esqlQuery$.getValue() }),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ jest.mock('./utils/get_esql_single_column_values', () => {
};
});

const mockExecuteTriggerActions = jest.fn();
jest.mock('../../services/kibana_services', () => ({
...jest.requireActual('../../services/kibana_services'),
uiActionsService: {
executeTriggerActions: (...args: unknown[]) => mockExecuteTriggerActions(...args),
},
}));

describe('ESQLControlApi', () => {
beforeEach(() => {
jest.resetAllMocks();
Expand Down Expand Up @@ -100,6 +108,37 @@ describe('ESQLControlApi', () => {
});
});

describe('editing a STATIC fields control', () => {
test('onEdit forwards the persisted source_esql_query as queryString', async () => {
const sourceQuery = 'FROM logs | KEEP ';
const initialState: OptionsListESQLControlState = {
...DEFAULT_ESQL_OPTIONS_LIST_STATE,
selected_options: ['column1'],
available_options: ['column1'],
variable_name: 'myField',
variable_type: ESQLVariableType.FIELDS,
control_type: EsqlControlType.STATIC_VALUES,
source_esql_query: sourceQuery,
};
const { api } = await factory.buildEmbeddable({
initializeDrilldownsManager: jest.fn(),
initialState,
finalizeApi,
uuid,
parentApi: dashboardApi,
});
await api.onEdit();
expect(mockExecuteTriggerActions).toHaveBeenCalledWith(
'ESQL_CONTROL_TRIGGER',
expect.objectContaining({
queryString: sourceQuery,
controlType: EsqlControlType.STATIC_VALUES,
variableType: ESQLVariableType.FIELDS,
})
);
});
});

describe('values from query', () => {
test('should update on load and fetch', async () => {
const initialState: OptionsListESQLControlState = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ export const getESQLControlFactory = <
};
try {
await uiActionsService.executeTriggerActions('ESQL_CONTROL_TRIGGER', {
queryString: isStaticESQLControl(nextState) ? '' : nextState.esql_query,
queryString: isStaticESQLControl(nextState)
? nextState.source_esql_query ?? ''
: nextState.esql_query,
variableType: nextState.variable_type,
controlType: nextState.control_type,
esqlVariables: variablesInParent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ describe('IdentifierControlForm', () => {
selected_options: ['column2'],
variable_name: 'myField',
variable_type: ESQLVariableType.FIELDS,
esql_query: 'FROM foo | STATS BY',
source_esql_query: 'FROM foo | STATS BY',
control_type: EsqlControlType.STATIC_VALUES,
} as OptionsListESQLControlState;
const { findByTestId } = render(
Expand Down Expand Up @@ -186,7 +186,7 @@ describe('IdentifierControlForm', () => {
selected_options: ['column2'],
variable_name: 'myField',
variable_type: ESQLVariableType.FIELDS,
esql_query: 'FROM foo | STATS BY',
source_esql_query: 'FROM foo | STATS BY',
control_type: EsqlControlType.STATIC_VALUES,
} as OptionsListESQLControlState;
const onEditControlSpy = jest.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ export function IdentifierControlForm({
variable_name: variableNameWithoutQuestionmark,
variable_type: variableType,
control_type: EsqlControlType.STATIC_VALUES as StaticESQLControl['control_type'],
// Persist the originating query for FIELDS controls so the available
// identifiers list can be repopulated when the control is edited.
...(variableType === ESQLVariableType.FIELDS && queryString
? { source_esql_query: queryString }
: {}),
};
if (!isEqual(state, initialState)) {
setControlState(state);
Expand Down
Loading