[ES|QL] Adds license knowledge to the editor (#217796)

## Summary

Part of https://github.com/elastic/kibana/issues/216791

- Suggests remote indices if ES license plugin enables it 

<img width="580" alt="image"
src="https://github.com/user-attachments/assets/a237f646-286e-489c-b41b-a1ce7d82440a"
/>



- Doesn't suggest remote indices for basic license
<img width="292" alt="image"
src="https://github.com/user-attachments/assets/2bc9e512-91ab-4b20-94ce-d1931732567e"
/>


### Checklist

- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Stratoula Kalafateli 2025-04-14 12:52:10 +02:00 committed by GitHub
parent a1a0d3462d
commit 708917df6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 76 additions and 8 deletions

View file

@ -26,6 +26,7 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { AggregateQuery, TimeRange } from '@kbn/es-query';
import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import type { ILicense } from '@kbn/licensing-plugin/public';
import { ESQLLang, ESQL_LANG_ID, monaco, type ESQLCallbacks } from '@kbn/monaco';
import memoize from 'lodash/memoize';
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
@ -153,6 +154,7 @@ export const ESQLEditor = memo(function ESQLEditor({
const [isCodeEditorExpandedFocused, setIsCodeEditorExpandedFocused] = useState(false);
const [isQueryLoading, setIsQueryLoading] = useState(true);
const [abortController, setAbortController] = useState(new AbortController());
const [license, setLicense] = useState<ILicense | undefined>(undefined);
// contains both client side validation and server messages
const [editorMessages, setEditorMessages] = useState<{
@ -435,7 +437,7 @@ export const ESQLEditor = memo(function ESQLEditor({
}, []);
const { cache: dataSourcesCache, memoizedSources } = useMemo(() => {
const fn = memoize((...args: [DataViewsPublicPluginStart, CoreStart]) => ({
const fn = memoize((...args: [DataViewsPublicPluginStart, CoreStart, boolean]) => ({
timestamp: Date.now(),
result: getESQLSources(...args),
}));
@ -447,7 +449,9 @@ export const ESQLEditor = memo(function ESQLEditor({
const callbacks: ESQLCallbacks = {
getSources: async () => {
clearCacheWhenOld(dataSourcesCache, fixedQuery);
const sources = await memoizedSources(dataViews, core).result;
const ccrFeature = license?.getFeature('ccr');
const areRemoteIndicesAvailable = ccrFeature?.isAvailable ?? false;
const sources = await memoizedSources(dataViews, core, areRemoteIndicesAvailable).result;
return sources;
},
getColumnsFor: async ({ query: queryToExecute }: { query?: string } | undefined = {}) => {
@ -509,6 +513,7 @@ export const ESQLEditor = memo(function ESQLEditor({
return callbacks;
}, [
fieldsMetadata,
license,
kibana.services?.esql?.getJoinIndicesAutocomplete,
dataSourcesCache,
fixedQuery,
@ -585,6 +590,20 @@ export const ESQLEditor = memo(function ESQLEditor({
}
}, [isLoading, isQueryLoading, parseMessages, code]);
useEffect(() => {
async function fetchLicense() {
try {
const ls = await kibana.services?.esql?.getLicense();
if (!isEqual(license, ls)) {
setLicense(ls);
}
} catch (error) {
// failed to fetch
}
}
fetchLicense();
}, [kibana.services?.esql, license]);
const queryValidation = useCallback(
async ({ active }: { active: boolean }) => {
if (!editorModel.current || editorModel.current.isDisposed()) return;

View file

@ -313,8 +313,38 @@ describe('helpers', function () {
},
]),
};
const indices = await getRemoteIndicesList(updatedDataViewsMock);
const indices = await getRemoteIndicesList(updatedDataViewsMock, true);
expect(indices).toStrictEqual([{ name: 'remote:logs', hidden: false, type: 'Index' }]);
});
it('should not suggest ccs indices if not allowed', async function () {
const dataViewsMock = dataViewPluginMocks.createStartContract();
const updatedDataViewsMock = {
...dataViewsMock,
getIndices: jest.fn().mockResolvedValue([
{
name: 'remote: alias1',
item: {
indices: ['index1'],
},
},
{
name: 'remote:.system1',
item: {
name: 'system',
},
},
{
name: 'remote:logs',
item: {
name: 'logs',
timestamp_field: '@timestamp',
},
},
]),
};
const indices = await getRemoteIndicesList(updatedDataViewsMock, false);
expect(indices).toStrictEqual([]);
});
});
});

View file

@ -196,7 +196,13 @@ export const getIndicesList = async (dataViews: DataViewsPublicPluginStart) => {
});
};
export const getRemoteIndicesList = async (dataViews: DataViewsPublicPluginStart) => {
export const getRemoteIndicesList = async (
dataViews: DataViewsPublicPluginStart,
areRemoteIndicesAvailable: boolean
) => {
if (!areRemoteIndicesAvailable) {
return [];
}
const indices = await dataViews.getIndices({
showAllIndices: false,
pattern: '*:*',
@ -255,9 +261,13 @@ const getIntegrations = async (core: CoreStart) => {
);
};
export const getESQLSources = async (dataViews: DataViewsPublicPluginStart, core: CoreStart) => {
export const getESQLSources = async (
dataViews: DataViewsPublicPluginStart,
core: CoreStart,
areRemoteIndicesAvailable: boolean
) => {
const [remoteIndices, localIndices, integrations] = await Promise.all([
getRemoteIndicesList(dataViews),
getRemoteIndicesList(dataViews, areRemoteIndicesAvailable),
getIndicesList(dataViews),
getIntegrations(core),
]);

View file

@ -13,6 +13,7 @@ import type { AggregateQuery } from '@kbn/es-query';
import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
import type { IndexManagementPluginSetup } from '@kbn/index-management-shared-types';
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
import type { ILicense } from '@kbn/licensing-plugin/public';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { Storage } from '@kbn/kibana-utils-plugin/public';
import type { UiActionsStart } from '@kbn/ui-actions-plugin/public';
@ -107,6 +108,7 @@ interface ESQLVariableService {
export interface EsqlPluginStartBase {
getJoinIndicesAutocomplete: () => Promise<JoinIndicesAutocompleteResult>;
variablesService: ESQLVariableService;
getLicense: () => Promise<ILicense | undefined>;
}
export interface ESQLEditorDeps {

View file

@ -34,7 +34,8 @@
"@kbn/kibana-utils-plugin",
"@kbn/ui-actions-plugin",
"@kbn/shared-ux-table-persist",
"@kbn/esql-types"
"@kbn/esql-types",
"@kbn/licensing-plugin"
],
"exclude": [
"target/**/*",

View file

@ -11,7 +11,8 @@
"optionalPlugins": [
"indexManagement",
"fieldsMetadata",
"usageCollection"
"usageCollection",
"licensing"
],
"requiredPlugins": [
"data",

View file

@ -10,6 +10,7 @@
import type { Plugin, CoreStart, CoreSetup } from '@kbn/core/public';
import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { ExpressionsStart } from '@kbn/expressions-plugin/public';
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { IndexManagementPluginSetup } from '@kbn/index-management-shared-types';
import type { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public';
@ -41,6 +42,7 @@ interface EsqlPluginStartDependencies {
uiActions: UiActionsStart;
data: DataPublicPluginStart;
fieldsMetadata: FieldsMetadataPublicStart;
licensing?: LicensingPluginStart;
usageCollection?: UsageCollectionStart;
}
@ -70,6 +72,7 @@ export class EsqlPlugin implements Plugin<{}, EsqlPluginStart> {
uiActions,
fieldsMetadata,
usageCollection,
licensing,
}: EsqlPluginStartDependencies
): EsqlPluginStart {
const storage = new Storage(localStorage);
@ -112,6 +115,7 @@ export class EsqlPlugin implements Plugin<{}, EsqlPluginStart> {
const start = {
getJoinIndicesAutocomplete,
variablesService,
getLicense: async () => await licensing?.getLicense(),
};
setKibanaServices(

View file

@ -35,6 +35,7 @@
"@kbn/i18n-react",
"@kbn/visualization-utils",
"@kbn/esql-types",
"@kbn/licensing-plugin"
],
"exclude": [
"target/**/*",