From 2f8c6f4a683d5405e9770059ddd54cce3a676782 Mon Sep 17 00:00:00 2001 From: Derek Ho Date: Fri, 19 Jul 2024 10:23:40 -0400 Subject: [PATCH] Conform to Navigation changes from OSD core (#2022) * Adopt to nav changes in core Signed-off-by: Derek Ho * Lint Signed-off-by: Derek Ho * feat: enable data source info in new left navigation Signed-off-by: SuZhou-Joe * feat: enable data source info in new left navigation Signed-off-by: SuZhou-Joe * Address PR feedback Signed-off-by: Derek Ho * Lint Signed-off-by: Derek Ho * Fix lint and tests Signed-off-by: Derek Ho --------- Signed-off-by: Derek Ho Signed-off-by: SuZhou-Joe Co-authored-by: SuZhou-Joe --- public/apps/configuration/app-router.tsx | 25 ++- .../apps/configuration/configuration-app.tsx | 2 + .../panels/tenant-list/configure_tab1.tsx | 2 +- .../panels/tenant-list/manage_tab.tsx | 3 +- .../panels/tenant-list/tenant-list.tsx | 2 +- .../tenant-list/test/tenant-list.test.tsx | 3 + .../__snapshots__/app-router.test.tsx.snap | 1 - .../configuration/test/app-router.test.tsx | 4 + public/apps/configuration/top-nav-menu.tsx | 6 +- public/apps/types.ts | 1 + public/plugin.ts | 163 +++++++++++++++++- public/utils/datasource-utils.ts | 19 +- 12 files changed, 209 insertions(+), 22 deletions(-) diff --git a/public/apps/configuration/app-router.tsx b/public/apps/configuration/app-router.tsx index 9164bbc29..f0dc0cae2 100644 --- a/public/apps/configuration/app-router.tsx +++ b/public/apps/configuration/app-router.tsx @@ -40,11 +40,11 @@ import { Action, RouteItem, SubAction } from './types'; import { ResourceType } from '../../../common'; import { buildHashUrl, buildUrl } from './utils/url-builder'; import { CrossPageToast } from './cross-page-toast'; -import { getDataSourceFromUrl } from '../../utils/datasource-utils'; +import { getDataSourceFromUrl, LocalCluster } from '../../utils/datasource-utils'; const LANDING_PAGE_URL = '/getstarted'; -const ROUTE_MAP: { [key: string]: RouteItem } = { +export const ROUTE_MAP: { [key: string]: RouteItem } = { getStarted: { name: 'Get Started', href: LANDING_PAGE_URL, @@ -145,8 +145,6 @@ export interface DataSourceContextType { setDataSource: React.Dispatch>; } -export const LocalCluster = { label: 'Local cluster', id: '' }; - export const DataSourceContext = createContext(null); export function AppRouter(props: AppDependencies) { @@ -161,14 +159,15 @@ export function AppRouter(props: AppDependencies) { - {allNavPanelUrls(multitenancyEnabled).map((route) => ( - // Create different routes to update the 'selected' nav item . - - - - - - ))} + {!props.coreStart.chrome.navGroup.getNavGroupEnabled() && + allNavPanelUrls(multitenancyEnabled).map((route) => ( + // Create different routes to update the 'selected' nav item . + + + + + + ))} )} - + diff --git a/public/apps/configuration/configuration-app.tsx b/public/apps/configuration/configuration-app.tsx index 1f341f781..e6c681921 100644 --- a/public/apps/configuration/configuration-app.tsx +++ b/public/apps/configuration/configuration-app.tsx @@ -28,6 +28,7 @@ export function renderApp( depsStart: SecurityPluginStartDependencies, params: AppMountParameters, config: ClientConfigType, + redirect: string, dataSourceManagement?: DataSourceManagementPluginSetup ) { const deps = { @@ -36,6 +37,7 @@ export function renderApp( params, config, dataSourceManagement, + redirect, }; ReactDOM.render( diff --git a/public/apps/configuration/panels/tenant-list/configure_tab1.tsx b/public/apps/configuration/panels/tenant-list/configure_tab1.tsx index b38f0a098..770496d85 100644 --- a/public/apps/configuration/panels/tenant-list/configure_tab1.tsx +++ b/public/apps/configuration/panels/tenant-list/configure_tab1.tsx @@ -52,7 +52,7 @@ import { import { getDashboardsInfo } from '../../../../utils/dashboards-info-utils'; import { LOCAL_CLUSTER_ID } from '../../../../../common'; import { AccessErrorComponent } from '../../access-error-component'; -import { LocalCluster } from '../../app-router'; +import { LocalCluster } from '../../../../utils/datasource-utils'; export function ConfigureTab1(props: AppDependencies) { const [isMultiTenancyEnabled, setIsMultiTenancyEnabled] = useState(false); diff --git a/public/apps/configuration/panels/tenant-list/manage_tab.tsx b/public/apps/configuration/panels/tenant-list/manage_tab.tsx index 544aff698..ab30bf0ee 100644 --- a/public/apps/configuration/panels/tenant-list/manage_tab.tsx +++ b/public/apps/configuration/panels/tenant-list/manage_tab.tsx @@ -69,7 +69,8 @@ import { useContextMenuState } from '../../utils/context-menu'; import { generateResourceName } from '../../utils/resource-utils'; import { DocLinks } from '../../constants'; import { TenantList } from './tenant-list'; -import { LocalCluster, getBreadcrumbs } from '../../app-router'; +import { getBreadcrumbs } from '../../app-router'; +import { LocalCluster } from '../../../../utils/datasource-utils'; import { buildUrl } from '../../utils/url-builder'; import { CrossPageToast } from '../../cross-page-toast'; import { getDashboardsInfo } from '../../../../utils/dashboards-info-utils'; diff --git a/public/apps/configuration/panels/tenant-list/tenant-list.tsx b/public/apps/configuration/panels/tenant-list/tenant-list.tsx index c624adcbe..cebfb4981 100644 --- a/public/apps/configuration/panels/tenant-list/tenant-list.tsx +++ b/public/apps/configuration/panels/tenant-list/tenant-list.tsx @@ -30,7 +30,7 @@ import { AppDependencies } from '../../../types'; import { ExternalLink } from '../../utils/display-utils'; import { DocLinks } from '../../constants'; import { getDashboardsInfo } from '../../../../utils/dashboards-info-utils'; -import { LocalCluster } from '../../app-router'; +import { LocalCluster } from '../../../../utils/datasource-utils'; import { SecurityPluginTopNavMenu } from '../../top-nav-menu'; interface TenantListProps extends AppDependencies { diff --git a/public/apps/configuration/panels/tenant-list/test/tenant-list.test.tsx b/public/apps/configuration/panels/tenant-list/test/tenant-list.test.tsx index ca4081fce..66117c398 100644 --- a/public/apps/configuration/panels/tenant-list/test/tenant-list.test.tsx +++ b/public/apps/configuration/panels/tenant-list/test/tenant-list.test.tsx @@ -64,6 +64,9 @@ describe('Tenant list', () => { setBreadcrumbs() { return 1; }, + navGroup: { + getNavGroupEnabled: jest.fn().mockReturnValue(false), + }, }, }; const config = { diff --git a/public/apps/configuration/test/__snapshots__/app-router.test.tsx.snap b/public/apps/configuration/test/__snapshots__/app-router.test.tsx.snap index 21aa4e515..d5ee503be 100644 --- a/public/apps/configuration/test/__snapshots__/app-router.test.tsx.snap +++ b/public/apps/configuration/test/__snapshots__/app-router.test.tsx.snap @@ -495,7 +495,6 @@ exports[`SecurityPluginTopNavMenu renders DataSourceMenu when dataSource is enab diff --git a/public/apps/configuration/test/app-router.test.tsx b/public/apps/configuration/test/app-router.test.tsx index 540f44614..4bb61888d 100644 --- a/public/apps/configuration/test/app-router.test.tsx +++ b/public/apps/configuration/test/app-router.test.tsx @@ -20,6 +20,7 @@ import { getDataSourceFromUrl } from '../../../utils/datasource-utils'; jest.mock('../../../utils/datasource-utils', () => ({ getDataSourceFromUrl: jest.fn(), + LocalCluster: { id: '', label: 'Local cluster' }, })); describe('SecurityPluginTopNavMenu', () => { @@ -30,6 +31,9 @@ describe('SecurityPluginTopNavMenu', () => { notifications: jest.fn(), chrome: { setBreadcrumbs: jest.fn(), + navGroup: { + getNavGroupEnabled: jest.fn().mockReturnValue(false), + }, }, }; diff --git a/public/apps/configuration/top-nav-menu.tsx b/public/apps/configuration/top-nav-menu.tsx index 5e5e51893..20197d69a 100644 --- a/public/apps/configuration/top-nav-menu.tsx +++ b/public/apps/configuration/top-nav-menu.tsx @@ -17,7 +17,10 @@ import React from 'react'; import { DataSourceSelectableConfig } from 'src/plugins/data_source_management/public'; import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types'; import { AppDependencies } from '../types'; -import { setDataSourceInUrl } from '../../utils/datasource-utils'; +import { + setDataSourceInUrl, + setDataSource as setDataSourceInSubscription, +} from '../../utils/datasource-utils'; export interface TopNavMenuProps extends AppDependencies { dataSourcePickerReadOnly: boolean; @@ -44,6 +47,7 @@ export const SecurityPluginTopNavMenu = React.memo( const wrapSetDataSourceWithUpdateUrl = (dataSources: DataSourceOption[]) => { setDataSourceInUrl(dataSources[0]); setDataSource(dataSources[0]); + setDataSourceInSubscription(dataSources[0]); }; return dataSourceEnabled ? ( diff --git a/public/apps/types.ts b/public/apps/types.ts index 276f1ce07..755431c33 100644 --- a/public/apps/types.ts +++ b/public/apps/types.ts @@ -24,6 +24,7 @@ export interface AppDependencies { config: ClientConfigType; dashboardsInfo: DashboardsInfo; dataSourceManagement: DataSourceManagementPluginSetup; + redirect?: string; } export interface BreadcrumbsPageDependencies extends AppDependencies { diff --git a/public/plugin.ts b/public/plugin.ts index 75a983ab9..fb71c9c88 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -17,14 +17,18 @@ import { BehaviorSubject } from 'rxjs'; import { SavedObjectsManagementColumn } from 'src/plugins/saved_objects_management/public'; import { i18n } from '@osd/i18n'; import { + AppCategory, AppMountParameters, + AppNavLinkStatus, AppStatus, AppUpdater, CoreSetup, CoreStart, DEFAULT_APP_CATEGORIES, + DEFAULT_NAV_GROUPS, Plugin, PluginInitializerContext, + WorkspaceAvailability, } from '../../../src/core/public'; import { APP_ID_LOGIN, CUSTOM_ERROR_PAGE_URI, LOGIN_PAGE_URI, PLUGIN_NAME } from '../common'; import { APP_ID_CUSTOMERROR } from '../common'; @@ -50,6 +54,11 @@ import { addTenantToShareURL } from './services/shared-link'; import { interceptError } from './utils/logout-utils'; import { tenantColumn, getNamespacesToRegister } from './apps/configuration/utils/tenant-utils'; import { getDashboardsInfoSafe } from './utils/dashboards-info-utils'; +import { + dataSource$, + getDataSourceEnabledUrl, + getDataSourceFromUrl, +} from './utils/datasource-utils'; async function hasApiPermission(core: CoreSetup): Promise { try { @@ -68,8 +77,13 @@ const APP_ID_DASHBOARDS = 'dashboards'; // OpenSearchDashboards app is for legacy url migration const APP_ID_OPENSEARCH_DASHBOARDS = 'kibana'; const APP_LIST_FOR_READONLY_ROLE = [APP_ID_HOME, APP_ID_DASHBOARDS, APP_ID_OPENSEARCH_DASHBOARDS]; -const GLOBAL_TENANT_RENDERING_TEXT = 'Global'; -const PRIVATE_TENANT_RENDERING_TEXT = 'Private'; + +const dataAccessUsersCategory: AppCategory & { group?: AppCategory } = { + id: 'dataAccessAndUsers', + label: 'Data Access and Users', + order: 9000, + euiIconType: 'managementApp', +}; export class SecurityPlugin implements @@ -82,6 +96,15 @@ export class SecurityPlugin // @ts-ignore : initializerContext not used constructor(private readonly initializerContext: PluginInitializerContext) {} + private updateDefaultRouteOfSecurityApplications: AppUpdater = () => { + const url = getDataSourceEnabledUrl(getDataSourceFromUrl()); + return { + defaultPath: `?${url.searchParams.toString()}`, + }; + }; + + private appStateUpdater = new BehaviorSubject(this.updateDefaultRouteOfSecurityApplications); + public async setup( core: CoreSetup, deps: SecurityPluginSetupDependencies @@ -97,11 +120,36 @@ export class SecurityPlugin (config.readonly_mode?.roles || DEFAULT_READONLY_ROLES).includes(role) ); + const mountWrapper = async (params: AppMountParameters, redirect: string) => { + const { renderApp } = await import('./apps/configuration/configuration-app'); + const [coreStart, depsStart] = await core.getStartServices(); + + // merge OpenSearchDashboards yml configuration + includeClusterPermissions(config.clusterPermissions.include); + includeIndexPermissions(config.indexPermissions.include); + + excludeFromDisabledTransportCategories(config.disabledTransportCategories.exclude); + excludeFromDisabledRestCategories(config.disabledRestCategories.exclude); + + return renderApp( + coreStart, + depsStart as SecurityPluginStartDependencies, + params, + config, + redirect, + deps.dataSourceManagement + ); + }; + if (mdsEnabled || apiPermission) { core.application.register({ id: PLUGIN_NAME, title: 'Security', order: 9050, + workspaceAvailability: WorkspaceAvailability.outsideWorkspace, + navLinkStatus: core.chrome.navGroup.getNavGroupEnabled() + ? AppNavLinkStatus.hidden + : AppNavLinkStatus.visible, mount: async (params: AppMountParameters) => { const { renderApp } = await import('./apps/configuration/configuration-app'); const [coreStart, depsStart] = await core.getStartServices(); @@ -118,12 +166,117 @@ export class SecurityPlugin depsStart as SecurityPluginStartDependencies, params, config, + '/getstarted', deps.dataSourceManagement ); }, category: DEFAULT_APP_CATEGORIES.management, }); + if (core.chrome.navGroup.getNavGroupEnabled()) { + core.application.register({ + id: `security-dashboards-plugin_getstarted`, + title: 'Get Started', + order: 8040, + workspaceAvailability: WorkspaceAvailability.outsideWorkspace, + updater$: this.appStateUpdater, + mount: async (params: AppMountParameters) => { + return mountWrapper(params, '/getstarted'); + }, + }); + core.application.register({ + id: `security-dashboards-plugin_auth`, + title: 'Authentication', + order: 8040, + workspaceAvailability: WorkspaceAvailability.outsideWorkspace, + updater$: this.appStateUpdater, + mount: async (params: AppMountParameters) => { + return mountWrapper(params, '/auth'); + }, + }); + core.application.register({ + id: `security-dashboards-plugin_roles`, + title: 'Roles', + order: 8040, + workspaceAvailability: WorkspaceAvailability.outsideWorkspace, + updater$: this.appStateUpdater, + mount: async (params: AppMountParameters) => { + return mountWrapper(params, '/roles'); + }, + }); + core.application.register({ + id: `security-dashboards-plugin_users`, + title: 'Internal users', + order: 8040, + workspaceAvailability: WorkspaceAvailability.outsideWorkspace, + updater$: this.appStateUpdater, + mount: async (params: AppMountParameters) => { + return mountWrapper(params, '/users'); + }, + }); + core.application.register({ + id: `security-dashboards-plugin_permissions`, + title: 'Permissions', + order: 8040, + workspaceAvailability: WorkspaceAvailability.outsideWorkspace, + updater$: this.appStateUpdater, + mount: async (params: AppMountParameters) => { + return mountWrapper(params, '/permissions'); + }, + }); + core.application.register({ + id: `security-dashboards-plugin_tenants`, + title: 'Tenants', + order: 8040, + workspaceAvailability: WorkspaceAvailability.outsideWorkspace, + updater$: this.appStateUpdater, + mount: async (params: AppMountParameters) => { + return mountWrapper(params, '/tenants'); + }, + }); + core.application.register({ + id: `security-dashboards-plugin_auditlog`, + title: 'Audit logs', + order: 8040, + workspaceAvailability: WorkspaceAvailability.outsideWorkspace, + updater$: this.appStateUpdater, + mount: async (params: AppMountParameters) => { + return mountWrapper(params, '/auditLogging'); + }, + }); + } + + core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.dataAdministration, [ + { + id: `security-dashboards-plugin_getstarted`, + category: dataAccessUsersCategory, + }, + { + id: `security-dashboards-plugin_auth`, + category: dataAccessUsersCategory, + }, + { + id: `security-dashboards-plugin_roles`, + category: dataAccessUsersCategory, + }, + { + id: `security-dashboards-plugin_users`, + category: dataAccessUsersCategory, + }, + { + id: `security-dashboards-plugin_permissions`, + category: dataAccessUsersCategory, + }, + { + id: `security-dashboards-plugin_tenants`, + category: dataAccessUsersCategory, + }, + { + id: `security-dashboards-plugin_auditlog`, + category: dataAccessUsersCategory, + }, + ]); + if (deps.managementOverview) { deps.managementOverview.register({ id: PLUGIN_NAME, @@ -135,6 +288,12 @@ export class SecurityPlugin }), }); } + + dataSource$.subscribe((dataSourceOption) => { + if (dataSourceOption) { + this.appStateUpdater.next(this.updateDefaultRouteOfSecurityApplications); + } + }); } core.application.register({ diff --git a/public/utils/datasource-utils.ts b/public/utils/datasource-utils.ts index 638c515eb..508a72a78 100644 --- a/public/utils/datasource-utils.ts +++ b/public/utils/datasource-utils.ts @@ -13,6 +13,7 @@ * permissions and limitations under the License. */ +import { BehaviorSubject } from 'rxjs'; import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types'; const DATASOURCEURLKEY = 'dataSource'; @@ -35,8 +36,22 @@ export function getDataSourceFromUrl(): DataSourceOption { } } -export function setDataSourceInUrl(dataSource: DataSourceOption) { +export function getDataSourceEnabledUrl(dataSource: DataSourceOption) { const url = new URL(window.location.href); url.searchParams.set(DATASOURCEURLKEY, JSON.stringify(dataSource)); - window.history.replaceState({}, '', url.toString()); + return url; +} + +export function setDataSourceInUrl(dataSource: DataSourceOption) { + window.history.replaceState({}, '', getDataSourceEnabledUrl(dataSource).toString()); +} + +export const LocalCluster = { label: 'Local cluster', id: '' }; + +export const dataSource$ = new BehaviorSubject( + getDataSourceFromUrl() || LocalCluster +); + +export function setDataSource(dataSource: DataSourceOption) { + dataSource$.next(dataSource); }