Skip to content

Commit

Permalink
Merge pull request backstage#4517 from andrewthauer/feat/support-button
Browse files Browse the repository at this point in the history
Add extended support button config
  • Loading branch information
andrewthauer authored Feb 24, 2021
2 parents 51e7fef + 3fca6fb commit 274ed78
Show file tree
Hide file tree
Showing 18 changed files with 319 additions and 191 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-balloons-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/core': patch
---

Added a new useSupportConfig hook that reads a new `app.support` config key. Also updated the SupportButton and ErrorPage components to use the new config.
13 changes: 13 additions & 0 deletions app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@ app:
title: Backstage Example App
baseUrl: http://localhost:3000
googleAnalyticsTrackingId: # UA-000000-0
support:
url: https://github.com/backstage/backstage/issues # Used by common ErrorPage
items: # Used by common SupportButton component
- title: Issues
icon: github
links:
- url: https://github.com/backstage/backstage/issues
title: GitHub Issues
- title: Discord Chatroom
icon: chat
links:
- url: https://discord.gg/MUpMjP2
title: '#backstage'

backend:
baseUrl: http://localhost:7000
Expand Down
4 changes: 2 additions & 2 deletions packages/core-api/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class AppContextImpl implements AppContext {
return this.app.getPlugins();
}

