mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
a1a0d3462d
commit
708917df6e
8 changed files with 76 additions and 8 deletions
|
@ -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;
|
||||
|
|
|
@ -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([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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),
|
||||
]);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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/**/*",
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"optionalPlugins": [
|
||||
"indexManagement",
|
||||
"fieldsMetadata",
|
||||
"usageCollection"
|
||||
"usageCollection",
|
||||
"licensing"
|
||||
],
|
||||
"requiredPlugins": [
|
||||
"data",
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"@kbn/i18n-react",
|
||||
"@kbn/visualization-utils",
|
||||
"@kbn/esql-types",
|
||||
"@kbn/licensing-plugin"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue