33 * SPDX-License-Identifier: Apache-2.0
44 */
55
6- import { AppNavLinkStatus , NavGroupType , PublicAppInfo } from '../../../core/public' ;
6+ import { BehaviorSubject } from 'rxjs' ;
7+ import { AppNavLinkStatus , AppStatus , NavGroupType , PublicAppInfo } from '../../../core/public' ;
78import {
89 featureMatchesConfig ,
910 filterWorkspaceConfigurableApps ,
@@ -12,6 +13,7 @@ import {
1213 getDataSourcesList ,
1314 convertNavGroupToWorkspaceUseCase ,
1415 isEqualWorkspaceUseCase ,
16+ pickUseCaseLandingAppId ,
1517 prependWorkspaceToBreadcrumbs ,
1618 mergeDataSourcesWithConnections ,
1719 fetchDataSourceConnections ,
@@ -967,6 +969,81 @@ describe('workspace utils: mergeDataSourcesWithConnections', () => {
967969 } ) ;
968970} ) ;
969971
972+ describe ( 'workspace utils: pickUseCaseLandingAppId' , ( ) => {
973+ const accessibleVisible = ( {
974+ status : AppStatus . accessible ,
975+ navLinkStatus : AppNavLinkStatus . default ,
976+ } as Partial < PublicAppInfo > ) as PublicAppInfo ;
977+ const featureFlagDisabled = ( {
978+ status : AppStatus . accessible ,
979+ navLinkStatus : AppNavLinkStatus . hidden ,
980+ } as Partial < PublicAppInfo > ) as PublicAppInfo ;
981+ const outsideWorkspaceHidden = ( {
982+ // Mirrors the state read on the workspace creator page for an
983+ // `insideWorkspace`-only app: workspace plugin pushed `inaccessible`,
984+ // some other path pushed `navLinkStatus: hidden` — both flip back
985+ // once the user enters the workspace, so the picker must keep them.
986+ status : AppStatus . inaccessible ,
987+ navLinkStatus : AppNavLinkStatus . hidden ,
988+ } as Partial < PublicAppInfo > ) as PublicAppInfo ;
989+
990+ it ( 'returns undefined when the use case has no features' , ( ) => {
991+ expect ( pickUseCaseLandingAppId ( undefined , new Map ( ) ) ) . toBeUndefined ( ) ;
992+ expect ( pickUseCaseLandingAppId ( [ ] , new Map ( ) ) ) . toBeUndefined ( ) ;
993+ } ) ;
994+
995+ it ( 'falls back to features[0] when no apps snapshot is provided' , ( ) => {
996+ expect ( pickUseCaseLandingAppId ( [ { id : 'first' } , { id : 'second' } ] , undefined ) ) . toBe ( 'first' ) ;
997+ } ) ;
998+
999+ it ( 'skips feature-flag-disabled apps (hidden + accessible)' , ( ) => {
1000+ const apps = new Map < string , PublicAppInfo > ( [
1001+ [ 'alerting' , featureFlagDisabled ] ,
1002+ [ 'dashboards' , accessibleVisible ] ,
1003+ ] ) ;
1004+ expect ( pickUseCaseLandingAppId ( [ { id : 'alerting' } , { id : 'dashboards' } ] , apps ) ) . toBe (
1005+ 'dashboards'
1006+ ) ;
1007+ } ) ;
1008+
1009+ it ( 'keeps apps that are transiently hidden outside a workspace (hidden + inaccessible)' , ( ) => {
1010+ // Repro of the bug we shipped this for: workspace creator runs outside
1011+ // any workspace, so `insideWorkspace` apps look hidden — but we must
1012+ // still pick the first one as the landing target, because it'll be
1013+ // accessible immediately after the redirect.
1014+ const apps = new Map < string , PublicAppInfo > ( [
1015+ [ 'dashboards' , outsideWorkspaceHidden ] ,
1016+ [ 'explore/logs' , outsideWorkspaceHidden ] ,
1017+ ] ) ;
1018+ expect ( pickUseCaseLandingAppId ( [ { id : 'dashboards' } , { id : 'explore/logs' } ] , apps ) ) . toBe (
1019+ 'dashboards'
1020+ ) ;
1021+ } ) ;
1022+
1023+ it ( 'falls back to features[0] when every feature is feature-flag-disabled' , ( ) => {
1024+ const apps = new Map < string , PublicAppInfo > ( [
1025+ [ 'a' , featureFlagDisabled ] ,
1026+ [ 'b' , featureFlagDisabled ] ,
1027+ ] ) ;
1028+ expect ( pickUseCaseLandingAppId ( [ { id : 'a' } , { id : 'b' } ] , apps ) ) . toBe ( 'a' ) ;
1029+ } ) ;
1030+
1031+ it ( 'treats a feature id absent from the apps map as selectable' , ( ) => {
1032+ // Load-bearing for the transient-load case: feature ids come from
1033+ // `convertNavGroupToWorkspaceUseCase` over real nav links, so an
1034+ // absent lookup means the apps snapshot hasn't propagated yet, not
1035+ // that the app is missing. Skipping such features would silently
1036+ // skip the entire list during early page load.
1037+ const apps = new Map < string , PublicAppInfo > ( [ [ 'known-feature-flag-off' , featureFlagDisabled ] ] ) ;
1038+ expect (
1039+ pickUseCaseLandingAppId (
1040+ [ { id : 'known-feature-flag-off' } , { id : 'not-yet-in-apps-map' } ] ,
1041+ apps
1042+ )
1043+ ) . toBe ( 'not-yet-in-apps-map' ) ;
1044+ } ) ;
1045+ } ) ;
1046+
9701047describe ( 'workspace utils: getUseCaseUrl' , ( ) => {
9711048 it ( 'should get use case url' , ( ) => {
9721049 startMock . application . getUrlForApp . mockImplementation ( ( id ) => `http://localhost/${ id } ` ) ;
@@ -979,6 +1056,32 @@ describe('workspace utils: getUseCaseUrl', () => {
9791056 const url = getUseCaseUrl ( undefined , 'foo' , startMock . application , startMock . http ) ;
9801057 expect ( url ) . toEqual ( 'http://localhost/w/foo/workspace_detail' ) ;
9811058 } ) ;
1059+
1060+ it ( 'should skip feature-flag-disabled features when picking the landing app' , ( ) => {
1061+ startMock . application . getUrlForApp . mockImplementation ( ( id ) => `http://localhost/${ id } ` ) ;
1062+ ( startMock . application . applications$ as BehaviorSubject < Map < string , PublicAppInfo > > ) . next (
1063+ new Map < string , PublicAppInfo > ( [
1064+ // `bar` is the first feature of `useCaseMock`. Flag it off so the
1065+ // picker has to fall through to the next feature.
1066+ [
1067+ 'bar' ,
1068+ ( {
1069+ status : AppStatus . accessible ,
1070+ navLinkStatus : AppNavLinkStatus . hidden ,
1071+ } as Partial < PublicAppInfo > ) as PublicAppInfo ,
1072+ ] ,
1073+ [
1074+ 'baz' ,
1075+ ( {
1076+ status : AppStatus . accessible ,
1077+ navLinkStatus : AppNavLinkStatus . default ,
1078+ } as Partial < PublicAppInfo > ) as PublicAppInfo ,
1079+ ] ,
1080+ ] )
1081+ ) ;
1082+ const url = getUseCaseUrl ( useCaseMock , 'foo' , startMock . application , startMock . http ) ;
1083+ expect ( url ) . toEqual ( 'http://localhost/w/foo/baz' ) ;
1084+ } ) ;
9821085} ) ;
9831086
9841087describe ( 'workspace utils: fetchDataSourceConnections' , ( ) => {
0 commit comments