Skip to content

Commit

Permalink
Adds page headers for updated UX (#2083) (#2092)
Browse files Browse the repository at this point in the history
* Adds initial commit to add header components and modifies get started and user list pages



* Updates auth-view page



* Updates user edit and create pages



* Updates user permissions page



* Updates dashboards tenancy page



* Updates dashboards audit logs page



* Updates roles and related pages



* Updates variable name and fixes indentation



* Push logic into new component



* Lint



* Migrate audit logging and tenant tabs to new page header



* Migrate all tabs to new component



* Remove prop and fix some test failures



* fix most tests



* Fix existing tests



* Push breadcrumb population into child components



* Migrate breadcrumbs into the page header component for the top level of all pages



* Lint



* Update all instances of breadcrumbs to new component and all existing tests pass



* Fixes target _blank redirects



* Fixes unit tests



* Add tests for new component and breadcrumb function



* Address PR feedback



* Update tests and snapshots



---------




(cherry picked from commit dc79df3)

Signed-off-by: Darshit Chanpura <[email protected]>
Signed-off-by: Derek Ho <[email protected]>
Co-authored-by: Darshit Chanpura <[email protected]>
  • Loading branch information
derek-ho and DarshitChanpura authored Aug 19, 2024
1 parent 80976be commit e78ba0d
Show file tree
Hide file tree
Showing 43 changed files with 1,899 additions and 568 deletions.
78 changes: 16 additions & 62 deletions public/apps/configuration/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
* permissions and limitations under the License.
*/

import { EuiBreadcrumb, EuiPage, EuiPageBody, EuiPageSideBar } from '@elastic/eui';
import { flow, partial } from 'lodash';
import { EuiPage, EuiPageBody, EuiPageSideBar } from '@elastic/eui';
import { flow } from 'lodash';
import React, { createContext, useState } from 'react';
import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom';
import { DataSourceOption } from 'src/plugins/data_source_management/public/components/data_source_menu/types';
Expand All @@ -38,31 +38,37 @@ import { TenantList } from './panels/tenant-list/tenant-list';
import { UserList } from './panels/user-list';
import { Action, RouteItem, SubAction } from './types';
import { ResourceType } from '../../../common';
import { buildHashUrl, buildUrl } from './utils/url-builder';
import { buildUrl } from './utils/url-builder';
import { CrossPageToast } from './cross-page-toast';
import { getDataSourceFromUrl, LocalCluster } from '../../utils/datasource-utils';
import { getBreadcrumbs } from './utils/resource-utils';

const LANDING_PAGE_URL = '/getstarted';

export const ROUTE_MAP: { [key: string]: RouteItem } = {
getStarted: {
name: 'Get Started',
breadCrumbDisplayNameWithoutSecurityBase: 'Get started with access control',
href: LANDING_PAGE_URL,
},
[ResourceType.roles]: {
name: 'Roles',
breadCrumbDisplayNameWithoutSecurityBase: 'Roles',
href: buildUrl(ResourceType.roles),
},
[ResourceType.users]: {
name: 'Internal users',
breadCrumbDisplayNameWithoutSecurityBase: 'Internal users',
href: buildUrl(ResourceType.users),
},
[ResourceType.permissions]: {
name: 'Permissions',
breadCrumbDisplayNameWithoutSecurityBase: 'Permissions',
href: buildUrl(ResourceType.permissions),
},
[ResourceType.tenants]: {
name: 'Tenants',
breadCrumbDisplayNameWithoutSecurityBase: 'Dashboard multi-tenancy',
href: buildUrl(ResourceType.tenants),
},
[ResourceType.tenantsConfigureTab]: {
Expand All @@ -71,10 +77,12 @@ export const ROUTE_MAP: { [key: string]: RouteItem } = {
},
[ResourceType.auth]: {
name: 'Authentication',
breadCrumbDisplayNameWithoutSecurityBase: 'Authentication and authorization',
href: buildUrl(ResourceType.auth),
},
[ResourceType.auditLogging]: {
name: 'Audit logs',
breadCrumbDisplayNameWithoutSecurityBase: 'Audit logs',
href: buildUrl(ResourceType.auditLogging),
},
};
Expand All @@ -100,39 +108,6 @@ export const allNavPanelUrls = (multitenancyEnabled: boolean) =>
...(multitenancyEnabled ? [buildUrl(ResourceType.tenantsConfigureTab)] : []),
]);

