diff --git a/packages/content-management/favorites/favorites_public/src/favorites_client.ts b/packages/content-management/favorites/favorites_public/src/favorites_client.ts index 84c44db5fd64c..c8a9fa5502553 100644 --- a/packages/content-management/favorites/favorites_public/src/favorites_client.ts +++ b/packages/content-management/favorites/favorites_public/src/favorites_client.ts @@ -9,11 +9,13 @@ import type { HttpStart } from '@kbn/core-http-browser'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; +import type { UserProfileServiceStart } from '@kbn/core-user-profile-browser'; import type { - GetFavoritesResponse as GetFavoritesResponseServer, AddFavoriteResponse, + GetFavoritesResponse as GetFavoritesResponseServer, RemoveFavoriteResponse, } from '@kbn/content-management-favorites-server'; +import { firstValueFrom } from 'rxjs'; export interface GetFavoritesResponse extends GetFavoritesResponseServer { @@ -29,6 +31,7 @@ export interface FavoritesClientPublic { addFavorite(params: AddFavoriteRequest): Promise; removeFavorite(params: { id: string }): Promise; + isAvailable(): Promise; getFavoriteType(): string; reportAddFavoriteClick(): void; reportRemoveFavoriteClick(): void; @@ -40,14 +43,29 @@ export class FavoritesClient constructor( private readonly appName: string, private readonly favoriteObjectType: string, - private readonly deps: { http: HttpStart; usageCollection?: UsageCollectionStart } + private readonly deps: { + http: HttpStart; + userProfile: UserProfileServiceStart; + usageCollection?: UsageCollectionStart; + } ) {} + public async isAvailable(): Promise { + return firstValueFrom(this.deps.userProfile.getEnabled$()); + } + + private async ifAvailablePreCheck() { + if (!(await this.isAvailable())) + throw new Error('Favorites service is not available for current user'); + } + public async getFavorites(): Promise> { + await this.ifAvailablePreCheck(); return this.deps.http.get(`/internal/content_management/favorites/${this.favoriteObjectType}`); } public async addFavorite(params: AddFavoriteRequest): Promise { + await this.ifAvailablePreCheck(); return this.deps.http.post( `/internal/content_management/favorites/${this.favoriteObjectType}/${params.id}/favorite`, { body: 'metadata' in params ? JSON.stringify({ metadata: params.metadata }) : undefined } @@ -55,6 +73,7 @@ export class FavoritesClient } public async removeFavorite({ id }: { id: string }): Promise { + await this.ifAvailablePreCheck(); return this.deps.http.post( `/internal/content_management/favorites/${this.favoriteObjectType}/${id}/unfavorite` ); diff --git a/packages/content-management/favorites/favorites_public/tsconfig.json b/packages/content-management/favorites/favorites_public/tsconfig.json index 3057c828fc3da..aa36c38cebab3 100644 --- a/packages/content-management/favorites/favorites_public/tsconfig.json +++ b/packages/content-management/favorites/favorites_public/tsconfig.json @@ -23,5 +23,6 @@ "@kbn/content-management-favorites-server", "@kbn/i18n-react", "@kbn/usage-collection-plugin", + "@kbn/core-user-profile-browser", ] } diff --git a/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx b/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx index cf28019f820d0..7dcff19fdecb5 100644 --- a/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx +++ b/packages/content-management/table_list_view_table/src/__jest__/tests.helpers.tsx @@ -29,7 +29,7 @@ export const getMockServices = (overrides?: Partial '', getTagIdsFromReferences: () => [], isTaggingEnabled: () => true, - isFavoritesEnabled: () => false, + isFavoritesEnabled: () => Promise.resolve(false), bulkGetUserProfiles: async () => [], getUserProfile: async () => ({ uid: '', enabled: true, data: {}, user: { username: '' } }), isKibanaVersioningEnabled: false, diff --git a/packages/content-management/table_list_view_table/src/mocks.tsx b/packages/content-management/table_list_view_table/src/mocks.tsx index 6f3ffda91ddf6..1cd7bbcef07a7 100644 --- a/packages/content-management/table_list_view_table/src/mocks.tsx +++ b/packages/content-management/table_list_view_table/src/mocks.tsx @@ -75,7 +75,7 @@ export const getStoryServices = (params: Params, action: ActionFn = () => {}) => getTagManagementUrl: () => '', getTagIdsFromReferences: () => [], isTaggingEnabled: () => true, - isFavoritesEnabled: () => false, + isFavoritesEnabled: () => Promise.resolve(false), isKibanaVersioningEnabled: false, ...params, }; diff --git a/packages/content-management/table_list_view_table/src/services.tsx b/packages/content-management/table_list_view_table/src/services.tsx index 9db14069107e8..7ae90d29a74ba 100644 --- a/packages/content-management/table_list_view_table/src/services.tsx +++ b/packages/content-management/table_list_view_table/src/services.tsx @@ -73,7 +73,7 @@ export interface Services { /** Predicate to indicate if tagging features is enabled */ isTaggingEnabled: () => boolean; /** Predicate to indicate if favorites features is enabled */ - isFavoritesEnabled: () => boolean; + isFavoritesEnabled: () => Promise; /** Predicate function to indicate if some of the saved object references are tags */ itemHasTags: (references: SavedObjectsReference[]) => boolean; /** Handler to return the url to navigate to the kibana tags management */ @@ -288,7 +288,7 @@ export const TableListViewKibanaProvider: FC< currentAppId$={application.currentAppId$} navigateToUrl={application.navigateToUrl} isTaggingEnabled={() => Boolean(savedObjectsTagging)} - isFavoritesEnabled={() => Boolean(services.favorites)} + isFavoritesEnabled={async () => services.favorites?.isAvailable() ?? false} getTagList={getTagList} TagList={TagList} itemHasTags={itemHasTags} diff --git a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx index 011f00256d979..7a5356f75eb2b 100644 --- a/packages/content-management/table_list_view_table/src/table_list_view_table.tsx +++ b/packages/content-management/table_list_view_table/src/table_list_view_table.tsx @@ -9,6 +9,7 @@ import React, { useReducer, useCallback, useEffect, useRef, useMemo } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; +import useAsync from 'react-use/lib/useAsync'; import { EuiBasicTableColumn, EuiButton, @@ -379,6 +380,8 @@ function TableListViewTableComp({ isKibanaVersioningEnabled, } = useServices(); + const favoritesEnabled = useAsync(isFavoritesEnabled, [])?.value ?? false; + const openContentEditor = useOpenContentEditor(); const contentInsightsServices = useContentInsightsServices(); @@ -621,7 +624,7 @@ function TableListViewTableComp({ } }} searchTerm={searchQuery.text} - isFavoritesEnabled={isFavoritesEnabled()} + isFavoritesEnabled={favoritesEnabled} /> ); }, @@ -754,7 +757,7 @@ function TableListViewTableComp({ tableItemsRowActions, inspectItem, entityName, - isFavoritesEnabled, + favoritesEnabled, isKibanaVersioningEnabled, ]); @@ -1218,7 +1221,7 @@ function TableListViewTableComp({ addOrRemoveExcludeTagFilter={addOrRemoveExcludeTagFilter} clearTagSelection={clearTagSelection} createdByEnabled={createdByEnabled} - favoritesEnabled={isFavoritesEnabled()} + favoritesEnabled={favoritesEnabled} /> {/* Delete modal */} diff --git a/packages/core/notifications/core-notifications-browser-internal/src/toasts/__snapshots__/error_toast.test.tsx.snap b/packages/core/notifications/core-notifications-browser-internal/src/toasts/__snapshots__/error_toast.test.tsx.snap index 73b165848162e..2838bd2e7e1a1 100644 --- a/packages/core/notifications/core-notifications-browser-internal/src/toasts/__snapshots__/error_toast.test.tsx.snap +++ b/packages/core/notifications/core-notifications-browser-internal/src/toasts/__snapshots__/error_toast.test.tsx.snap @@ -33,6 +33,7 @@ exports[`renders matching snapshot 1`] = ` Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], diff --git a/packages/core/notifications/core-notifications-browser-internal/src/toasts/__snapshots__/toasts_service.test.tsx.snap b/packages/core/notifications/core-notifications-browser-internal/src/toasts/__snapshots__/toasts_service.test.tsx.snap index 4a2e14cf9fcec..5e3e3eb8f42e6 100644 --- a/packages/core/notifications/core-notifications-browser-internal/src/toasts/__snapshots__/toasts_service.test.tsx.snap +++ b/packages/core/notifications/core-notifications-browser-internal/src/toasts/__snapshots__/toasts_service.test.tsx.snap @@ -35,6 +35,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], diff --git a/packages/core/overlays/core-overlays-browser-internal/src/flyout/__snapshots__/flyout_service.test.tsx.snap b/packages/core/overlays/core-overlays-browser-internal/src/flyout/__snapshots__/flyout_service.test.tsx.snap index 192c892b0128f..faa75edaa3374 100644 --- a/packages/core/overlays/core-overlays-browser-internal/src/flyout/__snapshots__/flyout_service.test.tsx.snap +++ b/packages/core/overlays/core-overlays-browser-internal/src/flyout/__snapshots__/flyout_service.test.tsx.snap @@ -43,6 +43,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -183,6 +184,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -316,6 +318,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], diff --git a/packages/core/overlays/core-overlays-browser-internal/src/modal/__snapshots__/modal_service.test.tsx.snap b/packages/core/overlays/core-overlays-browser-internal/src/modal/__snapshots__/modal_service.test.tsx.snap index c0e94fc9fb2d6..2a4c80431669a 100644 --- a/packages/core/overlays/core-overlays-browser-internal/src/modal/__snapshots__/modal_service.test.tsx.snap +++ b/packages/core/overlays/core-overlays-browser-internal/src/modal/__snapshots__/modal_service.test.tsx.snap @@ -125,6 +125,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -424,6 +425,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -788,6 +790,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -1071,6 +1074,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -1359,6 +1363,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -1642,6 +1647,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -1698,6 +1704,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -1869,6 +1876,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -2002,6 +2010,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -2140,6 +2149,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], @@ -2273,6 +2283,7 @@ Array [ Object { "bulkGet": [MockFunction], "getCurrent": [MockFunction], + "getEnabled$": [MockFunction], "getUserProfile$": [MockFunction], "partialUpdate": [MockFunction], "suggest": [MockFunction], diff --git a/packages/core/user-profile/core-user-profile-browser-internal/src/utils/convert_api.test.ts b/packages/core/user-profile/core-user-profile-browser-internal/src/utils/convert_api.test.ts index 5783a8f40d9cf..cf8e79007ceb3 100644 --- a/packages/core/user-profile/core-user-profile-browser-internal/src/utils/convert_api.test.ts +++ b/packages/core/user-profile/core-user-profile-browser-internal/src/utils/convert_api.test.ts @@ -19,6 +19,7 @@ describe('convertUserProfileAPI', () => { beforeEach(() => { source = { userProfile$: of(null), + enabled$: of(false), getCurrent: jest.fn(), bulkGet: jest.fn(), suggest: jest.fn(), @@ -34,6 +35,12 @@ describe('convertUserProfileAPI', () => { }); }); + describe('getEnabled$', () => { + it('returns the observable from the source', () => { + expect(output.getEnabled$()).toBe(source.enabled$); + }); + }); + describe('getCurrent', () => { it('calls the API from the source with the correct parameters', () => { output.getCurrent(); diff --git a/packages/core/user-profile/core-user-profile-browser-internal/src/utils/convert_api.ts b/packages/core/user-profile/core-user-profile-browser-internal/src/utils/convert_api.ts index a307ca2ec9460..462ae99c32b11 100644 --- a/packages/core/user-profile/core-user-profile-browser-internal/src/utils/convert_api.ts +++ b/packages/core/user-profile/core-user-profile-browser-internal/src/utils/convert_api.ts @@ -15,6 +15,7 @@ export const convertUserProfileAPI = ( ): InternalUserProfileServiceStart => { return { getUserProfile$: () => delegate.userProfile$, + getEnabled$: () => delegate.enabled$, getCurrent: delegate.getCurrent.bind(delegate), bulkGet: delegate.bulkGet.bind(delegate), suggest: delegate.suggest.bind(delegate), diff --git a/packages/core/user-profile/core-user-profile-browser-internal/src/utils/default_implementation.ts b/packages/core/user-profile/core-user-profile-browser-internal/src/utils/default_implementation.ts index 19febd2d17e71..2a1a1e650c4fd 100644 --- a/packages/core/user-profile/core-user-profile-browser-internal/src/utils/default_implementation.ts +++ b/packages/core/user-profile/core-user-profile-browser-internal/src/utils/default_implementation.ts @@ -17,6 +17,7 @@ import { UserProfileData } from '@kbn/core-user-profile-common'; export const getDefaultUserProfileImplementation = (): CoreUserProfileDelegateContract => { return { userProfile$: of(null), + enabled$: of(false), getCurrent: () => Promise.resolve(null as unknown as GetUserProfileResponse), bulkGet: () => Promise.resolve([]), diff --git a/packages/core/user-profile/core-user-profile-browser-mocks/src/user_profile_service.mock.ts b/packages/core/user-profile/core-user-profile-browser-mocks/src/user_profile_service.mock.ts index 8f86d8ac6555f..ba9842d55b765 100644 --- a/packages/core/user-profile/core-user-profile-browser-mocks/src/user_profile_service.mock.ts +++ b/packages/core/user-profile/core-user-profile-browser-mocks/src/user_profile_service.mock.ts @@ -28,6 +28,7 @@ const createSetupMock = () => { const createStartMock = () => { const mock: jest.Mocked = { getUserProfile$: jest.fn().mockReturnValue(of(null)), + getEnabled$: jest.fn().mockReturnValue(of(false)), getCurrent: jest.fn(), bulkGet: jest.fn(), suggest: jest.fn(), @@ -49,6 +50,7 @@ const createInternalSetupMock = () => { const createInternalStartMock = () => { const mock: jest.Mocked = { getUserProfile$: jest.fn().mockReturnValue(of(null)), + getEnabled$: jest.fn().mockReturnValue(of(false)), getCurrent: jest.fn(), bulkGet: jest.fn(), suggest: jest.fn(), diff --git a/packages/core/user-profile/core-user-profile-browser/src/api_provider.ts b/packages/core/user-profile/core-user-profile-browser/src/api_provider.ts index 0206af155a4cf..841a00d0d561e 100644 --- a/packages/core/user-profile/core-user-profile-browser/src/api_provider.ts +++ b/packages/core/user-profile/core-user-profile-browser/src/api_provider.ts @@ -11,6 +11,10 @@ import type { Observable } from 'rxjs'; import type { UserProfileData } from '@kbn/core-user-profile-common'; import type { UserProfileService } from './service'; -export type CoreUserProfileDelegateContract = Omit & { +export type CoreUserProfileDelegateContract = Omit< + UserProfileService, + 'getUserProfile$' | 'getEnabled$' +> & { userProfile$: Observable; + enabled$: Observable; }; diff --git a/packages/core/user-profile/core-user-profile-browser/src/service.ts b/packages/core/user-profile/core-user-profile-browser/src/service.ts index 766b1ad9d5cc8..d39fc84a57f0f 100644 --- a/packages/core/user-profile/core-user-profile-browser/src/service.ts +++ b/packages/core/user-profile/core-user-profile-browser/src/service.ts @@ -17,10 +17,13 @@ import type { export interface UserProfileService { /** - * Retrieve an observable emitting when the user profile is loaded. + * Retrieve an observable emitting the current user profile data. */ getUserProfile$(): Observable; + /** Flag to indicate if the current user has a user profile. Anonymous users don't have user profiles. */ + getEnabled$(): Observable; + /** * Retrieves the user profile of the current user. If the profile isn't available, e.g. for the anonymous users or * users authenticated via authenticating proxies, the `null` value is returned. diff --git a/src/platform/packages/private/kbn-esql-editor/src/editor_footer/esql_starred_queries_service.test.tsx b/src/platform/packages/private/kbn-esql-editor/src/editor_footer/esql_starred_queries_service.test.tsx index fca4d95c6f6cb..558705048f5ef 100644 --- a/src/platform/packages/private/kbn-esql-editor/src/editor_footer/esql_starred_queries_service.test.tsx +++ b/src/platform/packages/private/kbn-esql-editor/src/editor_footer/esql_starred_queries_service.test.tsx @@ -10,6 +10,7 @@ import { EsqlStarredQueriesService } from './esql_starred_queries_service'; import { coreMock } from '@kbn/core/public/mocks'; import type { Storage } from '@kbn/kibana-utils-plugin/public'; +import { BehaviorSubject } from 'rxjs'; class LocalStorageMock { public store: Record; @@ -34,20 +35,36 @@ describe('EsqlStarredQueriesService', () => { const core = coreMock.createStart(); const storage = new LocalStorageMock({}) as unknown as Storage; - it('should initialize', async () => { + const isUserProfileEnabled$ = new BehaviorSubject(true); + jest.spyOn(core.userProfile, 'getEnabled$').mockImplementation(() => isUserProfileEnabled$); + + beforeEach(() => { + isUserProfileEnabled$.next(true); + }); + + const initialize = async () => { const service = await EsqlStarredQueriesService.initialize({ http: core.http, + userProfile: core.userProfile, storage, }); + return service!; + }; + + it('should return null if favorites service not available', async () => { + isUserProfileEnabled$.next(false); + const service = await initialize(); + expect(service).toBeNull(); + }); + + it('should initialize', async () => { + const service = await initialize(); expect(service).toBeDefined(); expect(service.queries$.value).toEqual([]); }); it('should add a new starred query', async () => { - const service = await EsqlStarredQueriesService.initialize({ - http: core.http, - storage, - }); + const service = await initialize(); const query = { queryString: 'SELECT * FROM test', timeRan: '2021-09-01T00:00:00Z', @@ -66,10 +83,7 @@ describe('EsqlStarredQueriesService', () => { }); it('should not add the same query twice', async () => { - const service = await EsqlStarredQueriesService.initialize({ - http: core.http, - storage, - }); + const service = await initialize(); const query = { queryString: 'SELECT * FROM test', timeRan: '2021-09-01T00:00:00Z', @@ -94,10 +108,7 @@ describe('EsqlStarredQueriesService', () => { }); it('should add the query trimmed', async () => { - const service = await EsqlStarredQueriesService.initialize({ - http: core.http, - storage, - }); + const service = await initialize(); const query = { queryString: `SELECT * FROM test | WHERE field != 'value'`, @@ -118,10 +129,7 @@ describe('EsqlStarredQueriesService', () => { }); it('should remove a query', async () => { - const service = await EsqlStarredQueriesService.initialize({ - http: core.http, - storage, - }); + const service = await initialize(); const query = { queryString: `SELECT * FROM test | WHERE field != 'value'`, timeRan: '2021-09-01T00:00:00Z', @@ -144,10 +152,7 @@ describe('EsqlStarredQueriesService', () => { }); it('should return the button correctly', async () => { - const service = await EsqlStarredQueriesService.initialize({ - http: core.http, - storage, - }); + const service = await initialize(); const query = { queryString: 'SELECT * FROM test', timeRan: '2021-09-01T00:00:00Z', @@ -162,10 +167,7 @@ describe('EsqlStarredQueriesService', () => { }); it('should display the modal when the Remove button is clicked', async () => { - const service = await EsqlStarredQueriesService.initialize({ - http: core.http, - storage, - }); + const service = await initialize(); const query = { queryString: 'SELECT * FROM test', timeRan: '2021-09-01T00:00:00Z', @@ -183,10 +185,7 @@ describe('EsqlStarredQueriesService', () => { it('should NOT display the modal when Remove the button is clicked but the user has dismissed the modal permanently', async () => { storage.set('esqlEditor.starredQueriesDiscard', true); - const service = await EsqlStarredQueriesService.initialize({ - http: core.http, - storage, - }); + const service = await initialize(); const query = { queryString: 'SELECT * FROM test', timeRan: '2021-09-01T00:00:00Z', diff --git a/src/platform/packages/private/kbn-esql-editor/src/editor_footer/esql_starred_queries_service.tsx b/src/platform/packages/private/kbn-esql-editor/src/editor_footer/esql_starred_queries_service.tsx index 80ef716cfd4b0..ad271ad588acc 100644 --- a/src/platform/packages/private/kbn-esql-editor/src/editor_footer/esql_starred_queries_service.tsx +++ b/src/platform/packages/private/kbn-esql-editor/src/editor_footer/esql_starred_queries_service.tsx @@ -43,6 +43,7 @@ export interface StarredQueryItem extends QueryHistoryItem { interface EsqlStarredQueriesServices { http: CoreStart['http']; + userProfile: CoreStart['userProfile']; storage: Storage; usageCollection?: UsageCollectionStart; } @@ -81,9 +82,13 @@ export class EsqlStarredQueriesService { static async initialize(services: EsqlStarredQueriesServices) { const client = new FavoritesClient('esql_editor', 'esql_query', { http: services.http, + userProfile: services.userProfile, usageCollection: services.usageCollection, }); + const isAvailable = await client.isAvailable(); + if (!isAvailable) return null; + const { favoriteMetadata } = (await client?.getFavorites()) || {}; const retrievedQueries: StarredQueryItem[] = []; diff --git a/src/platform/packages/private/kbn-esql-editor/src/editor_footer/history_starred_queries.test.tsx b/src/platform/packages/private/kbn-esql-editor/src/editor_footer/history_starred_queries.test.tsx index 9e0d586622c31..ba00e7eae6359 100644 --- a/src/platform/packages/private/kbn-esql-editor/src/editor_footer/history_starred_queries.test.tsx +++ b/src/platform/packages/private/kbn-esql-editor/src/editor_footer/history_starred_queries.test.tsx @@ -10,13 +10,14 @@ import React from 'react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { coreMock } from '@kbn/core/public/mocks'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { QueryHistoryAction, getTableColumns, QueryColumn, HistoryAndStarredQueriesTabs, } from './history_starred_queries'; +import { of } from 'rxjs'; jest.mock('../history_local_storage', () => { const module = jest.requireActual('../history_local_storage'); @@ -218,6 +219,7 @@ describe('Starred and History queries components', () => { const services = { core: coreMock.createStart(), }; + it('should render two tabs', () => { render( @@ -271,5 +273,30 @@ describe('Starred and History queries components', () => { 'Showing 0 queries (max 100)' ); }); + + it('should hide starred tab if starred service failed to initialize', async () => { + jest.spyOn(services.core.userProfile, 'getEnabled$').mockImplementation(() => of(false)); + + render( + + + + ); + + // initial render two tabs are shown + expect(screen.getByTestId('history-queries-tab')).toBeInTheDocument(); + expect(screen.getByTestId('history-queries-tab')).toHaveTextContent('Recent'); + expect(screen.getByTestId('starred-queries-tab')).toBeInTheDocument(); + expect(screen.getByTestId('starred-queries-tab')).toHaveTextContent('Starred'); + + await waitFor(() => { + expect(screen.queryByText('starred-queries-tab')).not.toBeInTheDocument(); + }); + }); }); }); diff --git a/src/platform/packages/private/kbn-esql-editor/src/editor_footer/history_starred_queries.tsx b/src/platform/packages/private/kbn-esql-editor/src/editor_footer/history_starred_queries.tsx index c24d0a0b1817b..64039a6063b5f 100644 --- a/src/platform/packages/private/kbn-esql-editor/src/editor_footer/history_starred_queries.tsx +++ b/src/platform/packages/private/kbn-esql-editor/src/editor_footer/history_starred_queries.tsx @@ -470,22 +470,30 @@ export function HistoryAndStarredQueriesTabs({ const kibana = useKibana(); const { core, usageCollection, storage } = kibana.services; - const [starredQueriesService, setStarredQueriesService] = useState(); + const [starredQueriesService, setStarredQueriesService] = useState< + EsqlStarredQueriesService | null | undefined + >(); const [starredQueries, setStarredQueries] = useState([]); useEffect(() => { const initializeService = async () => { const starredService = await EsqlStarredQueriesService.initialize({ http: core.http, + userProfile: core.userProfile, usageCollection, storage, }); - setStarredQueriesService(starredService); + + if (starredService) { + setStarredQueriesService(starredService); + } else { + setStarredQueriesService(null); + } }; if (!starredQueriesService) { initializeService(); } - }, [core.http, starredQueriesService, storage, usageCollection]); + }, [core.http, core.userProfile, starredQueriesService, storage, usageCollection]); starredQueriesService?.queries$.subscribe((nextQueries) => { if (nextQueries.length !== starredQueries.length) { @@ -495,7 +503,11 @@ export function HistoryAndStarredQueriesTabs({ const { euiTheme } = useEuiTheme(); const tabs = useMemo(() => { - return [ + // use typed helper instead of .filter directly to remove falsy values from result type + function filterMissing(array: Array): T[] { + return array.filter((item): item is T => item !== undefined); + } + return filterMissing([ { id: 'history-queries-tab', name: i18n.translate('esqlEditor.query.historyQueriesTabLabel', { @@ -513,11 +525,11 @@ export function HistoryAndStarredQueriesTabs({ tableCaption={i18n.translate('esqlEditor.query.querieshistoryTable', { defaultMessage: 'Queries history table', })} - starredQueriesService={starredQueriesService} + starredQueriesService={starredQueriesService ?? undefined} /> ), }, - { + starredQueriesService !== null && { id: 'starred-queries-tab', dataTestSubj: 'starred-queries-tab', name: i18n.translate('esqlEditor.query.starredQueriesTabLabel', { @@ -539,12 +551,12 @@ export function HistoryAndStarredQueriesTabs({ tableCaption={i18n.translate('esqlEditor.query.starredQueriesTable', { defaultMessage: 'Starred queries table', })} - starredQueriesService={starredQueriesService} + starredQueriesService={starredQueriesService ?? undefined} isStarredTab={true} /> ), }, - ]; + ]); }, [ containerCSS, containerWidth, diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_listing/dashboard_listing.tsx b/src/platform/plugins/shared/dashboard/public/dashboard_listing/dashboard_listing.tsx index 7a8e5a40bb274..b50f977672675 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_listing/dashboard_listing.tsx +++ b/src/platform/plugins/shared/dashboard/public/dashboard_listing/dashboard_listing.tsx @@ -54,6 +54,7 @@ export const DashboardListing = ({ return new FavoritesClient(DASHBOARD_APP_ID, DASHBOARD_CONTENT_ID, { http: coreServices.http, usageCollection: usageCollectionService, + userProfile: coreServices.userProfile, }); }, []); diff --git a/x-pack/packages/security/plugin_types_public/src/user_profile/user_profile_api_client.ts b/x-pack/packages/security/plugin_types_public/src/user_profile/user_profile_api_client.ts index 862bcdccf942e..4118d1b189793 100644 --- a/x-pack/packages/security/plugin_types_public/src/user_profile/user_profile_api_client.ts +++ b/x-pack/packages/security/plugin_types_public/src/user_profile/user_profile_api_client.ts @@ -8,7 +8,6 @@ import type { Observable } from 'rxjs'; import type { CoreUserProfileDelegateContract } from '@kbn/core-user-profile-browser'; -import type { UserProfileData } from '@kbn/core-user-profile-common'; export type { GetUserProfileResponse, @@ -18,13 +17,10 @@ export type { } from '@kbn/core-user-profile-browser'; export type UserProfileAPIClient = CoreUserProfileDelegateContract & { - readonly userProfile$: Observable; /** * Indicates if the user profile data has been loaded from the server. * Useful to distinguish between the case when the user profile data is `null` because the HTTP * request has not finished or because there is no user profile data for the current user. */ readonly userProfileLoaded$: Observable; - /** Flag to indicate if the current user has a user profile. Anonymous users don't have user profiles. */ - readonly enabled$: Observable; }; diff --git a/x-pack/packages/security/plugin_types_public/tsconfig.json b/x-pack/packages/security/plugin_types_public/tsconfig.json index 6779851e86367..c65330102c02d 100644 --- a/x-pack/packages/security/plugin_types_public/tsconfig.json +++ b/x-pack/packages/security/plugin_types_public/tsconfig.json @@ -11,7 +11,6 @@ ], "kbn_references": [ "@kbn/core-user-profile-browser", - "@kbn/core-user-profile-common", "@kbn/security-plugin-types-common", "@kbn/core-security-common", ]