Refactoring based on recent feedback

Signed-off-by: Andre Wanlin <awanlin@rapidrtc.com>
This commit is contained in:
Andre Wanlin
2022-03-26 14:07:01 -05:00
parent f00ebdf678
commit a7423888d1
13 changed files with 150 additions and 59 deletions
+52
View File
@@ -4,3 +4,55 @@
- Show Group Page
- Show User Profile
- Quick access to Groups
### Group Page
Here's an example of what the Group Page looks like:
![Group Page example](./docs/group-page-example.png)
### User Profile
Here's an example of what the User Profile looks like:
![Group Page example](./docs/user-profile-example.png)
### MyGroupsSidebarItem
The MyGroupsSidebarItem provides quick access to the group(s) the logged in user is a member of directly in the sidebar.
To use the MyGroupsSidebarItem you'll need to add it to your `Root.tsx` - found at `packages\app\src\components\Root` - like this:
```diff
+ import { MyGroupsSidebarItem } from '@backstage/plugin-org';
+ import GroupIcon from '@material-ui/icons/People';
<SidebarPage>
<Sidebar>
//...
<SidebarGroup label="Menu" icon={<MenuIcon />}>
{/* Global nav, not org-specific */}
//...
<SidebarItem icon={HomeIcon} to="catalog" text="Home" />
+ <MyGroupsSidebarItem props={{
+ singularTitle: "My Squad",
+ pluralTitle: "My Squads",
+ icon: GroupIcon}}
+ />
//...
</SidebarGroup>
</ Sidebar>
</SidebarPage>
```
Once added MyGroupsSidebarItem will work in three ways:
1. The user is not logged in or the logged in user is not a member of any group: the MyGroupsSidebarItem will not display anything in the sidebar
2. The user is logged in and a member of only one group: the MyGroupsSidebarItem will display a single item in the sidebar like this:
![MyGroupsSidebarItem single example](./docs/mygroupssidebaritem-single.png)
3. The user is logged in and a member of more than one group: the MyGroupsSidebarItem will display a single items with a flyout menu with all the related groups like this:
![MyGroupsSidebarItem multiple example](./docs/mygroupssidebaritem-multiple.png)
+8 -10
View File
@@ -64,17 +64,15 @@ export const MembersListCard: (_props: {
pageSize?: number;
}) => JSX.Element;
// Warning: (ae-missing-release-tag) "MyGroups" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const MyGroups: ({
singularTitle,
pluralTitle,
icon,
// @public
export const MyGroupsSidebarItem: ({
props,
}: {
singularTitle: string;
pluralTitle: string;
icon: IconComponent;
props: {
singularTitle: string;
pluralTitle: string;
icon: IconComponent;
};
}) => JSX.Element;
// Warning: (ae-missing-release-tag) "orgPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