export function getBreadcrumbs(
resourceType?: ResourceType,
pageTitle?: string,
subAction?: string
): EuiBreadcrumb[] {
const breadcrumbs: EuiBreadcrumb[] = [
{
text: 'Security',
href: buildHashUrl(),
},
];

if (resourceType) {
breadcrumbs.push({
text: ROUTE_MAP[resourceType].name,
href: buildHashUrl(resourceType),
});
}

if (pageTitle) {
breadcrumbs.push({
text: pageTitle,
});
}

if (subAction) {
breadcrumbs.push({
text: subAction,
});
}
return breadcrumbs;
}

function decodeParams(params: { [k: string]: string }): any {
return Object.keys(params).reduce((obj: { [k: string]: string }, key: string) => {
obj[key] = decodeURIComponent(params[key]);
Expand All @@ -154,6 +129,7 @@ export function AppRouter(props: AppDependencies) {
const dataSourceFromUrl = dataSourceEnabled ? getDataSourceFromUrl() : LocalCluster;

const [dataSource, setDataSource] = useState<DataSourceOption>(dataSourceFromUrl);
const includeSecurityBase = !props.coreStart.uiSettings.get('home:useNewHomePage');

return (
<DataSourceContext.Provider value={{ dataSource, setDataSource }}>
Expand All @@ -173,100 +149,79 @@ export function AppRouter(props: AppDependencies) {
<Route
path={buildUrl(ResourceType.roles, Action.edit) + '/:roleName/' + SubAction.mapuser}
render={(match) => (
<RoleEditMappedUser
buildBreadcrumbs={partial(setGlobalBreadcrumbs, ResourceType.roles)}
{...{ ...props, ...decodeParams(match.match.params) }}
/>
<RoleEditMappedUser {...{ ...props, ...decodeParams(match.match.params) }} />
)}
/>
<Route
path={buildUrl(ResourceType.roles, Action.view) + '/:roleName/:prevAction?'}
render={(match) => (
<RoleView
buildBreadcrumbs={partial(setGlobalBreadcrumbs, ResourceType.roles)}
{...{ ...props, ...decodeParams(match.match.params) }}
/>
<RoleView {...{ ...props, ...decodeParams(match.match.params) }} />
)}
/>
<Route
path={buildUrl(ResourceType.roles) + '/:action/:sourceRoleName?'}
render={(match) => (
<RoleEdit
buildBreadcrumbs={partial(setGlobalBreadcrumbs, ResourceType.roles)}
{...{ ...props, ...decodeParams(match.match.params) }}
/>
<RoleEdit {...{ ...props, ...decodeParams(match.match.params) }} />
)}
/>
<Route
path={ROUTE_MAP.roles.href}
render={() => {
setGlobalBreadcrumbs(ResourceType.roles);
return <RoleList {...props} />;
}}
/>
<Route
path={ROUTE_MAP.auth.href}
render={() => {
setGlobalBreadcrumbs(ResourceType.auth);
return <AuthView {...props} />;
}}
/>
<Route
path={buildUrl(ResourceType.users) + '/:action/:sourceUserName?'}
render={(match) => (
<InternalUserEdit
buildBreadcrumbs={partial(setGlobalBreadcrumbs, ResourceType.users)}
{...{ ...props, ...decodeParams(match.match.params) }}
/>
<InternalUserEdit {...{ ...props, ...decodeParams(match.match.params) }} />
)}
/>
<Route
path={ROUTE_MAP.users.href}
render={() => {
setGlobalBreadcrumbs(ResourceType.users);
return <UserList {...props} />;
}}
/>
<Route
path={buildUrl(ResourceType.auditLogging) + SUB_URL_FOR_GENERAL_SETTINGS_EDIT}
render={() => {
setGlobalBreadcrumbs(ResourceType.auditLogging, 'General settings');
return <AuditLoggingEditSettings setting={'general'} {...props} />;
}}
/>
<Route
path={buildUrl(ResourceType.auditLogging) + SUB_URL_FOR_COMPLIANCE_SETTINGS_EDIT}
render={() => {
setGlobalBreadcrumbs(ResourceType.auditLogging, 'Compliance settings');
return <AuditLoggingEditSettings setting={'compliance'} {...props} />;
}}
/>
<Route
path={ROUTE_MAP.auditLogging.href + '/:fromType?'}
render={(match) => {
setGlobalBreadcrumbs(ResourceType.auditLogging);
return <AuditLogging {...{ ...props, ...match.match.params }} />;
}}
/>
<Route
path={ROUTE_MAP.permissions.href}
render={() => {
setGlobalBreadcrumbs(ResourceType.permissions);
return <PermissionList {...props} />;
}}
/>
<Route
path={ROUTE_MAP.getStarted.href}
render={() => {
setGlobalBreadcrumbs();
return <GetStarted {...props} />;
}}
/>
{multitenancyEnabled && (
<Route
path={ROUTE_MAP.tenants.href}
render={() => {
setGlobalBreadcrumbs(ResourceType.tenants);
return <TenantList tabID={'Manage'} {...props} />;
}}
/>
Expand All @@ -275,7 +230,6 @@ export function AppRouter(props: AppDependencies) {
<Route
path={ROUTE_MAP.tenantsConfigureTab.href}
render={() => {
setGlobalBreadcrumbs(ResourceType.tenants);
return <TenantList tabID={'Configure'} {...props} />;
}}
/>
Expand Down
62 changes: 62 additions & 0 deletions public/apps/configuration/header/header-components.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright OpenSearch Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import React from 'react';
import { flow } from 'lodash';
import { ControlProps, DescriptionProps, HeaderProps } from './header-props';
import { getBreadcrumbs } from '../utils/resource-utils';

export const HeaderButtonOrLink = React.memo((props: HeaderProps & ControlProps) => {
const { HeaderControl } = props.navigation.ui;

return (
<HeaderControl
setMountPoint={props.coreStart.application.setAppRightControls}
controls={props.appRightControls}
/>
);
});

export const PageHeader = (props: HeaderProps & DescriptionProps & ControlProps) => {
const { HeaderControl } = props.navigation.ui; // need to get this from SecurityPluginStartDependencies
const useNewUx = props.coreStart.uiSettings.get('home:useNewHomePage');
flow(getBreadcrumbs, props.coreStart.chrome.setBreadcrumbs)(
!useNewUx,
props.resourceType,
props.pageTitle,
props.subAction,
props.count
);
if (useNewUx) {
return (
<>
{props.descriptionControls ? (
<HeaderControl
setMountPoint={props.coreStart.application.setAppDescriptionControls}
controls={props.descriptionControls}
/>
) : null}
{props.appRightControls ? (
<HeaderControl
setMountPoint={props.coreStart.application.setAppRightControls}
controls={props.appRightControls}
/>
) : null}
</>
);
} else {
return props.fallBackComponent;
}
};
37 changes: 37 additions & 0 deletions public/apps/configuration/header/header-props.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright OpenSearch Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import { CoreStart } from 'opensearch-dashboards/public';
import { NavigationPublicPluginStart } from 'src/plugins/navigation/public';
import { TopNavControlData } from 'src/plugins/navigation/public/top_nav_menu/top_nav_control_data';
import { ResourceType } from '../../../../common';

export interface HeaderProps {
navigation: NavigationPublicPluginStart;
coreStart: CoreStart;
fallBackComponent: JSX.Element;
resourceType?: ResourceType;
pageTitle?: string;
subAction?: string;
count?: number;
}

export interface ControlProps {
appRightControls?: TopNavControlData[];
}

export interface DescriptionProps {
descriptionControls?: TopNavControlData[];
}
Loading

0 comments on commit e78ba0d

Please sign in to comment.