Skip to content

Commit eedca2b

Browse files
authored
Merge branch 'main' into fix-entity-store-api-list
2 parents 1dad5fb + 51c4f00 commit eedca2b

33 files changed

Lines changed: 659 additions & 397 deletions

File tree

src/platform/packages/shared/kbn-cps-utils/components/project_picker.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ describe('ProjectPicker', () => {
103103
});
104104

105105
describe('projectRouting change events', () => {
106-
it('should call onProjectRoutingChange with ALL when "All projects" is clicked', async () => {
106+
it('should call onProjectRoutingChange with PROJECT_ROUTING.ALL when "All projects" is clicked', async () => {
107107
const onProjectRoutingChange = jest.fn();
108108
await renderProjectPicker({
109109
projectRouting: PROJECT_ROUTING.ORIGIN,

src/platform/packages/shared/kbn-cps-utils/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*/
1414
export const PROJECT_ROUTING = {
1515
/** Search across all linked projects */
16-
ALL: 'ALL',
16+
ALL: '_alias:*',
1717
/** Search only the origin project */
1818
ORIGIN: '_alias:_origin',
1919
} as const;

src/platform/packages/shared/kbn-cps-utils/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,5 @@ export interface ICPSManager {
5050
getProjectRouting(): ProjectRouting | undefined;
5151
getDefaultProjectRouting(): ProjectRouting;
5252
getProjectPickerAccess$(): Observable<ProjectRoutingAccess>;
53+
getProjectPickerAccess(): ProjectRoutingAccess;
5354
}

src/platform/packages/shared/kbn-lens-common-2/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type {
2222
PublishesViewMode,
2323
PublishesWritableDescription,
2424
PublishesWritableTitle,
25+
PublishesUnsavedChanges,
2526
} from '@kbn/presentation-publishing';
2627
import type { LensApiSchemaType } from '@kbn/lens-embeddable-utils';
2728
import type { Simplify } from '@kbn/chart-expressions-common';
@@ -95,6 +96,8 @@ export type LensApi = Simplify<
9596
PublishesViewMode &
9697
// Let the container know the saved object id
9798
PublishesSavedObjectId &
99+
// Let the container know about unsaved changes
100+
PublishesUnsavedChanges &
98101
PublishesProjectRoutingOverrides &
99102
// Lens specific API methods:
100103
// Let the container know when the data has been loaded/updated

src/platform/packages/shared/kbn-lens-common/embeddable/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,8 @@ export type LensInternalApi = Simplify<
394394
updateBlockingError: (newBlockingError: Error | undefined) => void;
395395
resetAllMessages: () => void;
396396
getDisplayOptions: () => VisualizationDisplayOptions;
397+
updateEditingState: (inProgress: boolean) => void;
398+
isEditingInProgress: () => boolean;
397399
}
398400
>;
399401

src/platform/plugins/private/vis_types/vega/public/data_model/search_api.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,4 @@ describe('SearchAPI projectRouting', () => {
138138
test('should not include project_routing in ES params when projectRouting is undefined', (done) => {
139139
testProjectRouting(undefined, undefined, done);
140140
});
141-
142-
test('should sanitize ALL projectRouting value', (done) => {
143-
testProjectRouting('ALL', undefined, done);
144-
});
145141
});

src/platform/plugins/shared/cps/public/services/access_control.test.ts

Lines changed: 14 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@
88
*/
99

1010
import { ProjectRoutingAccess } from '@kbn/cps-utils';
11-
import {
12-
getProjectRoutingAccess,
13-
DEFAULT_ACCESS_CONTROL_CONFIG,
14-
type AccessControlConfig,
15-
} from './access_control';
11+
import { ACCESS_CONTROL_CONFIG, getProjectRoutingAccess } from './access_control';
1612

1713
describe('Access Control Configuration', () => {
1814
describe('getProjectRoutingAccess', () => {
@@ -65,85 +61,33 @@ describe('Access Control Configuration', () => {
6561
});
6662
});
6763

68-
describe('with custom configuration', () => {
69-
const customConfig: AccessControlConfig = {
70-
myApp: {
71-
defaultAccess: ProjectRoutingAccess.EDITABLE,
72-
},
73-
customApp: {
74-
defaultAccess: ProjectRoutingAccess.DISABLED,
75-
routeRules: [
76-
{
77-
pattern: /^#\/special/,
78-
access: ProjectRoutingAccess.EDITABLE,
79-
},
80-
],
81-
},
82-
};
83-
84-
it('should use custom app configuration', () => {
85-
expect(getProjectRoutingAccess('myApp', '#/anything', customConfig)).toBe(
86-
ProjectRoutingAccess.EDITABLE
87-
);
88-
});
89-
90-
it('should match route rules before defaultAccess', () => {
91-
expect(getProjectRoutingAccess('customApp', '#/special/page', customConfig)).toBe(
64+
describe('route rule priority', () => {
65+
it('should check rules in order and match pattern before defaulting', () => {
66+
// Rule matches: type:vega pattern -> EDITABLE (overrides DISABLED default)
67+
expect(getProjectRoutingAccess('visualize', '#/edit/123?type:vega')).toBe(
9268
ProjectRoutingAccess.EDITABLE
9369
);
94-
expect(getProjectRoutingAccess('customApp', '#/normal/page', customConfig)).toBe(
70+
// No rule match: falls back to defaultAccess -> DISABLED
71+
expect(getProjectRoutingAccess('visualize', '#/edit/456?type:lens')).toBe(
9572
ProjectRoutingAccess.DISABLED
9673
);
97-
});
98-
99-
it('should return DISABLED for unconfigured apps', () => {
100-
expect(getProjectRoutingAccess('unknownApp', '#/', customConfig)).toBe(
101-
ProjectRoutingAccess.DISABLED
102-
);
103-
});
104-
});
105-
106-
describe('route rule priority', () => {
107-
const config: AccessControlConfig = {
108-
testApp: {
109-
defaultAccess: ProjectRoutingAccess.DISABLED,
110-
routeRules: [
111-
{
112-
pattern: /^#\/admin/,
113-
access: ProjectRoutingAccess.READONLY,
114-
},
115-
{
116-
pattern: /^#\/edit/,
117-
access: ProjectRoutingAccess.EDITABLE,
118-
},
119-
],
120-
},
121-
};
122-
123-
it('should check rules in order', () => {
124-
expect(getProjectRoutingAccess('testApp', '#/admin/settings', config)).toBe(
125-
ProjectRoutingAccess.READONLY
126-
);
127-
expect(getProjectRoutingAccess('testApp', '#/edit/document', config)).toBe(
128-
ProjectRoutingAccess.EDITABLE
129-
);
130-
expect(getProjectRoutingAccess('testApp', '#/view/document', config)).toBe(
74+
expect(getProjectRoutingAccess('visualize', '#/create')).toBe(
13175
ProjectRoutingAccess.DISABLED
13276
);
13377
});
13478
});
13579
});
13680

137-
describe('DEFAULT_ACCESS_CONTROL_CONFIG', () => {
81+
describe('ACCESS_CONTROL_CONFIG', () => {
13882
it('should have configuration for expected apps', () => {
139-
expect(DEFAULT_ACCESS_CONTROL_CONFIG).toHaveProperty('dashboards');
140-
expect(DEFAULT_ACCESS_CONTROL_CONFIG).toHaveProperty('discover');
141-
expect(DEFAULT_ACCESS_CONTROL_CONFIG).toHaveProperty('visualize');
142-
expect(DEFAULT_ACCESS_CONTROL_CONFIG).toHaveProperty('lens');
83+
expect(ACCESS_CONTROL_CONFIG).toHaveProperty('dashboards');
84+
expect(ACCESS_CONTROL_CONFIG).toHaveProperty('discover');
85+
expect(ACCESS_CONTROL_CONFIG).toHaveProperty('visualize');
86+
expect(ACCESS_CONTROL_CONFIG).toHaveProperty('lens');
14387
});
14488

14589
it('should have route rules for dashboards', () => {
146-
expect(DEFAULT_ACCESS_CONTROL_CONFIG.dashboards.routeRules).toHaveLength(1);
90+
expect(ACCESS_CONTROL_CONFIG.dashboards.routeRules).toHaveLength(1);
14791
});
14892
});
14993
});

src/platform/plugins/shared/cps/public/services/access_control.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,8 @@ export type AccessControlConfig = Record<string, AppAccessConfig>;
4545
* - READONLY: View-only mode - shows current scope but prevents changes
4646
* - DISABLED: Picker is completely disabled
4747
*
48-
* Default Configuration:
49-
*
50-
* | App | Route | Access Level | Notes |
51-
* |------------|------------------------|--------------|-------------------------------------|
52-
* | dashboards | all routes except list | EDITABLE | All dashboard pages except listing |
53-
* | dashboards | #/list | DISABLED | List page only |
54-
* | discover | all routes | EDITABLE | All discover pages |
55-
* | visualize | type:vega in route | EDITABLE | Vega visualizations only |
56-
* | visualize | other routes | DISABLED | Other visualization types |
57-
* | lens | all routes | EDITABLE | All lens pages |
58-
* | maps | all routes | EDITABLE | All maps pages
59-
* | all others | all routes | DISABLED | Default behavior for unknown apps |
6048
*/
61-
export const DEFAULT_ACCESS_CONTROL_CONFIG: AccessControlConfig = {
49+
export const ACCESS_CONTROL_CONFIG: AccessControlConfig = {
6250
dashboards: {
6351
defaultAccess: ProjectRoutingAccess.EDITABLE,
6452
routeRules: [
@@ -93,10 +81,9 @@ export const DEFAULT_ACCESS_CONTROL_CONFIG: AccessControlConfig = {
9381
*/
9482
export const getProjectRoutingAccess = (
9583
currentAppId: string,
96-
hash: string,
97-
config: AccessControlConfig = DEFAULT_ACCESS_CONTROL_CONFIG
84+
hash: string
9885
): ProjectRoutingAccess => {
99-
const appConfig = config[currentAppId];
86+
const appConfig = ACCESS_CONTROL_CONFIG[currentAppId];
10087
if (!appConfig) {
10188
return ProjectRoutingAccess.DISABLED;
10289
}

src/platform/plugins/shared/cps/public/services/cps_manager.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010
import type { ApplicationStart, HttpSetup } from '@kbn/core/public';
1111
import type { Logger } from '@kbn/logging';
1212
import type { ProjectRouting } from '@kbn/es-query';
13-
import { BehaviorSubject, combineLatest, switchMap, tap } from 'rxjs';
14-
import { type ICPSManager, type ProjectsData, PROJECT_ROUTING } from '@kbn/cps-utils';
13+
import { BehaviorSubject, combineLatest, switchMap } from 'rxjs';
14+
import {
15+
type ICPSManager,
16+
type ProjectsData,
17+
PROJECT_ROUTING,
18+
ProjectRoutingAccess,
19+
} from '@kbn/cps-utils';
1520
import type { ProjectFetcher } from './project_fetcher';
1621

1722
/**
@@ -35,34 +40,31 @@ export class CPSManager implements ICPSManager {
3540
private readonly projectRouting$ = new BehaviorSubject<ProjectRouting | undefined>(
3641
DEFAULT_PROJECT_ROUTING
3742
);
38-
private readonly projectPickerAccess$;
39-
private readonly projectPickerAccessValue$ = new BehaviorSubject<string>('editable');
43+
private readonly projectPickerAccess$ = new BehaviorSubject<ProjectRoutingAccess>(
44+
ProjectRoutingAccess.EDITABLE
45+
);
4046

4147
constructor(deps: { http: HttpSetup; logger: Logger; application: ApplicationStart }) {
4248
this.http = deps.http;
4349
this.logger = deps.logger.get('cps_manager');
4450
this.application = deps.application;
4551

46-
this.projectPickerAccess$ = combineLatest([
47-
this.application.currentAppId$,
48-
this.application.currentLocation$,
49-
]).pipe(
50-
switchMap(async ([appId, location]) => {
51-
// Lazy load access control only when observable is subscribed
52-
const { getProjectRoutingAccess } = await import('./async_services');
53-
return getProjectRoutingAccess(appId ?? '', location ?? '');
54-
}),
55-
tap((access) => {
56-
this.projectPickerAccessValue$.next(access);
52+
combineLatest([this.application.currentAppId$, this.application.currentLocation$])
53+
.pipe(
54+
switchMap(async ([appId, location]) => {
55+
return (await import('./async_services')).getProjectRoutingAccess(
56+
appId ?? '',
57+
location ?? ''
58+
);
59+
})
60+
)
61+
.subscribe((access) => {
62+
this.projectPickerAccess$.next(access);
5763
// Reset project routing to default when access is disabled
58-
if (access === 'disabled') {
64+
if (access === ProjectRoutingAccess.DISABLED) {
5965
this.projectRouting$.next(DEFAULT_PROJECT_ROUTING);
6066
}
61-
})
62-
);
63-
64-
// Subscribe to keep the value updated
65-
this.projectPickerAccess$.subscribe();
67+
});
6668
}
6769

6870
/**
@@ -75,22 +77,22 @@ export class CPSManager implements ICPSManager {
7577
/**
7678
* Set the current project routing
7779
*/
78-
public setProjectRouting(projectRouting: ProjectRouting | undefined) {
80+
public setProjectRouting(projectRouting: ProjectRouting) {
7981
this.projectRouting$.next(projectRouting);
8082
}
8183

8284
/**
8385
* Get the current project routing value
8486
*/
8587
public getProjectRouting() {
86-
if (this.projectPickerAccessValue$.value === 'disabled') {
88+
if (this.projectPickerAccess$.value === ProjectRoutingAccess.DISABLED) {
8789
return undefined;
8890
}
8991
return this.projectRouting$.value;
9092
}
9193

9294
/**
93-
* Get the default project routing value.
95+
* Get the default project routing value from a global space setting.
9496
* This is the fallback value used when no app-specific or saved value exists.
9597
*/
9698
public getDefaultProjectRouting(): ProjectRouting {
@@ -101,12 +103,18 @@ export class CPSManager implements ICPSManager {
101103
* Get the project picker access level as an observable.
102104
* This combines the current app ID and location to determine whether
103105
* the project picker should be editable, readonly, or disabled.
104-
* Also returns the custom readonly message if applicable.
105106
*/
106107
public getProjectPickerAccess$() {
107108
return this.projectPickerAccess$;
108109
}
109110

111+
/**
112+
* Get the current project picker access value
113+
*/
114+
public getProjectPickerAccess() {
115+
return this.projectPickerAccess$.value;
116+
}
117+
110118
/**
111119
* Fetches projects from the server with caching and retry logic.
112120
* Returns cached data if already loaded. If a fetch is already in progress, returns the existing promise.

0 commit comments

Comments
 (0)