Move <UserSettings/> to a user-settings plugin

This commit is contained in:
Marcus Eide
2020-09-18 11:19:05 +02:00
parent 3cfdbd10ad
commit 07e08834a5
28 changed files with 349 additions and 27 deletions
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+13
View File
@@ -0,0 +1,13 @@
# user-settings
Welcome to the user-settings plugin!
_This plugin was created through the Backstage CLI_
## Getting started
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/user-settings](http://localhost:3000/user-settings).
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { createDevApp } from '@backstage/dev-utils';
import { plugin } from '../src/plugin';
createDevApp().registerPlugin(plugin).render();
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@backstage/plugin-user-settings",
"version": "0.1.1-alpha.21",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"scripts": {
"build": "backstage-cli plugin:build",
"start": "backstage-cli plugin:serve",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"diff": "backstage-cli plugin:diff",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-use": "^15.3.3"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
"@backstage/dev-utils": "^0.1.1-alpha.21",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"jest-fetch-mock": "^3.0.3"
},
"files": [
"dist"
]
}
@@ -0,0 +1,26 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React from 'react';
import { List, ListSubheader } from '@material-ui/core';
import { SidebarThemeToggle } from './ThemeToggle';
import { SidebarPinButton } from './PinButton';
export const AppSettingsList = () => (
<List dense subheader={<ListSubheader>App Settings</ListSubheader>}>
<SidebarThemeToggle />
<SidebarPinButton />
</List>
);
@@ -0,0 +1,28 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React from 'react';
import { List, ListSubheader } from '@material-ui/core';
type Props = {
providerSettings: React.ReactNode;
};
export const AuthProvidersList = ({ providerSettings }: Props) => (
<List subheader={<ListSubheader>Available Auth Providers</ListSubheader>}>
{providerSettings}
</List>
);
@@ -0,0 +1,90 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
configApiRef,
githubAuthApiRef,
gitlabAuthApiRef,
googleAuthApiRef,
oauth2ApiRef,
oktaAuthApiRef,
microsoftAuthApiRef,
samlAuthApiRef,
useApi,
} from '@backstage/core';
import Star from '@material-ui/icons/Star';
import React from 'react';
import { OAuthProviderSettings } from './OAuthProviderSettings';
import { OIDCProviderSettings } from './OIDCProviderSettings';
export const DefaultProviderSettings = () => {
const configApi = useApi(configApiRef);
const providersConfig = configApi.getOptionalConfig('auth.providers');
const providers = providersConfig?.keys() ?? [];
return (
<>
{providers.includes('google') && (
<ProviderSettingsItem
title="Google"
apiRef={googleAuthApiRef}
icon={Star}
/>
)}
{providers.includes('microsoft') && (
<ProviderSettingsItem
title="Microsoft"
apiRef={microsoftAuthApiRef}
icon={Star}
/>
)}
{providers.includes('github') && (
<ProviderSettingsItem
title="Github"
apiRef={githubAuthApiRef}
icon={Star}
/>
)}
{providers.includes('gitlab') && (
<ProviderSettingsItem
title="Gitlab"
apiRef={gitlabAuthApiRef}
icon={Star}
/>
)}
{providers.includes('okta') && (
<ProviderSettingsItem
title="Okta"
apiRef={oktaAuthApiRef}
icon={Star}
/>
)}
{providers.includes('saml') && (
<ProviderSettingsItem
title="SAML"
apiRef={samlAuthApiRef}
icon={Star}
/>
)}
{providers.includes('oauth2') && (
<ProviderSettingsItem
title="YourOrg"
apiRef={oauth2ApiRef}
icon={Star}
/>
)}
</>
);
};
@@ -0,0 +1,69 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React from 'react';
import { FeatureFlagName, useApi, featureFlagsApiRef } from '@backstage/core';
import {
ListItem,
ListItemSecondaryAction,
ListItemText,
Tooltip,
} from '@material-ui/core';
import CheckIcon from '@material-ui/icons/CheckCircle';
import { ToggleButton } from '@material-ui/lab';
export type Item = {
name: FeatureFlagName;
pluginId: string;
};
type Props = {
featureFlag: Item;
};
export const FlagItem = ({ featureFlag }: Props) => {
const api = useApi(featureFlagsApiRef);
const [enabled, setEnabled] = React.useState(
Boolean(api.getFlags().get(featureFlag.name)),
);
const toggleFlag = () => {
const newState = api.getFlags().toggle(featureFlag.name);
setEnabled(Boolean(newState));
};
return (
<ListItem>
<ListItemText
primary={featureFlag.name}
secondary={`Registered in ${featureFlag.pluginId} plugin`}
/>
<ListItemSecondaryAction>
<ToggleButton
size="small"
value="flag"
selected={enabled}
onChange={toggleFlag}
>
<Tooltip placement="top" arrow title={enabled ? 'Disable' : 'Enable'}>
<CheckIcon />
</Tooltip>
</ToggleButton>
</ListItemSecondaryAction>
</ListItem>
);
};
@@ -0,0 +1,32 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React from 'react';
import List from '@material-ui/core/List';
import ListSubheader from '@material-ui/core/ListSubheader';
import { FlagItem, Item } from './FeatureFlagsItem';
type Props = {
featureFlags: Item[];
};
export const FeatureFlagsList = ({ featureFlags }: Props) => (
<List dense subheader={<ListSubheader>Feature Flags</ListSubheader>}>
{featureFlags.map(featureFlag => (
<FlagItem key={featureFlag.name} featureFlag={featureFlag} />
))}
</List>
);
@@ -0,0 +1,80 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
ApiRef,
OAuthApi,
SessionStateApi,
useApi,
Subscription,
IconComponent,
SessionState,
} from '@backstage/core';
import React, { FC, useState, useEffect } from 'react';
import { ProviderSettingsItem } from './ProviderSettingsItem';
type OAuthProviderSidebarProps = {
title: string;
icon: IconComponent;
apiRef: ApiRef<OAuthApi & SessionStateApi>;
};
export const OAuthProviderSettings: FC<OAuthProviderSidebarProps> = ({
title,
icon,
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
let didCancel = false;
const checkSession = async () => {
const session = await api.getAccessToken('', { optional: true });
if (!didCancel) {
setSignedIn(!!session);
}
};
let subscription: Subscription;
const observeSession = () => {
subscription = api
.sessionState$()
.subscribe((sessionState: SessionState) => {
if (!didCancel) {
setSignedIn(sessionState === SessionState.SignedIn);
}
});
};
checkSession();
observeSession();
return () => {
didCancel = true;
subscription.unsubscribe();
};
}, [api]);
return (
<ProviderSettingsItem
title={title}
icon={icon}
signedIn={signedIn}
api={api}
signInHandler={() => api.getAccessToken()}
/>
);
};
@@ -0,0 +1,81 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
ApiRef,
OpenIdConnectApi,
SessionStateApi,
useApi,
Subscription,
IconComponent,
SessionState,
} from '@backstage/core';
import React, { FC, useState, useEffect } from 'react';
import { ProviderSettingsItem } from './ProviderSettingsItem';
export type OIDCProviderSidebarProps = {
title: string;
icon: IconComponent;
apiRef: ApiRef<OpenIdConnectApi & SessionStateApi>;
};
export const OIDCProviderSettings: FC<OIDCProviderSidebarProps> = ({
title,
icon,
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
let didCancel = false;
const checkSession = async () => {
const session = await api.getIdToken({ optional: true });
if (!didCancel) {
setSignedIn(!!session);
}
};
let subscription: Subscription;
const observeSession = () => {
subscription = api
.sessionState$()
.subscribe((sessionState: SessionState) => {
if (!didCancel) {
setSignedIn(sessionState === SessionState.SignedIn);
}
});
};
checkSession();
observeSession();
return () => {
didCancel = true;
subscription.unsubscribe();
};
}, [api]);
return (
<ProviderSettingsItem
title={title}
icon={icon}
signedIn={signedIn}
api={api}
signInHandler={() => api.getIdToken()}
/>
);
};
@@ -0,0 +1,64 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React, { useContext } from 'react';
import {
ListItem,
ListItemSecondaryAction,
ListItemText,
Tooltip,
} from '@material-ui/core';
import LockIcon from '@material-ui/icons/Lock';
import LockOpenIcon from '@material-ui/icons/LockOpen';
import { ToggleButton } from '@material-ui/lab';
import { SidebarPinStateContext } from '@backstage/core';
export const SidebarPinButton = () => {
const { isPinned, toggleSidebarPinState } = useContext(
SidebarPinStateContext,
);
const PinIcon = () => (
<Tooltip
placement="top"
arrow
title={`${isPinned ? 'Unpin' : 'Pin'} Sidebar`}
>
{isPinned ? <LockIcon /> : <LockOpenIcon />}
</Tooltip>
);
return (
<ListItem>
<ListItemText
primary="Pin Sidebar"
secondary="Prevent the sidebar from collapsing"
/>
<ListItemSecondaryAction>
<ToggleButton
size="small"
value="pin"
selected={isPinned}
onChange={() => {
toggleSidebarPinState();
}}
>
<PinIcon />
</ToggleButton>
</ListItemSecondaryAction>
</ListItem>
);
};
@@ -0,0 +1,91 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React from 'react';
import { IconComponent, OAuthApi, OpenIdConnectApi } from '@backstage/core';
import {
ListItem,
ListItemIcon,
ListItemSecondaryAction,
ListItemText,
Tooltip,
} from '@material-ui/core';
import PowerButton from '@material-ui/icons/PowerSettingsNew';
import { ToggleButton } from '@material-ui/lab';
import {
ApiRef,
SessionApi,
useApi,
IconComponent,
SessionState,
} from '@backstage/core-api';
type OAuthProviderSidebarProps = {
title: string;
icon: IconComponent;
apiRef: ApiRef<SessionApi>;
};
export const ProviderSettingsItem: FC<OAuthProviderSidebarProps> = ({
title,
icon: Icon,
apiRef,
}) => {
const api = useApi(apiRef);
const [signedIn, setSignedIn] = useState(false);
useEffect(() => {
let didCancel = false;
const subscription = api
.sessionState$()
.subscribe((sessionState: SessionState) => {
if (!didCancel) {
setSignedIn(sessionState === SessionState.SignedIn);
}
});
return () => {
didCancel = true;
subscription.unsubscribe();
};
}, [api]);
return (
<ListItem>
<ListItemIcon>
<Icon />
</ListItemIcon>
<ListItemText primary={title} />
<ListItemSecondaryAction>
<ToggleButton
size="small"
value={title}
selected={signedIn}
onChange={() => (signedIn ? api.signOut() : api.signIn())}
>
<Tooltip
placement="top"
arrow
title={signedIn ? `Sign out from ${title}` : `Sign in to ${title}`}
>
<PowerButton />
</Tooltip>
</ToggleButton>
</ListItemSecondaryAction>
</ListItem>
);
};
@@ -0,0 +1,74 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React from 'react';
import {
Card,
CardContent,
CardHeader,
makeStyles,
Divider,
} from '@material-ui/core';
import { AppSettingsList } from './AppSettingsList';
import { AuthProvidersList } from './AuthProviderList';
import { FeatureFlagsList } from './FeatureFlagsList';
import { SignInAvatar } from './SignInAvatar';
import { UserSettingsMenu } from './UserSettingsMenu';
import { useUserProfile } from './useUserProfileInfo';
import { useApi, featureFlagsApiRef } from '@backstage/core';
const useStyles = makeStyles({
root: {
minWidth: 400,
},
});
type Props = {
providerSettings?: React.ReactNode;
};
export const SettingsDialog = ({ providerSettings }: Props) => {
const classes = useStyles();
const { profile, displayName } = useUserProfile();
const featureFlagsApi = useApi(featureFlagsApiRef);
const featureFlags = featureFlagsApi.getRegisteredFlags();
return (
<Card className={classes.root}>
<CardHeader
avatar={<SignInAvatar size={48} />}
action={<UserSettingsMenu />}
title={displayName}
subheader={profile.email}
/>
<CardContent>
<AppSettingsList />
{providerSettings && (
<>
<Divider />
<AuthProvidersList providerSettings={providerSettings} />
</>
)}
{featureFlags.length > 0 && (
<>
<Divider />
<FeatureFlagsList featureFlags={featureFlags} />
</>
)}
</CardContent>
</Card>
);
};
@@ -0,0 +1,42 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React from 'react';
import { BackstageTheme } from '@backstage/theme';
import { makeStyles, Avatar } from '@material-ui/core';
import { useUserProfile } from './useUserProfileInfo';
import { sidebarConfig } from '@backstage/core';
const useStyles = makeStyles<BackstageTheme, { size: number }>({
avatar: {
width: ({ size }) => size,
height: ({ size }) => size,
},
});
type Props = { size?: number };
export const SignInAvatar = ({ size }: Props) => {
const { iconSize } = sidebarConfig;
const classes = useStyles(size ? { size } : { size: iconSize });
const { profile, displayName } = useUserProfile();
return (
<Avatar src={profile.picture} className={classes.avatar}>
{displayName[0]}
</Avatar>
);
};
@@ -0,0 +1,87 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React from 'react';
import { useObservable } from 'react-use';
import LightIcon from '@material-ui/icons/WbSunny';
import DarkIcon from '@material-ui/icons/Brightness2';
import AutoIcon from '@material-ui/icons/BrightnessAuto';
import { appThemeApiRef, useApi } from '@backstage/core';
import ToggleButton from '@material-ui/lab/ToggleButton';
import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup';
import {
ListItem,
ListItemText,
ListItemSecondaryAction,
Tooltip,
} from '@material-ui/core';
export const SidebarThemeToggle = () => {
const appThemeApi = useApi(appThemeApiRef);
const themeId = useObservable(
appThemeApi.activeThemeId$(),
appThemeApi.getActiveThemeId(),
);
const themeIds = appThemeApi.getInstalledThemes();
// TODO(marcuseide): can these be put on the theme itself?
const themeIcons = {
dark: <DarkIcon />,
light: <LightIcon />,
};
const handleSetTheme = (
_event: React.MouseEvent<HTMLElement>,
newThemeId: string | undefined,
) => {
if (themeIds.some(t => t.id === newThemeId)) {
appThemeApi.setActiveThemeId(newThemeId);
} else {
appThemeApi.setActiveThemeId(undefined);
}
};
return (
<ListItem>
<ListItemText primary="Theme" secondary="Change the theme mode" />
<ListItemSecondaryAction>
<ToggleButtonGroup
exclusive
size="small"
value={themeId ?? 'auto'}
onChange={handleSetTheme}
>
{themeIds.map(theme => (
<ToggleButton key={theme.id} value={theme.variant}>
<Tooltip
placement="top"
arrow
title={`Select ${theme.variant} theme`}
>
{themeIcons[theme.variant]}
</Tooltip>
</ToggleButton>
))}
<ToggleButton value="auto">
<Tooltip placement="top" arrow title="Select auto theme">
<AutoIcon />
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
</ListItemSecondaryAction>
</ListItem>
);
};
@@ -0,0 +1,76 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React, { useEffect, useContext } from 'react';
import { Popover } from '@material-ui/core';
import { SignInAvatar } from './SignInAvatar';
import { SettingsDialog } from './SettingsDialog';
import { SidebarItem, SidebarContext } from '@backstage/core';
import { useUserProfile } from './useUserProfileInfo';
type Props = {
providerSettings?: React.ReactNode;
};
export const UserSettings = ({ providerSettings }: Props) => {
const { isOpen: sidebarOpen } = useContext(SidebarContext);
const { displayName } = useUserProfile();
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | undefined>(
undefined,
);
const handleOpen = (event?: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event?.currentTarget ?? undefined);
setOpen(true);
};
const handleClose = () => {
setAnchorEl(undefined);
setOpen(false);
};
useEffect(() => {
if (!sidebarOpen && open) setOpen(false);
}, [open, sidebarOpen]);
const SidebarAvatar = () => <SignInAvatar />;
return (
<>
<SidebarItem
text={displayName}
onClick={handleOpen}
icon={SidebarAvatar}
/>
<Popover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: 'center',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<SettingsDialog providerSettings={providerSettings} />
</Popover>
</>
);
};
@@ -0,0 +1,55 @@
/*
* Copyright 2020 Spotify AB
*
* 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 React from 'react';
import { identityApiRef, useApi } from '@backstage/core';
import { IconButton, ListItemIcon, Menu, MenuItem } from '@material-ui/core';
import SignOutIcon from '@material-ui/icons/MeetingRoom';
import MoreVertIcon from '@material-ui/icons/MoreVert';
export const UserSettingsMenu = () => {
const identityApi = useApi(identityApiRef);
const [open, setOpen] = React.useState(false);
const [anchorEl, setAnchorEl] = React.useState<undefined | HTMLElement>(
undefined,
);
const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
setOpen(true);
};
const handleClose = () => {
setAnchorEl(undefined);
setOpen(false);
};
return (
<>
<IconButton onClick={handleOpen}>
<MoreVertIcon />
</IconButton>
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={() => identityApi.signOut()}>
<ListItemIcon>
<SignOutIcon />
</ListItemIcon>
Sign Out
</MenuItem>
</Menu>
</>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { UserSettings } from './UserSettings';
@@ -0,0 +1,26 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { useApi, identityApiRef } from '@backstage/core';
export const useUserProfile = () => {
const identityApi = useApi(identityApiRef);
const userId = identityApi.getUserId();
const profile = identityApi.getProfile();
const displayName = profile.displayName ?? userId;
return { profile, displayName };
};
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { plugin } from './plugin';
export { UserSettings } from './components/UserSettings';
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { plugin } from './plugin';
describe('user-settings', () => {
it('should export plugin', () => {
expect(plugin).toBeDefined();
});
});
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { createPlugin } from '@backstage/core';
// import ExampleComponent from './components/ExampleComponent';
// export const rootRouteRef = createRouteRef({
// path: '/user-settings',
// title: 'user-settings',
// });
export const plugin = createPlugin({
id: 'user-settings',
// apis: [createApiFactory(GCPApiRef, new GCPClient())],
// register({ router }) {
// router.addRoute(rootRouteRef, ExampleComponent);
// },
});
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2020 Spotify AB
*
* 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 '@testing-library/jest-dom';
require('jest-fetch-mock').enableMocks();