getSystemIcon(key: string): IconComponent {
getSystemIcon(key: IconKey): IconComponent | undefined {
return this.app.getSystemIcon(key);
}

Expand Down Expand Up @@ -206,7 +206,7 @@ export class PrivateAppImpl implements BackstageApp {
return this.plugins;
}

getSystemIcon(key: IconKey): IconComponent {
getSystemIcon(key: IconKey): IconComponent | undefined {
return this.icons[key];
}

Expand Down
4 changes: 2 additions & 2 deletions packages/core-api/src/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export type BackstageApp = {
/**
* Get a common or custom icon for this app.
*/
getSystemIcon(key: IconKey): IconComponent;
getSystemIcon(key: IconKey): IconComponent | undefined;

/**
* Provider component that should wrap the Router created with getRouter()
Expand Down Expand Up @@ -202,7 +202,7 @@ export type AppContext = {
/**
* Get a common or custom icon for this app.
*/
getSystemIcon(key: IconKey): IconComponent;
getSystemIcon(key: IconKey): IconComponent | undefined;

/**
* Get the components registered for various purposes in the app.
Expand Down
27 changes: 21 additions & 6 deletions packages/core-api/src/icons/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,46 @@
*/

import { SvgIconProps } from '@material-ui/core';
import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage';
import MuiChatIcon from '@material-ui/icons/Chat';
import MuiDashboardIcon from '@material-ui/icons/Dashboard';
import MuiEmailIcon from '@material-ui/icons/Email';
import MuiGitHubIcon from '@material-ui/icons/GitHub';
import MuiHelpIcon from '@material-ui/icons/Help';
import PeopleIcon from '@material-ui/icons/People';
import PersonIcon from '@material-ui/icons/Person';
import MuiPeopleIcon from '@material-ui/icons/People';
import MuiPersonIcon from '@material-ui/icons/Person';
import MuiWarningIcon from '@material-ui/icons/Warning';
import React from 'react';
import { useApp } from '../app/AppContext';
import { IconComponent, SystemIconKey, IconComponentMap } from './types';
import { IconComponent, IconComponentMap, SystemIconKey } from './types';

export const defaultSystemIcons: IconComponentMap = {
user: PersonIcon,
group: PeopleIcon,
brokenImage: MuiBrokenImageIcon,
chat: MuiChatIcon,
dashboard: MuiDashboardIcon,
email: MuiEmailIcon,
github: MuiGitHubIcon,
group: MuiPeopleIcon,
help: MuiHelpIcon,
user: MuiPersonIcon,
warning: MuiWarningIcon,
};

const overridableSystemIcon = (key: SystemIconKey): IconComponent => {
const Component = (props: SvgIconProps) => {
const app = useApp();
const Icon = app.getSystemIcon(key);
return <Icon {...props} />;
return Icon ? <Icon {...props} /> : <MuiBrokenImageIcon {...props} />;
};
return Component;
};

export const BrokenImageIcon = overridableSystemIcon('brokenImage');
export const ChatIcon = overridableSystemIcon('chat');
export const DashboardIcon = overridableSystemIcon('dashboard');
export const EmailIcon = overridableSystemIcon('email');
export const GitHubIcon = overridableSystemIcon('github');
export const GroupIcon = overridableSystemIcon('group');
export const HelpIcon = overridableSystemIcon('help');
export const UserIcon = overridableSystemIcon('user');
export const WarningIcon = overridableSystemIcon('warning');
11 changes: 10 additions & 1 deletion packages/core-api/src/icons/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@
import { ComponentType } from 'react';
import { SvgIconProps } from '@material-ui/core';

export type SystemIconKey = 'user' | 'group' | 'dashboard' | 'help';
export type SystemIconKey =
| 'brokenImage'
| 'chat'
| 'dashboard'
| 'email'
| 'github'
| 'group'
| 'help'
| 'user'
| 'warning';

export type IconComponent = ComponentType<SvgIconProps>;
export type IconKey = SystemIconKey | string;
Expand Down
35 changes: 35 additions & 0 deletions packages/core/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,41 @@ export interface Config {
* @visibility frontend
*/
title?: string;

/**
* Information about support of this Backstage instance and how to contact the integrator team.
*/
support?: {
/**
* The primary support url.
* @visibility frontend
*/
url: string;
/**
* A list of categorized support item groupings.
*/
items: {
/**
* The title of the support item grouping.
* @visibility frontend
*/
title: string;
/**
* An optional icon for the support item grouping.
* @visibility frontend
*/
icon?: string;
/**
* A list of support links for the Backstage instance.
*/
links: {
/** @visibility frontend */
url: string;
/** @visibility frontend */
title?: string;
}[];
}[];
};
};

/**
Expand Down
132 changes: 50 additions & 82 deletions packages/core/src/components/SupportButton/SupportButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,72 @@
* limitations under the License.
*/

import React, {
Fragment,
useState,
MouseEventHandler,
PropsWithChildren,
} from 'react';
import { HelpIcon, useApp } from '@backstage/core-api';
import {
Button,
Link,
List,
ListItem,
ListItemIcon,
Popover,
Typography,
makeStyles,
ListItemText,
makeStyles,
Popover,
} from '@material-ui/core';
import GroupIcon from '@material-ui/icons/Group';
import HelpIcon from '@material-ui/icons/Help';

// import { EmailIcon, SlackIcon, SupportIcon } from 'shared/icons';
// import { Button, Link } from 'shared/components';
// import { StackOverflow, StackOverflowTag } from 'shared/components/layout';
import React, {
Fragment,
MouseEventHandler,
PropsWithChildren,
useState,
} from 'react';
import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks';
import { Link } from '../Link';

type Props = {
slackChannel?: string | string[];
email?: string | string[];
plugin?: any;
};
type Props = {};

const useStyles = makeStyles(theme => ({
leftIcon: {
marginRight: theme.spacing(1),
},
popoverList: {
minWidth: 260,
maxWidth: 320,
maxWidth: 400,
},
}));

export const SupportButton = ({
slackChannel = '#backstage',
email = [],
children,
}: // plugin,
PropsWithChildren<Props>) => {
// TODO: get plugin manifest with hook
const SupportIcon = ({ icon }: { icon: string | undefined }) => {
const app = useApp();
const Icon = icon ? app.getSystemIcon(icon) ?? HelpIcon : HelpIcon;
return <Icon />;
};

const SupportLink = ({ link }: { link: SupportItemLink }) => (
<Link to={link.url} target="_blank" rel="noreferrer noopener">
{link.title ?? link.url}
</Link>
);

const SupportListItem = ({ item }: { item: SupportItem }) => {
return (
<ListItem>
<ListItemIcon>
<SupportIcon icon={item.icon} />
</ListItemIcon>
<ListItemText
primary={item.title}
secondary={
<>
{item.links &&
item.links.map(link => (
<SupportLink link={link} key={link.url} />
))}
</>
}
/>
</ListItem>
);
};

export const SupportButton = ({ children }: PropsWithChildren<Props>) => {
const { items } = useSupportConfig();

const [popoverOpen, setPopoverOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
Expand All @@ -75,12 +94,6 @@ PropsWithChildren<Props>) => {
setPopoverOpen(false);
};

// const tags = plugin ? plugin.stackoverflowTags : undefined;
const slackChannels = Array.isArray(slackChannel)
? slackChannel
: [slackChannel];
const contactEmails = Array.isArray(email) ? email : [email];

return (
<Fragment>
<Button
Expand Down Expand Up @@ -111,53 +124,8 @@ PropsWithChildren<Props>) => {
{child}
</ListItem>
))}
{/* {tags && tags.length > 0 && (
<ListItem alignItems="flex-start">
<StackOverflow>
{tags.map((tag, i) => (
<StackOverflowTag key={i} tag={tag} />
))}
</StackOverflow>
</ListItem>
)} */}
{slackChannels && (
<ListItem>
<ListItemIcon>
<GroupIcon />
</ListItemIcon>
<ListItemText
disableTypography
primary={<Typography>Support</Typography>}
secondary={
<div>
{slackChannels.map((channel, i) => (
<Link key={i}>{channel}</Link>
))}
</div>
}
/>
</ListItem>
)}
{contactEmails.length > 0 && (
<ListItem>
<ListItemIcon>
<GroupIcon />
</ListItemIcon>
<ListItemText
disableTypography
primary={<Typography>Contact</Typography>}
secondary={
<div>
{contactEmails.map((em, index) => (
<Typography key={index}>
<Link>{em}</Link>
</Typography>
))}
</div>
}
/>
</ListItem>
)}
{items &&
items.map((item, i) => <SupportListItem item={item} key={i} />)}
</List>
</Popover>
</Fragment>
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@
*/

export { useQueryParamState } from './useQueryParamState';
export { useSupportConfig } from './useSupportConfig';
export type {
SupportConfig,
SupportItem,
SupportItemLink,
} from './useSupportConfig';
Loading

0 comments on commit 274ed78

Please sign in to comment.