+5
View File
@@ -13,6 +13,11 @@
"backstage": {
"role": "frontend-plugin"
},
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/org"
},
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
@@ -16,16 +16,16 @@
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import { MyGroups } from '.';
import { MyGroupsSidebarItem } from './MyGroupsSidebarItem';
import GroupIcon from '@material-ui/icons/People';
import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
describe('MyGroups Test', () => {
describe('MyGroupsSidebarItem Test', () => {
describe('For guests or users with no groups', () => {
it('MyGroups should be empty', async () => {
it('MyGroupsSidebarItem should be empty', async () => {
const identityApi: Partial<IdentityApi> = {
getBackstageIdentity: async () => ({
type: 'user',
@@ -46,12 +46,19 @@ describe('MyGroups Test', () => {
[catalogApiRef, catalogApi],
]}
>
<MyGroups
singularTitle="My Squad"
pluralTitle="My Squads"
icon={GroupIcon}
<MyGroupsSidebarItem
props={{
singularTitle: 'My Squad',
pluralTitle: 'My Squads',
icon: GroupIcon,
}}
/>
</TestApiProvider>,
{
mountedRoutes: {
'/': entityRouteRef,
},
},
);
expect(rendered.container).toBeEmptyDOMElement();
@@ -59,7 +66,7 @@ describe('MyGroups Test', () => {
});
describe('For users that are members of a single group', () => {
it('MyGroups should display a single item that links to their group', async () => {
it('MyGroupsSidebarItem should display a single item that links to their group', async () => {
const identityApi: Partial<IdentityApi> = {
getBackstageIdentity: async () => ({
type: 'user',
@@ -94,12 +101,19 @@ describe('MyGroups Test', () => {
[catalogApiRef, catalogApi],
]}
>
<MyGroups
singularTitle="My Squad"
pluralTitle="My Squads"
icon={GroupIcon}
<MyGroupsSidebarItem
props={{
singularTitle: 'My Squad',
pluralTitle: 'My Squads',
icon: GroupIcon,
}}
/>
</TestApiProvider>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
},
},
);
expect(rendered.getByLabelText('My Squad')).toBeInTheDocument();
expect(rendered.getByLabelText('My Squad')).toHaveAttribute(
@@ -110,7 +124,7 @@ describe('MyGroups Test', () => {
});
describe('For users that are members of multiple groups', () => {
it('MyGroups should display a sub-menu with all their groups and a link to each group', async () => {
it('MyGroupsSidebarItem should display a sub-menu with all their groups and a link to each group', async () => {
const identityApi: Partial<IdentityApi> = {
getBackstageIdentity: async () => ({
type: 'user',
@@ -171,12 +185,19 @@ describe('MyGroups Test', () => {
[catalogApiRef, catalogApi],
]}
>
<MyGroups
singularTitle="My Squad"
pluralTitle="My Squads"
icon={GroupIcon}
<MyGroupsSidebarItem
props={{
singularTitle: 'My Squad',
pluralTitle: 'My Squads',
icon: GroupIcon,
}}
/>
</TestApiProvider>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
},
},
);
expect(rendered.getByLabelText('My Squads')).toBeInTheDocument();
@@ -24,21 +24,30 @@ import {
IconComponent,
identityApiRef,
useApi,
useRouteRef,
} from '@backstage/core-plugin-api';
import useAsync from 'react-use/lib/useAsync';
import { catalogApiRef, CatalogApi } from '@backstage/plugin-catalog-react';
import {
catalogApiRef,
CatalogApi,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { getCompoundEntityRef } from '@backstage/catalog-model';
export const MyGroups = ({
singularTitle,
pluralTitle,
icon,
/**
* MyGroupsSidebarItem can be added to your sidebar providing quick access to groups the logged in user is a member of
*
* @public
*/
export const MyGroupsSidebarItem = ({
props,
}: {
singularTitle: string;
pluralTitle: string;
icon: IconComponent;
props: { singularTitle: string; pluralTitle: string; icon: IconComponent };
}) => {
const { singularTitle, pluralTitle, icon } = props;
const identityApi = useApi(identityApiRef);
const catalogApi: CatalogApi = useApi(catalogApiRef);
const catalogEntityRoute = useRouteRef(entityRouteRef);
const { value: groups } = useAsync(async () => {
const profile = await identityApi.getBackstageIdentity();
@@ -47,31 +56,36 @@ export const MyGroups = ({
filter: [{ kind: 'group', 'relations.hasMember': profile.userEntityRef }],
fields: ['metadata', 'kind'],
});
return response.items;
}, []);
if (groups && groups.length === 1) {
// Only member of one group
// Not a member of any groups
if (!groups?.length) {
return <></>;
}
// Only member of one group
if (groups.length === 1) {
const group = groups[0];
return (
<SidebarItem
text={singularTitle}
to={`/catalog/${group.metadata.namespace}/${group.kind}/${group.metadata.name}`}
to={catalogEntityRoute(getCompoundEntityRef(group))}
icon={icon}
/>
);
}
// Member of more than one group
// or not a member of any groups
return groups && groups.length > 1 ? (
return (
<SidebarItem icon={icon} to="catalog" text={pluralTitle}>
<SidebarSubmenu title={pluralTitle}>
{groups?.map(function groupsMap(group) {
return (
<SidebarSubmenuItem
title={group.metadata.title || group.metadata.name}
to={`/catalog/${group.metadata.namespace}/${group.kind}/${group.metadata.name}`}
to={catalogEntityRoute(getCompoundEntityRef(group))}
icon={icon}
key={group.metadata.name}
/>
@@ -79,7 +93,5 @@ export const MyGroups = ({
})}
</SidebarSubmenu>
</SidebarItem>
) : (
<></>
);
};
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { MyGroups } from './MyGroups';
export { MyGroupsSidebarItem } from './MyGroupsSidebarItem';
+1 -1
View File
@@ -15,4 +15,4 @@
*/
export * from './Cards';
export { MyGroups } from './MyGroups';
export { MyGroupsSidebarItem } from './MyGroupsSidebarItem';