From 2e138c95ca6544d36356b171e9378e13c6de357c Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Fri, 14 Oct 2022 14:42:51 +1000 Subject: [PATCH 01/10] Update profile cards to display more information if relevant Signed-off-by: Joe Patterson --- .changeset/tender-jeans-clean.md | 12 ++++ ADOPTERS.md | 1 + .../src/kinds/GroupEntityV1alpha1.ts | 14 +++-- .../src/kinds/UserEntityV1alpha1.ts | 14 +++-- .../src/components/Table/Table.tsx | 2 +- .../GroupProfile/GroupProfileCard.stories.tsx | 43 +++++++++++++ .../Group/GroupProfile/GroupProfileCard.tsx | 48 ++++++++++++++- .../UserProfileCard.stories.tsx | 43 +++++++++++++ .../UserProfileCard/UserProfileCard.test.tsx | 61 +++++++++++++++++++ .../User/UserProfileCard/UserProfileCard.tsx | 50 ++++++++++++++- storybook/.storybook/preview-head.html | 4 ++ 11 files changed, 279 insertions(+), 13 deletions(-) create mode 100644 .changeset/tender-jeans-clean.md create mode 100644 storybook/.storybook/preview-head.html diff --git a/.changeset/tender-jeans-clean.md b/.changeset/tender-jeans-clean.md new file mode 100644 index 0000000000..2729fa393a --- /dev/null +++ b/.changeset/tender-jeans-clean.md @@ -0,0 +1,12 @@ +--- +'@backstage/catalog-model': minor +'@backstage/plugin-org': minor +--- + +Updates the profile of Group and User to allow any extra string key pair value. + +Then updates the user profile and group profile cards to display any links and extra profile details. + +This allows extra customization without going down the full customization route. + +So for example if you wanted to add address, phone number, job title, slack link to users or departments this allows you to within the current spec diff --git a/ADOPTERS.md b/ADOPTERS.md index ca56a00890..d37aabde72 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -215,3 +215,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Ferrovial](https://ferrovial.com) | [Jose Luis Rosado](mailto:jlrosado@ferrovial.com) | Backstage is helping us to improve and acelerate dev experience helping teams to quickly find technical documentation, infrastructure templates, pipelines, software components and quickstarters that have been developed by our squads in a inner source friendly environment. | | [Inter&Co](https://bancointer.com.br) | [Arnaud Lanna](https://github.com/arnaudlanna), [Adriano Silva](https://github.com/adrianovss), [Bruno Grossi](https://github.com/begrossi) | We're using Backstage as our internal Developer Portal to catalog and collect repositories and microservices pieces of information like ownership, deployment time, and documentation. | | [StatusNeo](https://statusneo.com/) | [Karan Nangru](mailto:nangru@statusneo.com), [@NishkarshRaj](https://github.com/NishkarshRaj), and [Gaurav Sarien](mailto:gaurav.sarien@statusneo.com) | Harnessing the power of central catalog inventory and self-serving software templates | +| [Contino](https://www.contino.io/) | [Joseph Patterson](mailto:joseph.patterson@contino.io) | Building out a central catalog for our software community | diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index 8d88817dbe..9e514b1000 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -18,6 +18,14 @@ import type { Entity } from '../entity/Entity'; import schema from '../schema/kinds/Group.v1alpha1.schema.json'; import { ajvCompiledJsonSchemaValidator } from './util'; +interface GroupProfileStatic { + displayName?: string; + email?: string; + picture?: string; +} + +type GroupProfile = Record & GroupProfileStatic; + /** * Backstage catalog Group kind Entity. * @@ -28,11 +36,7 @@ export interface GroupEntityV1alpha1 extends Entity { kind: 'Group'; spec: { type: string; - profile?: { - displayName?: string; - email?: string; - picture?: string; - }; + profile?: GroupProfile; parent?: string; children: string[]; members?: string[]; diff --git a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts index 55c9b176ea..53446a5275 100644 --- a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts @@ -18,6 +18,14 @@ import type { Entity } from '../entity/Entity'; import schema from '../schema/kinds/User.v1alpha1.schema.json'; import { ajvCompiledJsonSchemaValidator } from './util'; +interface UserProfileStatic { + displayName?: string; + email?: string; + picture?: string; +} + +type UserProfile = Record & UserProfileStatic; + /** * Backstage catalog User kind Entity. * @@ -27,11 +35,7 @@ export interface UserEntityV1alpha1 extends Entity { apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; kind: 'User'; spec: { - profile?: { - displayName?: string; - email?: string; - picture?: string; - }; + profile?: UserProfile; memberOf?: string[]; }; } diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx index 49538b680a..13f77106e0 100644 --- a/packages/core-components/src/components/Table/Table.tsx +++ b/packages/core-components/src/components/Table/Table.tsx @@ -373,7 +373,7 @@ export function Table(props: TableProps) { const newData = (data as any[]).filter( el => !!Object.entries(selectedFilters) - .filter(([, value]) => !!value.length) + .filter(([, value]) => !!(value as []).length) .every(([key, filterValue]) => { const fieldValue = extractValueByField( el, diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx index fc45d9a8fe..7d28b5a2fa 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx @@ -87,3 +87,46 @@ export default { ), ], }; + +const extraDetailsEntity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'team-a', + description: 'Team A', + links: [ + { + url: 'slack://user?team=T00000000&id=U00000000', + title: 'Slack', + icon: 'message', + }, + { + url: 'https://www.google.com', + title: 'Google', + }, + ], + }, + spec: { + profile: { + displayName: 'Team A', + email: 'team-a@example.com', + picture: + 'https://avatars.dicebear.com/api/identicon/team-a@example.com.svg?background=%23fff&margin=25', + Telephone: '123456789', + Location: 'London', + }, + type: 'group', + children: [], + }, + relations: [dummyDepartment], +}; + +export const ExtraDetails = () => ( + + + + + + + +); diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index dfdb566b20..c12b4b8906 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -37,10 +37,13 @@ import { ListItemText, Tooltip, IconButton, + Divider, } from '@material-ui/core'; +import Icon from '@material-ui/core/Icon'; import AccountTreeIcon from '@material-ui/icons/AccountTree'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; +import LinkIcon from '@material-ui/icons/Link'; import EditIcon from '@material-ui/icons/Edit'; import CachedIcon from '@material-ui/icons/Cached'; import Alert from '@material-ui/lab/Alert'; @@ -53,6 +56,8 @@ import { } from '@backstage/core-components'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; +const staticProfileKeys = ['displayName', 'email', 'picture']; + const CardTitle = (props: { title: string }) => ( @@ -76,7 +81,7 @@ export const GroupProfileCard = (props: { variant?: InfoCardVariants }) => { } const { - metadata: { name, description, annotations }, + metadata: { name, description, annotations, links }, spec: { profile }, } = group; @@ -94,6 +99,11 @@ export const GroupProfileCard = (props: { variant?: InfoCardVariants }) => { const entityMetadataEditUrl = group.metadata.annotations?.[ANNOTATION_EDIT_URL]; + const profileKeys = + profile !== undefined + ? Object.keys(profile).filter(key => !staticProfileKeys.includes(key)) + : []; + const displayName = profile?.displayName ?? name; const emailHref = profile?.email ? `mailto:${profile.email}` : '#'; const infoCardAction = entityMetadataEditUrl ? ( @@ -190,6 +200,42 @@ export const GroupProfileCard = (props: { variant?: InfoCardVariants }) => { secondary="Child Groups" /> + {links !== undefined && } + {links !== undefined && + links.map(link => { + return ( + + {link.icon ? ( + + + {link.icon} + + + ) : ( + + + + )} + {link.title} + + ); + })} + {profile !== undefined && profileKeys.length > 0 && } + {profile !== undefined && + profileKeys.length > 0 && + profileKeys.map(key => { + const value = profile[key]; + + return ( + + + {key} + + + {value} + + ); + })} diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index eb7b8fadda..28fc3d3831 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -102,3 +102,46 @@ export default { }), ], }; + +const extraDetailsEntity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'guest', + description: 'Description for guest', + links: [ + { + url: 'slack://user?team=T00000000&id=U00000000', + title: 'Slack', + icon: 'message', + }, + { + url: 'https://www.google.com', + title: 'Google', + }, + ], + }, + spec: { + profile: { + displayName: 'Guest User', + email: 'guest@example.com', + picture: + 'https://avatars.dicebear.com/api/avataaars/guest@example.com.svg?background=%23fff', + 'Job Title': 'Software Engineer', + Department: 'Engineering', + Location: 'San Francisco, CA', + }, + memberOf: ['team-a'], + }, + relations: [dummyGroup], +}; + +export const ExtraDetails = () => ( + + + + + + + +); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index f339a5828c..7eabdf2903 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -155,4 +155,65 @@ describe('Edit Button', () => { ); expect(rendered.getByRole('button')).toBeInTheDocument(); }); + + it('Should show the extra fields if either links or extra profile are filled', async () => { + const annotations: Record = { + 'backstage.io/edit-url': 'https://example.com/user.yaml', + }; + const userEntity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'calum.leavy', + description: 'Super awesome human', + annotations, + links: [ + { + url: 'slack://user?team=T00000000&id=U00000000', + title: 'Slack', + icon: 'message', + }, + { + url: 'https://www.google.com', + title: 'Google', + }, + ], + }, + spec: { + profile: { + displayName: 'Calum Leavy', + email: 'calum-leavy@example.com', + 'Job Title': 'Software Engineer', + Department: 'Engineering', + Location: 'San Francisco, CA', + }, + memberOf: ['ExampleGroup'], + }, + relations: [ + { + type: 'memberOf', + targetRef: 'group:default/examplegroup', + }, + ], + }; + + const rendered = await renderWithEffects( + wrapInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ), + ); + expect(rendered.getByText('Software Engineer')).toBeInTheDocument(); + expect(rendered.getByText('Department')).toBeInTheDocument(); + expect(rendered.getByText('San Francisco, CA')).toBeInTheDocument(); + expect(rendered.getByText('Location')).toBeInTheDocument(); + expect(rendered.getByText('Slack')).toBeInTheDocument(); + expect(rendered.getByText('Google')).toBeInTheDocument(); + }); }); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index dd75578687..68a27764d8 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -33,12 +33,15 @@ import { ListItemIcon, ListItemText, Tooltip, + Divider, } from '@material-ui/core'; import EditIcon from '@material-ui/icons/Edit'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; import PersonIcon from '@material-ui/icons/Person'; +import LinkIcon from '@material-ui/icons/Link'; import Alert from '@material-ui/lab/Alert'; +import Icon from '@material-ui/core/Icon'; import React from 'react'; import { Avatar, @@ -47,6 +50,8 @@ import { Link, } from '@backstage/core-components'; +const staticProfileKeys = ['displayName', 'email', 'picture']; + const CardTitle = (props: { title?: string }) => props.title ? ( @@ -66,7 +71,7 @@ export const UserProfileCard = (props: { variant?: InfoCardVariants }) => { user.metadata.annotations?.[ANNOTATION_EDIT_URL]; const { - metadata: { name: metaName, description }, + metadata: { name: metaName, description, links }, spec: { profile }, } = user; const displayName = profile?.displayName ?? metaName; @@ -75,6 +80,11 @@ export const UserProfileCard = (props: { variant?: InfoCardVariants }) => { kind: 'Group', }); + const profileKeys = + profile !== undefined + ? Object.keys(profile).filter(key => !staticProfileKeys.includes(key)) + : []; + return ( } @@ -128,6 +138,44 @@ export const UserProfileCard = (props: { variant?: InfoCardVariants }) => { /> + + {links !== undefined && } + {links !== undefined && + links.map(link => { + return ( + + {link.icon ? ( + + + {link.icon} + + + ) : ( + + + + )} + {link.title} + + ); + })} + + {profile !== undefined && profileKeys.length > 0 && } + {profile !== undefined && + profileKeys.length > 0 && + profileKeys.map(key => { + const value = profile[key]; + + return ( + + + {key} + + + {value} + + ); + })} diff --git a/storybook/.storybook/preview-head.html b/storybook/.storybook/preview-head.html new file mode 100644 index 0000000000..a21b9971e8 --- /dev/null +++ b/storybook/.storybook/preview-head.html @@ -0,0 +1,4 @@ + From 69b35ed0d1c0e5340e3037910d9a56c58fe92dd6 Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Mon, 17 Oct 2022 07:54:14 +1000 Subject: [PATCH 02/10] update api report Signed-off-by: Joe Patterson --- packages/catalog-model/api-report.md | 17 +++++++---------- .../src/kinds/GroupEntityV1alpha1.ts | 9 +++++++-- .../src/kinds/UserEntityV1alpha1.ts | 10 +++++++--- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index b7456bb2f8..1a58dcb5ca 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -257,11 +257,7 @@ interface GroupEntityV1alpha1 extends Entity { // (undocumented) spec: { type: string; - profile?: { - displayName?: string; - email?: string; - picture?: string; - }; + profile?: GroupProfile; parent?: string; children: string[]; members?: string[]; @@ -491,11 +487,7 @@ interface UserEntityV1alpha1 extends Entity { kind: 'User'; // (undocumented) spec: { - profile?: { - displayName?: string; - email?: string; - picture?: string; - }; + profile?: UserProfile; memberOf?: string[]; }; } @@ -517,4 +509,9 @@ export type Validators = { isValidAnnotationValue(value: unknown): boolean; isValidTag(value: unknown): boolean; }; + +// Warnings were encountered during analysis: +// +// src/kinds/GroupEntityV1alpha1.d.ts:23:9 - (ae-forgotten-export) The symbol "GroupProfile" needs to be exported by the entry point index.d.ts +// src/kinds/UserEntityV1alpha1.d.ts:22:9 - (ae-forgotten-export) The symbol "UserProfile" needs to be exported by the entry point index.d.ts ``` diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index 9e514b1000..51f5f30f04 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -18,13 +18,18 @@ import type { Entity } from '../entity/Entity'; import schema from '../schema/kinds/Group.v1alpha1.schema.json'; import { ajvCompiledJsonSchemaValidator } from './util'; -interface GroupProfileStatic { +export interface GroupProfileStatic { displayName?: string; email?: string; picture?: string; } -type GroupProfile = Record & GroupProfileStatic; +/** + * Backstage Group Profile. + * + * @public + */ +export type GroupProfile = Record & GroupProfileStatic; /** * Backstage catalog Group kind Entity. diff --git a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts index 53446a5275..39d3861702 100644 --- a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts @@ -18,13 +18,17 @@ import type { Entity } from '../entity/Entity'; import schema from '../schema/kinds/User.v1alpha1.schema.json'; import { ajvCompiledJsonSchemaValidator } from './util'; -interface UserProfileStatic { +export interface UserProfileStatic { displayName?: string; email?: string; picture?: string; } - -type UserProfile = Record & UserProfileStatic; +/** + * Backstage User Profile. + * + * @public + */ +export type UserProfile = Record & UserProfileStatic; /** * Backstage catalog User kind Entity. From bc825a7a7bce5d514eb079c2d374b243a2e04b2d Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Mon, 17 Oct 2022 08:17:28 +1000 Subject: [PATCH 03/10] remove changes not directly related to this change Signed-off-by: Joe Patterson --- ADOPTERS.md | 1 - packages/core-components/src/components/Table/Table.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index d37aabde72..ca56a00890 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -215,4 +215,3 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Ferrovial](https://ferrovial.com) | [Jose Luis Rosado](mailto:jlrosado@ferrovial.com) | Backstage is helping us to improve and acelerate dev experience helping teams to quickly find technical documentation, infrastructure templates, pipelines, software components and quickstarters that have been developed by our squads in a inner source friendly environment. | | [Inter&Co](https://bancointer.com.br) | [Arnaud Lanna](https://github.com/arnaudlanna), [Adriano Silva](https://github.com/adrianovss), [Bruno Grossi](https://github.com/begrossi) | We're using Backstage as our internal Developer Portal to catalog and collect repositories and microservices pieces of information like ownership, deployment time, and documentation. | | [StatusNeo](https://statusneo.com/) | [Karan Nangru](mailto:nangru@statusneo.com), [@NishkarshRaj](https://github.com/NishkarshRaj), and [Gaurav Sarien](mailto:gaurav.sarien@statusneo.com) | Harnessing the power of central catalog inventory and self-serving software templates | -| [Contino](https://www.contino.io/) | [Joseph Patterson](mailto:joseph.patterson@contino.io) | Building out a central catalog for our software community | diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx index 13f77106e0..49538b680a 100644 --- a/packages/core-components/src/components/Table/Table.tsx +++ b/packages/core-components/src/components/Table/Table.tsx @@ -373,7 +373,7 @@ export function Table(props: TableProps) { const newData = (data as any[]).filter( el => !!Object.entries(selectedFilters) - .filter(([, value]) => !!(value as []).length) + .filter(([, value]) => !!value.length) .every(([key, filterValue]) => { const fieldValue = extractValueByField( el, From d7b0a117686ba30d67b7d6e36a57d97ea0ae075b Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Mon, 17 Oct 2022 11:02:15 +1000 Subject: [PATCH 04/10] updating the pr on the feedback to add docs, remove some items not needed including the preview html Signed-off-by: Joe Patterson --- .../software-catalog/external-integrations.md | 126 ++++++++++++++++++ packages/catalog-model/api-report.md | 19 ++- .../src/kinds/GroupEntityV1alpha1.ts | 12 +- .../src/kinds/UserEntityV1alpha1.ts | 11 +- packages/catalog-model/src/kinds/index.ts | 2 + .../GroupProfile/GroupProfileCard.stories.tsx | 2 +- .../Group/GroupProfile/GroupProfileCard.tsx | 49 +------ .../src/components/Cards/Meta/LinksGroup.tsx | 69 ++++++++++ .../Cards/Meta/ProfileInfoGroup.tsx | 56 ++++++++ .../org/src/components/Cards/Meta/index.ts | 17 +++ .../UserProfileCard.stories.tsx | 2 +- .../User/UserProfileCard/UserProfileCard.tsx | 50 +------ storybook/.storybook/preview-head.html | 4 - 13 files changed, 302 insertions(+), 117 deletions(-) create mode 100644 plugins/org/src/components/Cards/Meta/LinksGroup.tsx create mode 100644 plugins/org/src/components/Cards/Meta/ProfileInfoGroup.tsx create mode 100644 plugins/org/src/components/Cards/Meta/index.ts delete mode 100644 storybook/.storybook/preview-head.html diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index 40f917e2f3..f074f3a641 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -261,6 +261,132 @@ the `connect` call has been made to the provider. Start up the backend - it should now start reading from the previously registered location and you'll see your entities start to appear in Backstage. +### Example User Entity Provider + +If you have a 3rd party entity provider such as an internal HR system that you wish to use you are not limited to using our entity providers, (or simply wish to add to existing entity providers with your own data). + +We can create an entity provider to read entities that are based off that provider. + +We create a basic entity provider as shown above. In the example below we might want to extract our users from an HR system, I am assuming the HR system already has the slackUserId to get that information please see the [Slack Api](https://api.slack.com/methods). + +```typescript +import { + ANNOTATION_LOCATION, + ANNOTATION_ORIGIN_LOCATION, +} from '@backstage/catalog-model' +import { + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-backend' +import { WebClient } from '@slack/web-api' +import {kebabCase} from 'lodash' + +interface Staff { + displayName: string + slackUserId: string + jobTitle: string + photoUrl: string + address: string + email:string +} + +export class UserEntityProvider implements EntityProvider { + private readonly getStaffUrl: string + protected readonly slackTeam: string + protected readonly slackToken: string + protected connection?: EntityProviderConnection + + static fromConfig(config: Config, options: { logger: Logger }) { + const getStaffUrl = config.getString('staff.url') + const slackToken = config.getString('slack.token') + const slackTeam = config.getString('slack.team') + return new UserEntityProvider({ + ...options, + getStaffUrl, + slackToken, + slackTeam, + }) + } + + private constructor(options: { + getStaffUrl: string + slackToken: string + slackTeam: string + }) { + this.getStaffUrl = options.getStaffUrl + this.slackToken = options.slackToken + this.slackTeam = options.slackTeam + } + + async getAllStaff(): Promise{ + await return axios.get(this.getStaffUrl) + } + + public async connect(connection: EntityProviderConnection): Promise { + this.connection = connection + } + + async run(): Promise { + if (!this.connection) { + throw new Error('USer Connection Not initialized') + } + + const userResources: UserEntity[] = [] + const staff = await this.getAllStaff() + + for (const user of staff) { + // we can add any links here in this case it would be adding a slack link to the users so you can directly slack them. + const links = + user.slackUserId != null && user.slackUserId.length > 0 + ? [ + { + url: `slack://user?team=${this.slackTeam}&id=${user.slackUserId}`, + title: 'Slack', + icon: 'message', + }, + ] + : undefined + const userEntity: UserEntity = { + kind: 'User', + apiVersion: 'backstage.io/v1alpha1', + metadata: { + annotations: { + [ANNOTATION_LOCATION]: 'hr-user-https://www.hrurl.com/', + [ANNOTATION_ORIGIN_LOCATION]: 'hr-user-https://www.hrurl.com/', + }, + links, + // name of the entity + name: kebabCase(user.displayName as string), + // name for display purposes could be anything including email + title: user.displayName as string, + }, + spec: { + profile: { + displayName: user.displayName as string, + email: user.email, + picture: user.photoUrl ?? 'fake', + // we can add any string/string here and it will be displayed on a user profile card, eg Job Title, Address, or any other information you want displayed + 'Job Title': user.jobTitle as string, + 'Address': user.address, + }, + memberOf: [], + }, + } + + userResources.push(userEntity) + } + + await this.connection.applyMutation({ + type: 'full', + entities: userResources.map((entity) => ({ + entity, + locationKey: 'hr-user-https://www.hrurl.com/', + })), + }) +} + +``` + ## Custom Processors The other possible way of ingesting data into the catalog is through the use of diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 1a58dcb5ca..da4c335051 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -269,6 +269,13 @@ export { GroupEntityV1alpha1 }; // @public export const groupEntityV1alpha1Validator: KindValidator; +// @public +export type GroupProfile = Record & { + displayName?: string; + email?: string; + picture?: string; +}; + // @public (undocumented) export function isApiEntity(entity: Entity): entity is ApiEntityV1alpha1; @@ -497,6 +504,13 @@ export { UserEntityV1alpha1 }; // @public export const userEntityV1alpha1Validator: KindValidator; +// @public +export type UserProfile = Record & { + displayName?: string; + email?: string; + picture?: string; +}; + // @public export type Validators = { isValidApiVersion(value: unknown): boolean; @@ -509,9 +523,4 @@ export type Validators = { isValidAnnotationValue(value: unknown): boolean; isValidTag(value: unknown): boolean; }; - -// Warnings were encountered during analysis: -// -// src/kinds/GroupEntityV1alpha1.d.ts:23:9 - (ae-forgotten-export) The symbol "GroupProfile" needs to be exported by the entry point index.d.ts -// src/kinds/UserEntityV1alpha1.d.ts:22:9 - (ae-forgotten-export) The symbol "UserProfile" needs to be exported by the entry point index.d.ts ``` diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index 51f5f30f04..50304e5220 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -18,18 +18,16 @@ import type { Entity } from '../entity/Entity'; import schema from '../schema/kinds/Group.v1alpha1.schema.json'; import { ajvCompiledJsonSchemaValidator } from './util'; -export interface GroupProfileStatic { - displayName?: string; - email?: string; - picture?: string; -} - /** * Backstage Group Profile. * * @public */ -export type GroupProfile = Record & GroupProfileStatic; +export type GroupProfile = Record & { + displayName?: string; + email?: string; + picture?: string; +}; /** * Backstage catalog Group kind Entity. diff --git a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts index 39d3861702..9e034ad6b3 100644 --- a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts @@ -18,17 +18,16 @@ import type { Entity } from '../entity/Entity'; import schema from '../schema/kinds/User.v1alpha1.schema.json'; import { ajvCompiledJsonSchemaValidator } from './util'; -export interface UserProfileStatic { - displayName?: string; - email?: string; - picture?: string; -} /** * Backstage User Profile. * * @public */ -export type UserProfile = Record & UserProfileStatic; +export type UserProfile = Record & { + displayName?: string; + email?: string; + picture?: string; +}; /** * Backstage catalog User kind Entity. diff --git a/packages/catalog-model/src/kinds/index.ts b/packages/catalog-model/src/kinds/index.ts index 7211c25a8d..dd9d0545c6 100644 --- a/packages/catalog-model/src/kinds/index.ts +++ b/packages/catalog-model/src/kinds/index.ts @@ -33,6 +33,7 @@ export { groupEntityV1alpha1Validator } from './GroupEntityV1alpha1'; export type { GroupEntityV1alpha1 as GroupEntity, GroupEntityV1alpha1, + GroupProfile, } from './GroupEntityV1alpha1'; export { locationEntityV1alpha1Validator } from './LocationEntityV1alpha1'; export type { @@ -55,4 +56,5 @@ export { userEntityV1alpha1Validator } from './UserEntityV1alpha1'; export type { UserEntityV1alpha1 as UserEntity, UserEntityV1alpha1, + UserProfile, } from './UserEntityV1alpha1'; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx index 7d28b5a2fa..264c4081d7 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx @@ -98,7 +98,7 @@ const extraDetailsEntity: GroupEntity = { { url: 'slack://user?team=T00000000&id=U00000000', title: 'Slack', - icon: 'message', + icon: 'chat', }, { url: 'https://www.google.com', diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index c12b4b8906..e6cd183e8f 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -37,13 +37,10 @@ import { ListItemText, Tooltip, IconButton, - Divider, } from '@material-ui/core'; -import Icon from '@material-ui/core/Icon'; import AccountTreeIcon from '@material-ui/icons/AccountTree'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; -import LinkIcon from '@material-ui/icons/Link'; import EditIcon from '@material-ui/icons/Edit'; import CachedIcon from '@material-ui/icons/Cached'; import Alert from '@material-ui/lab/Alert'; @@ -55,8 +52,7 @@ import { Link, } from '@backstage/core-components'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; - -const staticProfileKeys = ['displayName', 'email', 'picture']; +import { LinksGroup, ProfileInfoGroup } from '../../Meta'; const CardTitle = (props: { title: string }) => ( @@ -99,11 +95,6 @@ export const GroupProfileCard = (props: { variant?: InfoCardVariants }) => { const entityMetadataEditUrl = group.metadata.annotations?.[ANNOTATION_EDIT_URL]; - const profileKeys = - profile !== undefined - ? Object.keys(profile).filter(key => !staticProfileKeys.includes(key)) - : []; - const displayName = profile?.displayName ?? name; const emailHref = profile?.email ? `mailto:${profile.email}` : '#'; const infoCardAction = entityMetadataEditUrl ? ( @@ -200,42 +191,8 @@ export const GroupProfileCard = (props: { variant?: InfoCardVariants }) => { secondary="Child Groups" /> - {links !== undefined && } - {links !== undefined && - links.map(link => { - return ( - - {link.icon ? ( - - - {link.icon} - - - ) : ( - - - - )} - {link.title} - - ); - })} - {profile !== undefined && profileKeys.length > 0 && } - {profile !== undefined && - profileKeys.length > 0 && - profileKeys.map(key => { - const value = profile[key]; - - return ( - - - {key} - - - {value} - - ); - })} + + diff --git a/plugins/org/src/components/Cards/Meta/LinksGroup.tsx b/plugins/org/src/components/Cards/Meta/LinksGroup.tsx new file mode 100644 index 0000000000..e826c1cf9c --- /dev/null +++ b/plugins/org/src/components/Cards/Meta/LinksGroup.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 { EntityLink } from '@backstage/catalog-model'; +import { IconComponent, useApp } from '@backstage/core-plugin-api'; +import LanguageIcon from '@material-ui/icons/Language'; +import { + ListItem, + ListItemIcon, + ListItemText, + Divider, +} from '@material-ui/core'; +import React, { useCallback } from 'react'; + +const WebLink = ({ + href, + Icon, + text, +}: { + href: string; + text?: string; + Icon?: IconComponent; +}) => ( + + {Icon ? : } + {text} + +); + +export const LinksGroup = ({ links }: { links?: EntityLink[] }) => { + const app = useApp(); + const iconResolver = useCallback( + (key?: string): IconComponent => + key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon, + [app], + ); + + if (links === undefined) { + return null; + } + + return ( + <> + + {links.map(link => { + return ( + + ); + })} + + ); +}; diff --git a/plugins/org/src/components/Cards/Meta/ProfileInfoGroup.tsx b/plugins/org/src/components/Cards/Meta/ProfileInfoGroup.tsx new file mode 100644 index 0000000000..177983d4a5 --- /dev/null +++ b/plugins/org/src/components/Cards/Meta/ProfileInfoGroup.tsx @@ -0,0 +1,56 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 { ListItem, ListItemText, Divider } from '@material-ui/core'; +import React, { useMemo } from 'react'; + +const staticProfileKeys = ['displayName', 'email', 'picture']; + +export const ProfileInfoGroup = ({ + profile, +}: { + profile?: Record; +}) => { + const profileKeys = useMemo( + () => + profile !== undefined + ? Object.keys(profile).filter(key => !staticProfileKeys.includes(key)) + : [], + [profile], + ); + + if (profile === undefined || profileKeys.length === 0) { + return null; + } + + return ( + <> + + {profileKeys.map(key => { + const value = profile[key]; + + return ( + + + {key} + + + {value} + + ); + })} + + ); +}; diff --git a/plugins/org/src/components/Cards/Meta/index.ts b/plugins/org/src/components/Cards/Meta/index.ts new file mode 100644 index 0000000000..5f4e5b4690 --- /dev/null +++ b/plugins/org/src/components/Cards/Meta/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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. + */ +export * from './LinksGroup'; +export * from './ProfileInfoGroup'; diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index 28fc3d3831..7abfbee389 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -113,7 +113,7 @@ const extraDetailsEntity: UserEntity = { { url: 'slack://user?team=T00000000&id=U00000000', title: 'Slack', - icon: 'message', + icon: 'chat', }, { url: 'https://www.google.com', diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 68a27764d8..8a56dc58c2 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -33,15 +33,12 @@ import { ListItemIcon, ListItemText, Tooltip, - Divider, } from '@material-ui/core'; import EditIcon from '@material-ui/icons/Edit'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; import PersonIcon from '@material-ui/icons/Person'; -import LinkIcon from '@material-ui/icons/Link'; import Alert from '@material-ui/lab/Alert'; -import Icon from '@material-ui/core/Icon'; import React from 'react'; import { Avatar, @@ -49,8 +46,7 @@ import { InfoCardVariants, Link, } from '@backstage/core-components'; - -const staticProfileKeys = ['displayName', 'email', 'picture']; +import { LinksGroup, ProfileInfoGroup } from '../../Meta'; const CardTitle = (props: { title?: string }) => props.title ? ( @@ -80,11 +76,6 @@ export const UserProfileCard = (props: { variant?: InfoCardVariants }) => { kind: 'Group', }); - const profileKeys = - profile !== undefined - ? Object.keys(profile).filter(key => !staticProfileKeys.includes(key)) - : []; - return ( } @@ -139,43 +130,8 @@ export const UserProfileCard = (props: { variant?: InfoCardVariants }) => { - {links !== undefined && } - {links !== undefined && - links.map(link => { - return ( - - {link.icon ? ( - - - {link.icon} - - - ) : ( - - - - )} - {link.title} - - ); - })} - - {profile !== undefined && profileKeys.length > 0 && } - {profile !== undefined && - profileKeys.length > 0 && - profileKeys.map(key => { - const value = profile[key]; - - return ( - - - {key} - - - {value} - - ); - })} + + diff --git a/storybook/.storybook/preview-head.html b/storybook/.storybook/preview-head.html deleted file mode 100644 index a21b9971e8..0000000000 --- a/storybook/.storybook/preview-head.html +++ /dev/null @@ -1,4 +0,0 @@ - From 0f6c6e84fb05371f8c0ecdecc0cf797302c98256 Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Wed, 19 Oct 2022 14:35:03 +1000 Subject: [PATCH 05/10] Update docs/features/software-catalog/external-integrations.md Co-authored-by: Patrik Oldsberg Signed-off-by: Joe Patterson --- docs/features/software-catalog/external-integrations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index f074f3a641..a93e77dc00 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -328,7 +328,7 @@ export class UserEntityProvider implements EntityProvider { async run(): Promise { if (!this.connection) { - throw new Error('USer Connection Not initialized') + throw new Error('User Connection Not initialized') } const userResources: UserEntity[] = [] From 6d42dfe385bc165e51fcd99838a1c65c7a80c10f Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Wed, 19 Oct 2022 14:35:25 +1000 Subject: [PATCH 06/10] Update docs/features/software-catalog/external-integrations.md Co-authored-by: Patrik Oldsberg Signed-off-by: Joe Patterson --- docs/features/software-catalog/external-integrations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index a93e77dc00..57ee9baf25 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -318,7 +318,7 @@ export class UserEntityProvider implements EntityProvider { this.slackTeam = options.slackTeam } - async getAllStaff(): Promise{ + async getAllStaff(): Promise{ await return axios.get(this.getStaffUrl) } From dc50ba192b0a2205ab4d4f799944bb6fc5667be6 Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Wed, 19 Oct 2022 14:35:44 +1000 Subject: [PATCH 07/10] Update docs/features/software-catalog/external-integrations.md Co-authored-by: Patrik Oldsberg Signed-off-by: Joe Patterson --- docs/features/software-catalog/external-integrations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index 57ee9baf25..65f019efa3 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -380,7 +380,7 @@ export class UserEntityProvider implements EntityProvider { type: 'full', entities: userResources.map((entity) => ({ entity, - locationKey: 'hr-user-https://www.hrurl.com/', + locationKey: 'hr-user:https://www.hrurl.com', })), }) } From 0b115001515c3abc4764b3b4b707889caa5f5131 Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Wed, 19 Oct 2022 15:09:49 +1000 Subject: [PATCH 08/10] update pr to remove profile changes Signed-off-by: Joe Patterson --- .changeset/popular-ants-mix.md | 5 ++ .changeset/tender-jeans-clean.md | 12 ---- .../software-catalog/external-integrations.md | 13 ++--- packages/catalog-model/api-report.md | 26 ++++----- .../src/kinds/GroupEntityV1alpha1.ts | 17 ++---- .../src/kinds/UserEntityV1alpha1.ts | 17 ++---- packages/catalog-model/src/kinds/index.ts | 2 - .../GroupProfile/GroupProfileCard.stories.tsx | 2 - .../Group/GroupProfile/GroupProfileCard.tsx | 3 +- .../Cards/Meta/ProfileInfoGroup.tsx | 56 ------------------- .../org/src/components/Cards/Meta/index.ts | 1 - .../UserProfileCard.stories.tsx | 3 - .../UserProfileCard/UserProfileCard.test.tsx | 7 --- .../User/UserProfileCard/UserProfileCard.tsx | 3 +- 14 files changed, 32 insertions(+), 135 deletions(-) create mode 100644 .changeset/popular-ants-mix.md delete mode 100644 .changeset/tender-jeans-clean.md delete mode 100644 plugins/org/src/components/Cards/Meta/ProfileInfoGroup.tsx diff --git a/.changeset/popular-ants-mix.md b/.changeset/popular-ants-mix.md new file mode 100644 index 0000000000..f0e6b17f8d --- /dev/null +++ b/.changeset/popular-ants-mix.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': minor +--- + +Updates the User and Group Profile cards to add the links from the UserEntity or the GroupEntity diff --git a/.changeset/tender-jeans-clean.md b/.changeset/tender-jeans-clean.md deleted file mode 100644 index 2729fa393a..0000000000 --- a/.changeset/tender-jeans-clean.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/catalog-model': minor -'@backstage/plugin-org': minor ---- - -Updates the profile of Group and User to allow any extra string key pair value. - -Then updates the user profile and group profile cards to display any links and extra profile details. - -This allows extra customization without going down the full customization route. - -So for example if you wanted to add address, phone number, job title, slack link to users or departments this allows you to within the current spec diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index 65f019efa3..65f97fb8da 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -356,18 +356,15 @@ export class UserEntityProvider implements EntityProvider { }, links, // name of the entity - name: kebabCase(user.displayName as string), + name: kebabCase(user.displayName), // name for display purposes could be anything including email - title: user.displayName as string, + title: user.displayName, }, spec: { profile: { - displayName: user.displayName as string, + displayName: user.displayName, email: user.email, - picture: user.photoUrl ?? 'fake', - // we can add any string/string here and it will be displayed on a user profile card, eg Job Title, Address, or any other information you want displayed - 'Job Title': user.jobTitle as string, - 'Address': user.address, + picture: user.photoUrl, }, memberOf: [], }, @@ -380,7 +377,7 @@ export class UserEntityProvider implements EntityProvider { type: 'full', entities: userResources.map((entity) => ({ entity, - locationKey: 'hr-user:https://www.hrurl.com', + locationKey: 'hr-user-https://www.hrurl.com/', })), }) } diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index da4c335051..b7456bb2f8 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -257,7 +257,11 @@ interface GroupEntityV1alpha1 extends Entity { // (undocumented) spec: { type: string; - profile?: GroupProfile; + profile?: { + displayName?: string; + email?: string; + picture?: string; + }; parent?: string; children: string[]; members?: string[]; @@ -269,13 +273,6 @@ export { GroupEntityV1alpha1 }; // @public export const groupEntityV1alpha1Validator: KindValidator; -// @public -export type GroupProfile = Record & { - displayName?: string; - email?: string; - picture?: string; -}; - // @public (undocumented) export function isApiEntity(entity: Entity): entity is ApiEntityV1alpha1; @@ -494,7 +491,11 @@ interface UserEntityV1alpha1 extends Entity { kind: 'User'; // (undocumented) spec: { - profile?: UserProfile; + profile?: { + displayName?: string; + email?: string; + picture?: string; + }; memberOf?: string[]; }; } @@ -504,13 +505,6 @@ export { UserEntityV1alpha1 }; // @public export const userEntityV1alpha1Validator: KindValidator; -// @public -export type UserProfile = Record & { - displayName?: string; - email?: string; - picture?: string; -}; - // @public export type Validators = { isValidApiVersion(value: unknown): boolean; diff --git a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts index 50304e5220..8d88817dbe 100644 --- a/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/GroupEntityV1alpha1.ts @@ -18,17 +18,6 @@ import type { Entity } from '../entity/Entity'; import schema from '../schema/kinds/Group.v1alpha1.schema.json'; import { ajvCompiledJsonSchemaValidator } from './util'; -/** - * Backstage Group Profile. - * - * @public - */ -export type GroupProfile = Record & { - displayName?: string; - email?: string; - picture?: string; -}; - /** * Backstage catalog Group kind Entity. * @@ -39,7 +28,11 @@ export interface GroupEntityV1alpha1 extends Entity { kind: 'Group'; spec: { type: string; - profile?: GroupProfile; + profile?: { + displayName?: string; + email?: string; + picture?: string; + }; parent?: string; children: string[]; members?: string[]; diff --git a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts index 9e034ad6b3..55c9b176ea 100644 --- a/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/UserEntityV1alpha1.ts @@ -18,17 +18,6 @@ import type { Entity } from '../entity/Entity'; import schema from '../schema/kinds/User.v1alpha1.schema.json'; import { ajvCompiledJsonSchemaValidator } from './util'; -/** - * Backstage User Profile. - * - * @public - */ -export type UserProfile = Record & { - displayName?: string; - email?: string; - picture?: string; -}; - /** * Backstage catalog User kind Entity. * @@ -38,7 +27,11 @@ export interface UserEntityV1alpha1 extends Entity { apiVersion: 'backstage.io/v1alpha1' | 'backstage.io/v1beta1'; kind: 'User'; spec: { - profile?: UserProfile; + profile?: { + displayName?: string; + email?: string; + picture?: string; + }; memberOf?: string[]; }; } diff --git a/packages/catalog-model/src/kinds/index.ts b/packages/catalog-model/src/kinds/index.ts index dd9d0545c6..7211c25a8d 100644 --- a/packages/catalog-model/src/kinds/index.ts +++ b/packages/catalog-model/src/kinds/index.ts @@ -33,7 +33,6 @@ export { groupEntityV1alpha1Validator } from './GroupEntityV1alpha1'; export type { GroupEntityV1alpha1 as GroupEntity, GroupEntityV1alpha1, - GroupProfile, } from './GroupEntityV1alpha1'; export { locationEntityV1alpha1Validator } from './LocationEntityV1alpha1'; export type { @@ -56,5 +55,4 @@ export { userEntityV1alpha1Validator } from './UserEntityV1alpha1'; export type { UserEntityV1alpha1 as UserEntity, UserEntityV1alpha1, - UserProfile, } from './UserEntityV1alpha1'; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx index 264c4081d7..cfb3d847a4 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx @@ -112,8 +112,6 @@ const extraDetailsEntity: GroupEntity = { email: 'team-a@example.com', picture: 'https://avatars.dicebear.com/api/identicon/team-a@example.com.svg?background=%23fff&margin=25', - Telephone: '123456789', - Location: 'London', }, type: 'group', children: [], diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index e6cd183e8f..3a6b7c238d 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -52,7 +52,7 @@ import { Link, } from '@backstage/core-components'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; -import { LinksGroup, ProfileInfoGroup } from '../../Meta'; +import { LinksGroup } from '../../Meta'; const CardTitle = (props: { title: string }) => ( @@ -192,7 +192,6 @@ export const GroupProfileCard = (props: { variant?: InfoCardVariants }) => { /> - diff --git a/plugins/org/src/components/Cards/Meta/ProfileInfoGroup.tsx b/plugins/org/src/components/Cards/Meta/ProfileInfoGroup.tsx deleted file mode 100644 index 177983d4a5..0000000000 --- a/plugins/org/src/components/Cards/Meta/ProfileInfoGroup.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License 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 { ListItem, ListItemText, Divider } from '@material-ui/core'; -import React, { useMemo } from 'react'; - -const staticProfileKeys = ['displayName', 'email', 'picture']; - -export const ProfileInfoGroup = ({ - profile, -}: { - profile?: Record; -}) => { - const profileKeys = useMemo( - () => - profile !== undefined - ? Object.keys(profile).filter(key => !staticProfileKeys.includes(key)) - : [], - [profile], - ); - - if (profile === undefined || profileKeys.length === 0) { - return null; - } - - return ( - <> - - {profileKeys.map(key => { - const value = profile[key]; - - return ( - - - {key} - - - {value} - - ); - })} - - ); -}; diff --git a/plugins/org/src/components/Cards/Meta/index.ts b/plugins/org/src/components/Cards/Meta/index.ts index 5f4e5b4690..076af49bf9 100644 --- a/plugins/org/src/components/Cards/Meta/index.ts +++ b/plugins/org/src/components/Cards/Meta/index.ts @@ -14,4 +14,3 @@ * limitations under the License. */ export * from './LinksGroup'; -export * from './ProfileInfoGroup'; diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index 7abfbee389..a27be7857b 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -127,9 +127,6 @@ const extraDetailsEntity: UserEntity = { email: 'guest@example.com', picture: 'https://avatars.dicebear.com/api/avataaars/guest@example.com.svg?background=%23fff', - 'Job Title': 'Software Engineer', - Department: 'Engineering', - Location: 'San Francisco, CA', }, memberOf: ['team-a'], }, diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index 7eabdf2903..fb0df3d25f 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -183,9 +183,6 @@ describe('Edit Button', () => { profile: { displayName: 'Calum Leavy', email: 'calum-leavy@example.com', - 'Job Title': 'Software Engineer', - Department: 'Engineering', - Location: 'San Francisco, CA', }, memberOf: ['ExampleGroup'], }, @@ -209,10 +206,6 @@ describe('Edit Button', () => { }, ), ); - expect(rendered.getByText('Software Engineer')).toBeInTheDocument(); - expect(rendered.getByText('Department')).toBeInTheDocument(); - expect(rendered.getByText('San Francisco, CA')).toBeInTheDocument(); - expect(rendered.getByText('Location')).toBeInTheDocument(); expect(rendered.getByText('Slack')).toBeInTheDocument(); expect(rendered.getByText('Google')).toBeInTheDocument(); }); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 8a56dc58c2..a35c1c057b 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -46,7 +46,7 @@ import { InfoCardVariants, Link, } from '@backstage/core-components'; -import { LinksGroup, ProfileInfoGroup } from '../../Meta'; +import { LinksGroup } from '../../Meta'; const CardTitle = (props: { title?: string }) => props.title ? ( @@ -131,7 +131,6 @@ export const UserProfileCard = (props: { variant?: InfoCardVariants }) => { - From e63b1d973eccb80d66cf8c9b51e06a22e7d7789e Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Wed, 2 Nov 2022 09:59:03 +1000 Subject: [PATCH 09/10] fix missing icon Signed-off-by: Joe Patterson --- plugins/org/src/components/Cards/Meta/LinksGroup.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/org/src/components/Cards/Meta/LinksGroup.tsx b/plugins/org/src/components/Cards/Meta/LinksGroup.tsx index e826c1cf9c..91843f1582 100644 --- a/plugins/org/src/components/Cards/Meta/LinksGroup.tsx +++ b/plugins/org/src/components/Cards/Meta/LinksGroup.tsx @@ -41,11 +41,8 @@ const WebLink = ({ export const LinksGroup = ({ links }: { links?: EntityLink[] }) => { const app = useApp(); - const iconResolver = useCallback( - (key?: string): IconComponent => - key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon, - [app], - ); + const iconResolver = (key?: string): IconComponent => + key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon; if (links === undefined) { return null; From 49fc8ae6b453c8be159a4cae452de174000e3d2b Mon Sep 17 00:00:00 2001 From: Joe Patterson Date: Wed, 2 Nov 2022 10:11:00 +1000 Subject: [PATCH 10/10] fix type error in LinksGroup Signed-off-by: Joe Patterson --- plugins/org/src/components/Cards/Meta/LinksGroup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/org/src/components/Cards/Meta/LinksGroup.tsx b/plugins/org/src/components/Cards/Meta/LinksGroup.tsx index 91843f1582..eeade18411 100644 --- a/plugins/org/src/components/Cards/Meta/LinksGroup.tsx +++ b/plugins/org/src/components/Cards/Meta/LinksGroup.tsx @@ -22,7 +22,7 @@ import { ListItemText, Divider, } from '@material-ui/core'; -import React, { useCallback } from 'react'; +import React from 'react'; const WebLink = ({ href,