diff --git a/x-pack/plugins/enterprise_search/server/utils/connectors_search_result_provider.test.ts b/x-pack/plugins/enterprise_search/server/utils/connectors_search_result_provider.test.ts index f865da1f0dcef..0d05ed77ba7c5 100644 --- a/x-pack/plugins/enterprise_search/server/utils/connectors_search_result_provider.test.ts +++ b/x-pack/plugins/enterprise_search/server/utils/connectors_search_result_provider.test.ts @@ -5,10 +5,12 @@ * 2.0. */ -import { NEVER, lastValueFrom } from 'rxjs'; +import { NEVER, lastValueFrom, of } from 'rxjs'; import { IScopedClusterClient } from '@kbn/core/server'; +import type { GlobalSearchProviderContext } from '@kbn/global-search-plugin/server'; + import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../common/constants'; import { getConnectorsSearchResultProvider } from './connectors_search_result_provider'; @@ -57,6 +59,24 @@ describe('Enterprise Search - connectors search provider', () => { }, asInternalUser: {}, }; + + const getSearchProviderContext = ({ + enterpriseSearchEnabled, + }: { + enterpriseSearchEnabled: boolean; + }): GlobalSearchProviderContext => ({ + core: { + capabilities: of({ + catalogue: { enterpriseSearch: enterpriseSearchEnabled }, + management: {}, + navLinks: {}, + }), + savedObjects: {} as any, + uiSettings: {} as any, + }, + }); + const mockSearchProviderContext = getSearchProviderContext({ enterpriseSearchEnabled: true }); + afterEach(() => { jest.clearAllMocks(); }); @@ -74,7 +94,7 @@ describe('Enterprise Search - connectors search provider', () => { client, preference: '', }, - {} as any + mockSearchProviderContext ) ); expect(results).toEqual([{ ...getConnectorSearchData('postgres'), score: 100 }]); @@ -90,7 +110,7 @@ describe('Enterprise Search - connectors search provider', () => { maxResults: 100, preference: '', }, - {} as any + mockSearchProviderContext ) ); expect(results).toEqual([ @@ -110,7 +130,7 @@ describe('Enterprise Search - connectors search provider', () => { maxResults: 100, preference: '', }, - {} as any + mockSearchProviderContext ) ); expect(results).toHaveLength(0); @@ -126,7 +146,7 @@ describe('Enterprise Search - connectors search provider', () => { maxResults: 1, preference: '', }, - {} as any + mockSearchProviderContext ) ); expect(results).toEqual([{ ...getConnectorSearchData('postgres'), score: 90 }]); @@ -143,7 +163,7 @@ describe('Enterprise Search - connectors search provider', () => { maxResults: 100, preference: '', }, - {} as any + mockSearchProviderContext ) ); expect(results).toEqual([]); @@ -159,7 +179,7 @@ describe('Enterprise Search - connectors search provider', () => { maxResults: 100, preference: '', }, - {} as any + mockSearchProviderContext ) ); expect(results).toEqual([]); @@ -175,7 +195,7 @@ describe('Enterprise Search - connectors search provider', () => { maxResults: 100, preference: '', }, - {} as any + mockSearchProviderContext ) ); expect(results).toEqual([]); @@ -191,7 +211,23 @@ describe('Enterprise Search - connectors search provider', () => { maxResults: 100, preference: '', }, - {} as any + mockSearchProviderContext + ) + ); + expect(results).toEqual([]); + }); + + it('if capabilities.catalogue.enterpriseSearch is false', async () => { + const results = await lastValueFrom( + connectorsSearchResultProvider.find( + { term: 'companyName-postgres-connector-all' }, + { + aborted$: NEVER, + client, + maxResults: 100, + preference: '', + }, + getSearchProviderContext({ enterpriseSearchEnabled: false }) ) ); expect(results).toEqual([]); diff --git a/x-pack/plugins/enterprise_search/server/utils/connectors_search_result_provider.ts b/x-pack/plugins/enterprise_search/server/utils/connectors_search_result_provider.ts index 7e81d5b6d09ec..09386a4a30300 100644 --- a/x-pack/plugins/enterprise_search/server/utils/connectors_search_result_provider.ts +++ b/x-pack/plugins/enterprise_search/server/utils/connectors_search_result_provider.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { from, takeUntil } from 'rxjs'; +import { from, takeUntil, switchMap, of } from 'rxjs'; import type { IStaticAssets } from '@kbn/core-http-browser'; @@ -25,7 +25,7 @@ export function getConnectorsSearchResultProvider( staticAssets: IStaticAssets ): GlobalSearchResultProvider { return { - find: ({ term, types, tags }, { aborted$, client, maxResults }) => { + find: ({ term, types, tags }, { aborted$, client, maxResults }, { core: { capabilities } }) => { if (!client || !term || tags || (types && !types.includes('connector'))) { return from([[]]); } @@ -52,7 +52,17 @@ export function getConnectorsSearchResultProvider( .slice(0, maxResults); return searchResults; }; - return from(getConnectorData()).pipe(takeUntil(aborted$)); + + return capabilities.pipe( + takeUntil(aborted$), + switchMap((caps) => { + if (!caps.catalogue.enterpriseSearch) { + return of([]); + } + + return from(getConnectorData()); + }) + ); }, getSearchableTypes: () => ['connector'], id: 'enterpriseSearchConnectors', diff --git a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts index ef6e3f665b55a..49e94e76cd7a0 100644 --- a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts +++ b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.test.ts @@ -5,9 +5,11 @@ * 2.0. */ -import { NEVER } from 'rxjs'; +import { NEVER, of } from 'rxjs'; import { TestScheduler } from 'rxjs/testing'; +import type { GlobalSearchProviderContext } from '@kbn/global-search-plugin/server'; + import { ENTERPRISE_SEARCH_CONTENT_PLUGIN } from '../../common/constants'; import { getSearchResultProvider } from './search_result_provider'; @@ -18,6 +20,23 @@ const getTestScheduler = () => { }); }; +const getSearchProviderContext = ({ + enterpriseSearchEnabled, +}: { + enterpriseSearchEnabled: boolean; +}): GlobalSearchProviderContext => ({ + core: { + capabilities: of({ + catalogue: { enterpriseSearch: enterpriseSearchEnabled }, + management: {}, + navLinks: {}, + }), + savedObjects: {} as any, + uiSettings: {} as any, + }, +}); +const mockSearchProviderContext = getSearchProviderContext({ enterpriseSearchEnabled: true }); + const connectors = [ { categories: [ @@ -119,7 +138,7 @@ describe('Enterprise Search search provider', () => { searchResultProvider.find( { term: 'crawler' }, { aborted$: NEVER, maxResults: 100, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: [crawlerResult], @@ -133,7 +152,7 @@ describe('Enterprise Search search provider', () => { searchResultProvider.find( { term: '' }, { aborted$: NEVER, maxResults: 100, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: expect.arrayContaining([ @@ -150,7 +169,7 @@ describe('Enterprise Search search provider', () => { searchResultProvider.find( { term: '' }, { aborted$: NEVER, maxResults: 1, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: [{ ...crawlerResult, score: 80 }], @@ -173,7 +192,7 @@ describe('Enterprise Search search provider', () => { searchProvider.find( { term: '' }, { aborted$: NEVER, maxResults: 100, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: expect.not.arrayContaining([{ ...crawlerResult, score: 80 }]), @@ -196,7 +215,7 @@ describe('Enterprise Search search provider', () => { searchProvider.find( { term: '' }, { aborted$: NEVER, maxResults: 100, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: expect.not.arrayContaining([{ mongoResult, score: 80 }]), @@ -210,20 +229,34 @@ describe('Enterprise Search search provider', () => { searchResultProvider.find( { tags: ['tag'], term: '' }, { aborted$: NEVER, maxResults: 1, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: [], }); }); }); + it('returns nothing if unknown type is specified', () => { getTestScheduler().run(({ expectObservable }) => { expectObservable( searchResultProvider.find( { term: '', types: ['tag'] }, { aborted$: NEVER, maxResults: 1, preference: '' }, - {} as any + mockSearchProviderContext + ) + ).toBe('(a|)', { + a: [], + }); + }); + }); + it('returns nothing if capabilities.catalogue.enterpriseSearch is false', () => { + getTestScheduler().run(({ expectObservable }) => { + expectObservable( + searchResultProvider.find( + { term: '', types: ['tag'] }, + { aborted$: NEVER, maxResults: 1, preference: '' }, + getSearchProviderContext({ enterpriseSearchEnabled: false }) ) ).toBe('(a|)', { a: [], @@ -236,7 +269,7 @@ describe('Enterprise Search search provider', () => { searchResultProvider.find( { term: 'crawler', types: ['integration'] }, { aborted$: NEVER, maxResults: 1, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: [crawlerResult], @@ -249,7 +282,7 @@ describe('Enterprise Search search provider', () => { searchResultProvider.find( { term: 'crawler', types: ['enterprise search'] }, { aborted$: NEVER, maxResults: 1, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: [crawlerResult], @@ -272,7 +305,7 @@ describe('Enterprise Search search provider', () => { searchProvider.find( { term: 'app search' }, { aborted$: NEVER, maxResults: 1, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: [], @@ -295,7 +328,7 @@ describe('Enterprise Search search provider', () => { searchProvider.find( { term: 'workplace search' }, { aborted$: NEVER, maxResults: 1, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: [], @@ -318,7 +351,7 @@ describe('Enterprise Search search provider', () => { searchProvider.find( { term: '' }, { aborted$: NEVER, maxResults: 100, preference: '' }, - {} as any + mockSearchProviderContext ) ).toBe('(a|)', { a: expect.arrayContaining([ diff --git a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts index bd01e16109f80..15b1971c6aecd 100644 --- a/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts +++ b/x-pack/plugins/enterprise_search/server/utils/search_result_provider.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { from, takeUntil } from 'rxjs'; +import { takeUntil, of, map } from 'rxjs'; import { GlobalSearchResultProvider } from '@kbn/global-search-plugin/server'; import { i18n } from '@kbn/i18n'; @@ -79,73 +79,84 @@ export function getSearchResultProvider( crawlerIconPath: string ): GlobalSearchResultProvider { return { - find: ({ term, types, tags }, { aborted$, maxResults }) => { + find: ({ term, types, tags }, { aborted$, maxResults }, { core: { capabilities } }) => { if ( tags || (types && !(types.includes('integration') || types.includes('enterprise search'))) ) { - return from([[]]); + return of([]); } - const services: ServiceDefinition[] = [ - ...(config.hasWebCrawler - ? [ - { - iconPath: crawlerIconPath, - keywords: ['crawler', 'web', 'website', 'internet', 'google'], - name: i18n.translate('xpack.enterpriseSearch.searchProvider.webCrawler.name', { - defaultMessage: 'Elastic Web Crawler', - }), - serviceType: ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE, - }, - ] - : []), - ...(config.hasConnectors ? connectorTypes : []), - ...(config.canDeployEntSearch - ? [ - { - keywords: ['esre', 'search'], - name: i18n.translate('xpack.enterpriseSearch.searchProvider.aiSearch.name', { - defaultMessage: 'Search AI', - }), - serviceType: 'ai_search', - url: AI_SEARCH_PLUGIN.URL, - }, - ] - : []), - ]; - const result = services - .map((service) => { - const { iconPath, isNative, keywords, name, serviceType } = service; - const url = 'url' in service ? service.url : undefined; - let score = 0; - const searchTerm = (term || '').toLowerCase(); - const searchName = name.toLowerCase(); - if (!searchTerm) { - score = 80; - } else if (searchName === searchTerm) { - score = 100; - } else if (searchName.startsWith(searchTerm)) { - score = 90; - } else if (searchName.includes(searchTerm)) { - score = 75; - } else if (serviceType === searchTerm) { - score = 65; - } else if (keywords.some((keyword) => keyword.includes(searchTerm))) { - score = 50; + + return capabilities.pipe( + takeUntil(aborted$), + map((caps) => { + if (!caps.catalogue.enterpriseSearch) { + return []; } - return toSearchResult({ - iconPath, - isCloud, - isNative, - name, - score, - serviceType, - url, - }); + + const services: ServiceDefinition[] = [ + ...(config.hasWebCrawler + ? [ + { + iconPath: crawlerIconPath, + keywords: ['crawler', 'web', 'website', 'internet', 'google'], + name: i18n.translate('xpack.enterpriseSearch.searchProvider.webCrawler.name', { + defaultMessage: 'Elastic Web Crawler', + }), + serviceType: ENTERPRISE_SEARCH_CONNECTOR_CRAWLER_SERVICE_TYPE, + }, + ] + : []), + ...(config.hasConnectors ? connectorTypes : []), + ...(config.canDeployEntSearch + ? [ + { + keywords: ['esre', 'search'], + name: i18n.translate('xpack.enterpriseSearch.searchProvider.aiSearch.name', { + defaultMessage: 'Search AI', + }), + serviceType: 'ai_search', + url: AI_SEARCH_PLUGIN.URL, + }, + ] + : []), + ]; + const result = services + .map((service) => { + const { iconPath, isNative, keywords, name, serviceType } = service; + const url = 'url' in service ? service.url : undefined; + let score = 0; + const searchTerm = (term || '').toLowerCase(); + const searchName = name.toLowerCase(); + if (!searchTerm) { + score = 80; + } else if (searchName === searchTerm) { + score = 100; + } else if (searchName.startsWith(searchTerm)) { + score = 90; + } else if (searchName.includes(searchTerm)) { + score = 75; + } else if (serviceType === searchTerm) { + score = 65; + } else if (keywords.some((keyword) => keyword.includes(searchTerm))) { + score = 50; + } + return toSearchResult({ + iconPath, + isCloud, + isNative, + name, + score, + serviceType, + url, + }); + }) + .filter(({ score }) => score > 0) + .slice(0, maxResults); + + return result; }) - .filter(({ score }) => score > 0) - .slice(0, maxResults); - return from([result]).pipe(takeUntil(aborted$)); + ); }, getSearchableTypes: () => ['enterprise search', 'integration'], id: 'enterpriseSearch', diff --git a/x-pack/plugins/observability_solution/infra/server/features.ts b/x-pack/plugins/observability_solution/infra/server/features.ts index 48091c9fe4b7f..b2f83967920e1 100644 --- a/x-pack/plugins/observability_solution/infra/server/features.ts +++ b/x-pack/plugins/observability_solution/infra/server/features.ts @@ -106,7 +106,7 @@ export const LOGS_FEATURE = { order: 700, category: DEFAULT_APP_CATEGORIES.observability, scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security], - app: ['infra', 'logs', 'kibana'], + app: ['infra', 'logs', 'kibana', 'observability-logs-explorer'], catalogue: ['infralogging', 'logs'], management: { insightsAndAlerting: ['triggersActions'], @@ -114,7 +114,7 @@ export const LOGS_FEATURE = { alerting: logsRuleTypes, privileges: { all: { - app: ['infra', 'logs', 'kibana'], + app: ['infra', 'logs', 'kibana', 'observability-logs-explorer'], catalogue: ['infralogging', 'logs'], api: ['infra', 'rac'], savedObject: { @@ -135,7 +135,7 @@ export const LOGS_FEATURE = { ui: ['show', 'configureSource', 'save'], }, read: { - app: ['infra', 'logs', 'kibana'], + app: ['infra', 'logs', 'kibana', 'observability-logs-explorer'], catalogue: ['infralogging', 'logs'], api: ['infra', 'rac'], alerting: { diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts index f53bb02787f8d..364afdcaba66a 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.test.ts @@ -31,6 +31,35 @@ const createMockConfig = ( return ConfigSchema.validate(mockConfig, { serverless: !mockConfig.allowFeatureVisibility }); }; +const features = [ + { + id: 'feature_1', + name: 'Feature 1', + app: [], + category: { id: 'enterpriseSearch' }, + scope: ['spaces', 'security'], + }, + { + id: 'feature_2', + name: 'Feature 2', + app: ['feature2'], + scope: ['spaces', 'security'], + catalogue: ['feature2Entry'], + category: { id: 'observability' }, + }, + { + id: 'feature_3', + name: 'Feature 3', + app: ['feature3_app'], + scope: ['spaces', 'security'], + catalogue: ['feature3Entry'], + category: { id: 'securitySolution' }, + }, +] as unknown as KibanaFeature[]; +const featuresStart = featuresPluginMock.createStart(); + +featuresStart.getKibanaFeatures.mockReturnValue([...features]); + describe('#getAll', () => { const savedObjects: Array> = [ { @@ -84,7 +113,7 @@ describe('#getAll', () => { color: '#FFFFFF', initials: 'FB', imageUrl: 'go-bots/predates/transformers', - disabledFeatures: [], + disabledFeatures: ['feature_2', 'feature_3'], // Added dynamically because solution is 'es' solution: 'es', _reserved: true, }, @@ -117,7 +146,7 @@ describe('#getAll', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const actualSpaces = await client.getAll(); @@ -145,13 +174,16 @@ describe('#getAll', () => { mockCallWithRequestRepository, [], 'serverless', - featuresPluginMock.createStart() + featuresStart ); const [actualSpace] = await client.getAll(); - const [{ solution, ...expectedSpace }] = expectedSpaces; + const [{ solution, disabledFeatures, ...expectedSpace }] = expectedSpaces; expect(actualSpace.solution).toBeUndefined(); - expect(actualSpace).toEqual(expectedSpace); + expect(actualSpace).toEqual({ + ...expectedSpace, + disabledFeatures: [], // And the disabledFeatures is not dynamically added + }); expect(mockCallWithRequestRepository.find).toHaveBeenCalledWith({ type: 'space', page: 1, @@ -170,7 +202,7 @@ describe('#getAll', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); await expect( client.getAll({ purpose: 'invalid_purpose' as GetAllSpacesPurpose }) @@ -218,7 +250,7 @@ describe('#get', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; const actualSpace = await client.get(id); @@ -242,7 +274,7 @@ describe('#get', () => { mockCallWithRequestRepository, [], 'serverless', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; const actualSpace = await client.get(id); @@ -266,12 +298,16 @@ describe('#get', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; const actualSpace = await client.get(id); - expect(actualSpace).toEqual({ ...expectedSpace, solution: 'es' }); + expect(actualSpace).toEqual({ + ...expectedSpace, + solution: 'es', + disabledFeatures: ['feature_2', 'feature_3'], // Added dynamically because solution is 'es' + }); }); }); @@ -312,7 +348,10 @@ describe('#create', () => { const maxSpaces = 5; const mockDebugLogger = createMockDebugLogger(); const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); - mockCallWithRequestRepository.create.mockResolvedValue(savedObject); + mockCallWithRequestRepository.create.mockResolvedValue({ + ...savedObject, + attributes: { ...(savedObject.attributes as object), solution: 'es' }, + }); mockCallWithRequestRepository.find.mockResolvedValue({ total: maxSpaces - 1, } as any); @@ -330,20 +369,28 @@ describe('#create', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); - const actualSpace = await client.create(spaceToCreate); + const actualSpace = await client.create({ ...spaceToCreate, solution: 'es' }); - expect(actualSpace).toEqual(expectedReturnedSpace); + expect(actualSpace).toEqual({ + ...expectedReturnedSpace, + solution: 'es', + disabledFeatures: ['feature_2', 'feature_3'], // Added dynamically because solution is 'es' + }); expect(mockCallWithRequestRepository.find).toHaveBeenCalledWith({ type: 'space', page: 1, perPage: 0, }); - expect(mockCallWithRequestRepository.create).toHaveBeenCalledWith('space', attributes, { - id, - }); + expect(mockCallWithRequestRepository.create).toHaveBeenCalledWith( + 'space', + { ...attributes, solution: 'es' }, + { + id, + } + ); }); test(`throws bad request when creating space with disabled features`, async () => { @@ -355,7 +402,7 @@ describe('#create', () => { mockCallWithRequestRepository.find.mockResolvedValue({ total: maxSpaces - 1, } as any); - const featuresMock = featuresPluginMock.createStart(); + const featuresMock = featuresStart; featuresMock.getKibanaFeatures.mockReturnValue([ new KibanaFeature({ @@ -422,7 +469,7 @@ describe('#create', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); await expect(client.create(spaceToCreate)).rejects.toThrowErrorMatchingInlineSnapshot( @@ -459,7 +506,7 @@ describe('#create', () => { mockCallWithRequestRepository, [], 'serverless', - featuresPluginMock.createStart() + featuresStart ); await expect( @@ -507,7 +554,7 @@ describe('#create', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const actualSpace = await client.create({ ...spaceToCreate, solution: 'es' }); @@ -551,7 +598,7 @@ describe('#create', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const actualSpace = await client.create(spaceToCreate); @@ -589,7 +636,7 @@ describe('#create', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); await expect( @@ -630,7 +677,7 @@ describe('#create', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); await expect( @@ -687,7 +734,11 @@ describe('#update', () => { const mockDebugLogger = createMockDebugLogger(); const mockConfig = createMockConfig(); const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); - mockCallWithRequestRepository.get.mockResolvedValue(savedObject); + mockCallWithRequestRepository.get.mockResolvedValueOnce({ + ...savedObject, + attributes: { ...(savedObject.attributes as object), solution: 'es' }, + }); + featuresStart.getKibanaFeatures.mockReturnValue([...features]); const client = new SpacesClient( mockDebugLogger, @@ -695,13 +746,20 @@ describe('#update', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; - const actualSpace = await client.update(id, spaceToUpdate); + const actualSpace = await client.update(id, { ...spaceToUpdate, solution: 'es' }); - expect(actualSpace).toEqual(expectedReturnedSpace); - expect(mockCallWithRequestRepository.update).toHaveBeenCalledWith('space', id, attributes); + expect(actualSpace).toEqual({ + ...expectedReturnedSpace, + solution: 'es', + disabledFeatures: ['feature_2', 'feature_3'], // Added dynamically because solution is 'es' + }); + expect(mockCallWithRequestRepository.update).toHaveBeenCalledWith('space', id, { + ...attributes, + solution: 'es', + }); expect(mockCallWithRequestRepository.get).toHaveBeenCalledWith('space', id); }); @@ -710,7 +768,7 @@ describe('#update', () => { const mockConfig = createMockConfig(); const mockCallWithRequestRepository = savedObjectsRepositoryMock.create(); mockCallWithRequestRepository.get.mockResolvedValue(savedObject); - const featuresMock = featuresPluginMock.createStart(); + const featuresMock = featuresStart; featuresMock.getKibanaFeatures.mockReturnValue([ new KibanaFeature({ @@ -767,7 +825,7 @@ describe('#update', () => { mockCallWithRequestRepository, [], 'serverless', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; @@ -800,7 +858,7 @@ describe('#update', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; @@ -827,7 +885,7 @@ describe('#update', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; await client.update(id, { ...spaceToUpdate, solution: 'es' }); @@ -857,7 +915,7 @@ describe('#update', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; const actualSpace = await client.update(id, spaceToUpdate); @@ -884,7 +942,7 @@ describe('#update', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; @@ -917,7 +975,7 @@ describe('#update', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const id = savedObject.id; @@ -971,7 +1029,7 @@ describe('#delete', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); await expect(client.delete(id)).rejects.toThrowErrorMatchingInlineSnapshot( @@ -993,7 +1051,7 @@ describe('#delete', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); await client.delete(id); @@ -1016,7 +1074,7 @@ describe('#disableLegacyUrlAliases', () => { mockCallWithRequestRepository, [], 'traditional', - featuresPluginMock.createStart() + featuresStart ); const aliases = [ { targetSpace: 'space1', targetType: 'foo', sourceId: '123' }, diff --git a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts index 4043da9f87225..5d7ae1159f5ea 100644 --- a/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/spaces_client/spaces_client.ts @@ -20,6 +20,7 @@ import type { FeaturesPluginStart } from '@kbn/features-plugin/server'; import { isReservedSpace } from '../../common'; import type { spaceV1 as v1 } from '../../common'; import type { ConfigType } from '../config'; +import { withSpaceSolutionDisabledFeatures } from '../lib/utils/space_solution_disabled_features'; const SUPPORTED_GET_SPACE_PURPOSES: v1.GetAllSpacesPurpose[] = [ 'any', @@ -253,7 +254,11 @@ export class SpacesClient implements ISpacesClient { color: savedObject.attributes.color, initials: savedObject.attributes.initials, imageUrl: savedObject.attributes.imageUrl, - disabledFeatures: savedObject.attributes.disabledFeatures ?? [], + disabledFeatures: withSpaceSolutionDisabledFeatures( + this.features.getKibanaFeatures(), + savedObject.attributes.disabledFeatures ?? [], + !this.isServerless ? savedObject.attributes.solution : undefined + ), _reserved: savedObject.attributes._reserved, ...(!this.isServerless ? { solution: savedObject.attributes.solution } : {}), } as v1.Space; diff --git a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index 01d4b47230294..fcf6043a30012 100644 --- a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -42,6 +42,29 @@ } } +{ + "type": "_doc", + "value": { + "id": "space:space_3", + "index": ".kibana", + "source": { + "space": { + "description": "This is the third test space", + "solution": "es", + "disabledFeatures": [], + "name": "Space 3" + }, + "type": "space", + "updated_at": "2017-09-21T18:49:16.270Z", + "coreMigrationVersion": "8.8.0", + "typeMigrationVersion": "6.6.0", + "managed": false, + "references": [] + }, + "type": "_doc" + } +} + { "type": "_doc", "value": { diff --git a/x-pack/test/spaces_api_integration/common/lib/authentication.ts b/x-pack/test/spaces_api_integration/common/lib/authentication.ts index 6e76e46dc5ee0..27f644c3f5cd5 100644 --- a/x-pack/test/spaces_api_integration/common/lib/authentication.ts +++ b/x-pack/test/spaces_api_integration/common/lib/authentication.ts @@ -66,6 +66,14 @@ export const AUTHENTICATION = { username: 'a_kibana_rbac_space_1_2_read_user', password: 'password', }, + KIBANA_RBAC_SPACE_3_ALL_USER: { + username: 'a_kibana_rbac_space_3_all_user', + password: 'password', + }, + KIBANA_RBAC_SPACE_3_READ_USER: { + username: 'a_kibana_rbac_space_3_read_user', + password: 'password', + }, KIBANA_RBAC_DEFAULT_SPACE_SAVED_OBJECTS_ALL_USER: { username: 'a_kibana_rbac_default_space_saved_objects_all_user', password: 'password', diff --git a/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts b/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts index 58ef5ba9f9481..b66c4a02a5bd6 100644 --- a/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts +++ b/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts @@ -184,6 +184,30 @@ export const createUsersAndRoles = async (es: Client, supertest: SuperTestAgent) }) .expect(204); + await supertest + .put('/api/security/role/kibana_rbac_space_3_all_user') + .send({ + kibana: [ + { + base: ['all'], + spaces: ['space_3'], + }, + ], + }) + .expect(204); + + await supertest + .put('/api/security/role/kibana_rbac_space_3_read_user') + .send({ + kibana: [ + { + base: ['read'], + spaces: ['space_3'], + }, + ], + }) + .expect(204); + await supertest .put('/api/security/role/kibana_rbac_default_space_saved_objects_all_user') .send({ @@ -344,6 +368,26 @@ export const createUsersAndRoles = async (es: Client, supertest: SuperTestAgent) }, }); + await es.security.putUser({ + username: AUTHENTICATION.KIBANA_RBAC_SPACE_3_ALL_USER.username, + body: { + password: AUTHENTICATION.KIBANA_RBAC_SPACE_3_ALL_USER.password, + roles: ['kibana_rbac_space_3_all_user'], + full_name: 'a kibana rbac space 3 all user', + email: 'a_kibana_rbac_space_3_all_user@elastic.co', + }, + }); + + await es.security.putUser({ + username: AUTHENTICATION.KIBANA_RBAC_SPACE_3_READ_USER.username, + body: { + password: AUTHENTICATION.KIBANA_RBAC_SPACE_3_READ_USER.password, + roles: ['kibana_rbac_space_3_read_user'], + full_name: 'a kibana rbac space 3 read-only user', + email: 'a_kibana_rbac_space_3_readonly_user@elastic.co', + }, + }); + await es.security.putUser({ username: AUTHENTICATION.KIBANA_RBAC_SPACE_2_ALL_USER.username, body: { diff --git a/x-pack/test/spaces_api_integration/common/lib/spaces.ts b/x-pack/test/spaces_api_integration/common/lib/spaces.ts index 75a1e518452b9..c61b7646233e7 100644 --- a/x-pack/test/spaces_api_integration/common/lib/spaces.ts +++ b/x-pack/test/spaces_api_integration/common/lib/spaces.ts @@ -12,6 +12,9 @@ export const SPACES = { SPACE_2: { spaceId: 'space_2', }, + SPACE_3: { + spaceId: 'space_3', + }, DEFAULT: { spaceId: 'default', }, diff --git a/x-pack/test/spaces_api_integration/common/suites/create.ts b/x-pack/test/spaces_api_integration/common/suites/create.ts index fc2bd1b841ccc..ce3113ec9639c 100644 --- a/x-pack/test/spaces_api_integration/common/suites/create.ts +++ b/x-pack/test/spaces_api_integration/common/suites/create.ts @@ -65,14 +65,32 @@ export function createTestSuiteFactory(esArchiver: any, supertest: SuperTest) => { - expect(resp.body).to.eql({ + const disabledFeatures = resp.body.disabledFeatures.sort(); + + const expected = { id: 'solution', name: 'space with solution', description: 'a description', color: '#5c5959', - disabledFeatures: [], + disabledFeatures: [ + // Disabled features are automatically added to the space when a solution is set + 'apm', + 'infrastructure', + 'inventory', + 'logs', + 'observabilityAIAssistant', + 'observabilityCases', + 'securitySolutionAssistant', + 'securitySolutionAttackDiscovery', + 'securitySolutionCases', + 'siem', + 'slo', + 'uptime', + ], solution: 'es', - }); + }; + + expect({ ...resp.body, disabledFeatures }).to.eql(expected); }; const makeCreateTest = diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts index b1ad6241e11df..fd04b79fd1ef5 100644 --- a/x-pack/test/spaces_api_integration/common/suites/delete.ts +++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts @@ -62,14 +62,14 @@ export function deleteTestSuiteFactory(es: Client, esArchiver: any, supertest: S const expectedBuckets = [ { key: 'default', - doc_count: 9, + doc_count: 10, countByType: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ + { key: 'space', doc_count: 3 }, // since space objects are namespace-agnostic, they appear in the "default" agg bucket { key: 'visualization', doc_count: 3 }, { key: 'legacy-url-alias', doc_count: 2 }, // aliases (1) - { key: 'space', doc_count: 2 }, // since space objects are namespace-agnostic, they appear in the "default" agg bucket { key: 'dashboard', doc_count: 1 }, { key: 'index-pattern', doc_count: 1 }, ], @@ -182,7 +182,7 @@ export function deleteTestSuiteFactory(es: Client, esArchiver: any, supertest: S describe(`when the space doesn't exist`, () => { it(`should return ${tests.doesntExist.statusCode} ${scenario}`, async () => { return supertest - .delete(`${urlPrefix}/api/spaces/space/space_3`) + .delete(`${urlPrefix}/api/spaces/space/space_7`) .auth(user.username, user.password) .expect(tests.doesntExist.statusCode) .then(tests.doesntExist.response); diff --git a/x-pack/test/spaces_api_integration/common/suites/get.ts b/x-pack/test/spaces_api_integration/common/suites/get.ts index 6956599ae40a6..a733bd21c5fc2 100644 --- a/x-pack/test/spaces_api_integration/common/suites/get.ts +++ b/x-pack/test/spaces_api_integration/common/suites/get.ts @@ -71,8 +71,37 @@ export function getTestSuiteFactory(esArchiver: any, supertest: SuperAgent) description: 'This is the second test space', disabledFeatures: [], }, + { + id: 'space_3', + name: 'Space 3', + description: 'This is the third test space', + solution: 'es', + disabledFeatures: [ + // Disabled features are automatically added to the space when a solution is set + 'apm', + 'infrastructure', + 'inventory', + 'logs', + 'observabilityAIAssistant', + 'observabilityCases', + 'securitySolutionAssistant', + 'securitySolutionAttackDiscovery', + 'securitySolutionCases', + 'siem', + 'slo', + 'uptime', + ], + }, ]; - expect(resp.body).to.eql(allSpaces.find((space) => space.id === spaceId)); + + const disabledFeatures = (resp.body.disabledFeatures ?? []).sort(); + + const expectedSpace = allSpaces.find((space) => space.id === spaceId); + if (expectedSpace) { + expectedSpace.disabledFeatures.sort(); + } + + expect({ ...resp.body, disabledFeatures }).to.eql(expectedSpace); }; const makeGetTest = diff --git a/x-pack/test/spaces_api_integration/common/suites/get_all.ts b/x-pack/test/spaces_api_integration/common/suites/get_all.ts index 5452624f77648..88625c3d9b51e 100644 --- a/x-pack/test/spaces_api_integration/common/suites/get_all.ts +++ b/x-pack/test/spaces_api_integration/common/suites/get_all.ts @@ -35,7 +35,17 @@ interface AuthorizedPurposes { shareSavedObjectsIntoSpace: boolean; } -const ALL_SPACE_RESULTS = [ +interface Space { + id: string; + name: string; + color?: string; + description: string; + solution?: string; + _reserved?: boolean; + disabledFeatures: string[]; +} + +const ALL_SPACE_RESULTS: Space[] = [ { id: 'default', name: 'Default', @@ -56,14 +66,42 @@ const ALL_SPACE_RESULTS = [ description: 'This is the second test space', disabledFeatures: [], }, + { + id: 'space_3', + name: 'Space 3', + description: 'This is the third test space', + solution: 'es', + disabledFeatures: [ + // Disabled features are automatically added to the space when a solution is set + 'apm', + 'infrastructure', + 'inventory', + 'logs', + 'observabilityAIAssistant', + 'observabilityCases', + 'securitySolutionAssistant', + 'securitySolutionAttackDiscovery', + 'securitySolutionCases', + 'siem', + 'slo', + 'uptime', + ], + }, ]; +const sortDisabledFeatures = (space: Space) => { + return { + ...space, + disabledFeatures: [...space.disabledFeatures].sort(), + }; +}; + export function getAllTestSuiteFactory(esArchiver: any, supertest: SuperTest) { const createExpectResults = (...spaceIds: string[]) => (resp: { [key: string]: any }) => { const expectedBody = ALL_SPACE_RESULTS.filter((entry) => spaceIds.includes(entry.id)); - expect(resp.body).to.eql(expectedBody); + expect(resp.body.map(sortDisabledFeatures)).to.eql(expectedBody.map(sortDisabledFeatures)); }; const createExpectAllPurposesResults = @@ -72,7 +110,7 @@ export function getAllTestSuiteFactory(esArchiver: any, supertest: SuperTest spaceIds.includes(entry.id)).map( (x) => ({ ...x, authorizedPurposes }) ); - expect(resp.body).to.eql(expectedBody); + expect(resp.body.map(sortDisabledFeatures)).to.eql(expectedBody.map(sortDisabledFeatures)); }; const expectEmptyResult = (resp: { [key: string]: any }) => { diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts index 122a9218555fe..7354138b7987e 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get.ts @@ -58,6 +58,22 @@ export default function getSpaceTestSuite({ getService }: FtrProviderContext) { dualRead: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, }, }, + { + spaceId: SPACES.SPACE_3.spaceId, // This space has a solution set and we expect disabledFeatures to be automatically set + otherSpaceId: SPACES.DEFAULT.spaceId, + users: { + noAccess: AUTHENTICATION.NOT_A_KIBANA_USER, + superuser: AUTHENTICATION.SUPERUSER, + allGlobally: AUTHENTICATION.KIBANA_RBAC_USER, + readGlobally: AUTHENTICATION.KIBANA_RBAC_DASHBOARD_ONLY_USER, + allAtSpace: AUTHENTICATION.KIBANA_RBAC_SPACE_3_ALL_USER, + readAtSpace: AUTHENTICATION.KIBANA_RBAC_SPACE_3_READ_USER, + allAtOtherSpace: AUTHENTICATION.KIBANA_RBAC_DEFAULT_SPACE_ALL_USER, + legacyAll: AUTHENTICATION.KIBANA_LEGACY_USER, + dualAll: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_USER, + dualRead: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, + }, + }, ].forEach((scenario) => { getTest(`user with no access`, { currentSpaceId: scenario.spaceId, diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts index 992ab6c7028a6..d2c3b8be03be2 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts @@ -119,15 +119,15 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext tests: { exists: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, copySavedObjectsPurpose: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, shareSavedObjectsPurpose: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, includeAuthorizedPurposes: { statusCode: 200, @@ -135,7 +135,8 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext authorizedAll, 'default', 'space_1', - 'space_2' + 'space_2', + 'space_3' ), }, }, @@ -147,15 +148,15 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext tests: { exists: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, copySavedObjectsPurpose: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, shareSavedObjectsPurpose: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, includeAuthorizedPurposes: { statusCode: 200, @@ -163,7 +164,8 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext authorizedAll, 'default', 'space_1', - 'space_2' + 'space_2', + 'space_3' ), }, }, @@ -175,15 +177,15 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext tests: { exists: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, copySavedObjectsPurpose: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, shareSavedObjectsPurpose: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, includeAuthorizedPurposes: { statusCode: 200, @@ -191,7 +193,8 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext authorizedAll, 'default', 'space_1', - 'space_2' + 'space_2', + 'space_3' ), }, }, @@ -226,7 +229,7 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext tests: { exists: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, copySavedObjectsPurpose: { statusCode: 403, @@ -242,7 +245,8 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext authorizedRead, 'default', 'space_1', - 'space_2' + 'space_2', + 'space_3' ), }, }, @@ -254,7 +258,7 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext tests: { exists: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, copySavedObjectsPurpose: { statusCode: 403, @@ -270,7 +274,8 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext authorizedRead, 'default', 'space_1', - 'space_2' + 'space_2', + 'space_3' ), }, }, diff --git a/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts b/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts index 46ddb59461945..6331c843649fa 100644 --- a/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts +++ b/x-pack/test/spaces_api_integration/spaces_only/apis/get_all.ts @@ -34,19 +34,19 @@ export default function getAllSpacesTestSuite({ getService }: FtrProviderContext tests: { exists: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, copySavedObjectsPurpose: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, shareSavedObjectsPurpose: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, includeAuthorizedPurposes: { statusCode: 200, - response: createExpectResults('default', 'space_1', 'space_2'), + response: createExpectResults('default', 'space_1', 'space_2', 'space_3'), }, }, }); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts index ecf6ddd2c76f8..ac53ceacf9a0a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/platform_security/authorization.ts @@ -231,10 +231,12 @@ export default function ({ getService }: FtrProviderContext) { "api:infra", "app:infra", "app:logs", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:navLinks/infra", "ui:navLinks/logs", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-ui-source/bulk_get", "saved_object:infrastructure-ui-source/get", "saved_object:infrastructure-ui-source/find", @@ -881,10 +883,12 @@ export default function ({ getService }: FtrProviderContext) { "api:infra", "app:infra", "app:logs", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:navLinks/infra", "ui:navLinks/logs", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-ui-source/bulk_get", "saved_object:infrastructure-ui-source/get", "saved_object:infrastructure-ui-source/find", @@ -1441,10 +1445,12 @@ export default function ({ getService }: FtrProviderContext) { "api:infra", "app:infra", "app:logs", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:navLinks/infra", "ui:navLinks/logs", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-ui-source/bulk_get", "saved_object:infrastructure-ui-source/get", "saved_object:infrastructure-ui-source/find", @@ -1724,10 +1730,12 @@ export default function ({ getService }: FtrProviderContext) { "api:infra", "app:infra", "app:logs", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:navLinks/infra", "ui:navLinks/logs", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-ui-source/bulk_get", "saved_object:infrastructure-ui-source/get", "saved_object:infrastructure-ui-source/find", @@ -3820,12 +3828,14 @@ export default function ({ getService }: FtrProviderContext) { "app:infra", "app:logs", "app:kibana", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:management/insightsAndAlerting/triggersActions", "ui:navLinks/infra", "ui:navLinks/logs", "ui:navLinks/kibana", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-ui-source/bulk_get", "saved_object:infrastructure-ui-source/get", "saved_object:infrastructure-ui-source/find", @@ -4171,12 +4181,14 @@ export default function ({ getService }: FtrProviderContext) { "app:infra", "app:logs", "app:kibana", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:management/insightsAndAlerting/triggersActions", "ui:navLinks/infra", "ui:navLinks/logs", "ui:navLinks/kibana", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-ui-source/bulk_get", "saved_object:infrastructure-ui-source/get", "saved_object:infrastructure-ui-source/find", @@ -4429,12 +4441,14 @@ export default function ({ getService }: FtrProviderContext) { "app:infra", "app:logs", "app:kibana", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:management/insightsAndAlerting/triggersActions", "ui:navLinks/infra", "ui:navLinks/logs", "ui:navLinks/kibana", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-ui-source/bulk_get", "saved_object:infrastructure-ui-source/get", "saved_object:infrastructure-ui-source/find", @@ -4591,12 +4605,14 @@ export default function ({ getService }: FtrProviderContext) { "app:infra", "app:logs", "app:kibana", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:management/insightsAndAlerting/triggersActions", "ui:navLinks/infra", "ui:navLinks/logs", "ui:navLinks/kibana", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-ui-source/bulk_get", "saved_object:infrastructure-ui-source/get", "saved_object:infrastructure-ui-source/find", @@ -4901,9 +4917,11 @@ export default function ({ getService }: FtrProviderContext) { "alerting:xpack.ml.anomaly_detection_alert/infrastructure/alert/getAlertSummary", "alerting:xpack.ml.anomaly_detection_alert/infrastructure/alert/update", "app:logs", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:navLinks/logs", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-monitoring-log-view/bulk_get", "saved_object:infrastructure-monitoring-log-view/get", "saved_object:infrastructure-monitoring-log-view/find", @@ -5592,9 +5610,11 @@ export default function ({ getService }: FtrProviderContext) { "alerting:xpack.ml.anomaly_detection_alert/infrastructure/alert/getAlertSummary", "alerting:xpack.ml.anomaly_detection_alert/infrastructure/alert/update", "app:logs", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:navLinks/logs", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-monitoring-log-view/bulk_get", "saved_object:infrastructure-monitoring-log-view/get", "saved_object:infrastructure-monitoring-log-view/find", @@ -6160,9 +6180,11 @@ export default function ({ getService }: FtrProviderContext) { "alerting:xpack.ml.anomaly_detection_alert/infrastructure/alert/getAuthorizedAlertsIndices", "alerting:xpack.ml.anomaly_detection_alert/infrastructure/alert/getAlertSummary", "app:logs", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:navLinks/logs", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-monitoring-log-view/bulk_get", "saved_object:infrastructure-monitoring-log-view/get", "saved_object:infrastructure-monitoring-log-view/find", @@ -6458,9 +6480,11 @@ export default function ({ getService }: FtrProviderContext) { "alerting:xpack.ml.anomaly_detection_alert/infrastructure/alert/getAuthorizedAlertsIndices", "alerting:xpack.ml.anomaly_detection_alert/infrastructure/alert/getAlertSummary", "app:logs", + "app:observability-logs-explorer", "ui:catalogue/infralogging", "ui:catalogue/logs", "ui:navLinks/logs", + "ui:navLinks/observability-logs-explorer", "saved_object:infrastructure-monitoring-log-view/bulk_get", "saved_object:infrastructure-monitoring-log-view/get", "saved_object:infrastructure-monitoring-log-view/find",