Merge pull request #2659 from spotify/eide/user-settings-plugin
Plugin: user-settings
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint')],
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
# user-settings
|
||||
|
||||
Welcome to the user-settings plugin!
|
||||
|
||||
_This plugin was created through the Backstage CLI_
|
||||
|
||||
## About the plugin
|
||||
|
||||
This plugin provides two components, `<UserSettings />` is intended to be used within the [`<Sidebar>`](https://backstage.io/storybook/?path=/story/sidebar--sample-sidebar) and displays the signed-in users profile picture and name.
|
||||
|
||||
The second component is a settings page where the user can control different settings across the App.
|
||||
|
||||
## Usage
|
||||
|
||||
Add the item to the Sidebar:
|
||||
|
||||
```ts
|
||||
import { Settings as SidebarSettings } from '@backstage/plugin-user-settings';
|
||||
|
||||
<SidebarPage>
|
||||
<Sidebar>
|
||||
<SidebarSettings />
|
||||
</Sidebar>
|
||||
</SidebarPage>;
|
||||
```
|
||||
|
||||
Add the page to the App routing:
|
||||
|
||||
```ts
|
||||
import { Router as SettingsRouter } from '@backstage/plugin-user-settings';
|
||||
|
||||
const AppRoutes = () => (
|
||||
<Routes>
|
||||
<Route path="/settings" element={<SettingsRouter />} />
|
||||
</Routes>
|
||||
);
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
**Auth Providers**
|
||||
|
||||
By default, the plugin provides a list of configured authentication providers fetched from `app-config.yaml` and displayed in the "Authentication Providers" tab.
|
||||
|
||||
If you want to supply your own custom list of Authentication Providers, use the `providerSettings` prop:
|
||||
|
||||
```ts
|
||||
const MyAuthProviders = () => (
|
||||
<ListItem>
|
||||
<ListItemText primary="example" />
|
||||
<ListItemSecondaryAction>{someAction}</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
const AppRoutes = () => (
|
||||
<Routes>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={<SettingsRouter providerSettings={<MyAuthProviders />} />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
```
|
||||
|
||||
> **Note that the list of providers expects to be rendered within a MUI [`<List>`](https://material-ui.com/components/lists/)**
|
||||
@@ -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();
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@backstage/plugin-user-settings",
|
||||
"version": "0.1.1-alpha.24",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"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.24",
|
||||
"@backstage/theme": "^0.1.1-alpha.24",
|
||||
"@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-router": "6.0.0-beta.0",
|
||||
"react-use": "^15.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.24",
|
||||
"@backstage/dev-utils": "^0.1.1-alpha.24",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.24",
|
||||
"@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",
|
||||
"msw": "^0.20.5",
|
||||
"node-fetch": "^2.6.1"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
configApiRef,
|
||||
ConfigReader,
|
||||
googleAuthApiRef,
|
||||
} from '@backstage/core';
|
||||
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { AuthProviders } from './AuthProviders';
|
||||
|
||||
const mockSignInHandler = jest.fn().mockReturnValue('');
|
||||
const mockGoogleAuth = {
|
||||
sessionState$: () => ({
|
||||
subscribe: () => ({
|
||||
unsubscribe: () => null,
|
||||
}),
|
||||
}),
|
||||
signIn: mockSignInHandler,
|
||||
};
|
||||
|
||||
const createConfig = () =>
|
||||
ConfigReader.fromConfigs([
|
||||
{
|
||||
context: '',
|
||||
data: {
|
||||
auth: {
|
||||
providers: {
|
||||
google: { development: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const config = createConfig();
|
||||
|
||||
const apiRegistry = ApiRegistry.from([
|
||||
[configApiRef, config],
|
||||
[googleAuthApiRef, mockGoogleAuth],
|
||||
]);
|
||||
|
||||
describe('<AuthProviders />', () => {
|
||||
it('displays a provider and calls its sign-in handler on click', async () => {
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<AuthProviders />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(rendered.getByText('Google')).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.getByText(googleAuthApiRef.description),
|
||||
).toBeInTheDocument();
|
||||
|
||||
const button = rendered.getByTitle('Sign in to Google');
|
||||
fireEvent.click(button);
|
||||
expect(mockSignInHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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';
|
||||
import { configApiRef, InfoCard, useApi } from '@backstage/core';
|
||||
import { EmptyProviders } from './EmptyProviders';
|
||||
import { DefaultProviderSettings } from './DefaultProviderSettings';
|
||||
|
||||
type Props = {
|
||||
providerSettings?: JSX.Element;
|
||||
};
|
||||
|
||||
export const AuthProviders = ({ providerSettings }: Props) => {
|
||||
const configApi = useApi(configApiRef);
|
||||
const providersConfig = configApi.getOptionalConfig('auth.providers');
|
||||
const configuredProviders = providersConfig?.keys() || [];
|
||||
const providers = providerSettings ?? (
|
||||
<DefaultProviderSettings configuredProviders={configuredProviders} />
|
||||
);
|
||||
|
||||
if (!providerSettings && !configuredProviders?.length) {
|
||||
return <EmptyProviders />;
|
||||
}
|
||||
|
||||
return (
|
||||
<InfoCard>
|
||||
<List dense>{providers}</List>
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 {
|
||||
githubAuthApiRef,
|
||||
gitlabAuthApiRef,
|
||||
googleAuthApiRef,
|
||||
oauth2ApiRef,
|
||||
oktaAuthApiRef,
|
||||
microsoftAuthApiRef,
|
||||
} from '@backstage/core';
|
||||
import Star from '@material-ui/icons/Star';
|
||||
import React from 'react';
|
||||
import { ProviderSettingsItem } from './ProviderSettingsItem';
|
||||
|
||||
type Props = {
|
||||
configuredProviders: string[];
|
||||
};
|
||||
|
||||
export const DefaultProviderSettings = ({ configuredProviders }: Props) => (
|
||||
<>
|
||||
{configuredProviders.includes('google') && (
|
||||
<ProviderSettingsItem
|
||||
title="Google"
|
||||
description={googleAuthApiRef.description}
|
||||
apiRef={googleAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
)}
|
||||
{configuredProviders.includes('microsoft') && (
|
||||
<ProviderSettingsItem
|
||||
title="Microsoft"
|
||||
description={microsoftAuthApiRef.description}
|
||||
apiRef={microsoftAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
)}
|
||||
{configuredProviders.includes('github') && (
|
||||
<ProviderSettingsItem
|
||||
title="Github"
|
||||
description={githubAuthApiRef.description}
|
||||
apiRef={githubAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
)}
|
||||
{configuredProviders.includes('gitlab') && (
|
||||
<ProviderSettingsItem
|
||||
title="Gitlab"
|
||||
description={gitlabAuthApiRef.description}
|
||||
apiRef={gitlabAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
)}
|
||||
{configuredProviders.includes('okta') && (
|
||||
<ProviderSettingsItem
|
||||
title="Okta"
|
||||
description={oktaAuthApiRef.description}
|
||||
apiRef={oktaAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
)}
|
||||
{configuredProviders.includes('oauth2') && (
|
||||
<ProviderSettingsItem
|
||||
title="YourOrg"
|
||||
description={oauth2ApiRef.description}
|
||||
apiRef={oauth2ApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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 { CodeSnippet, EmptyState } from '@backstage/core';
|
||||
import { Button, Typography } from '@material-ui/core';
|
||||
|
||||
const EXAMPLE = `auth:
|
||||
providers:
|
||||
google:
|
||||
development:
|
||||
clientId:
|
||||
$env: AUTH_GOOGLE_CLIENT_ID
|
||||
clientSecret:
|
||||
$env: AUTH_GOOGLE_CLIENT_SECRET
|
||||
`;
|
||||
|
||||
export const EmptyProviders = () => (
|
||||
<EmptyState
|
||||
missing="content"
|
||||
title="No Authentication Providers"
|
||||
description="You can add Authentication Providers to Backstage which allows you to use these providers to authenticate yourself."
|
||||
action={
|
||||
<>
|
||||
<Typography variant="body1">
|
||||
Open <code>app-config.yaml</code> and make the changes as highlighted
|
||||
below:
|
||||
</Typography>
|
||||
<CodeSnippet
|
||||
text={EXAMPLE}
|
||||
language="yaml"
|
||||
showLineNumbers
|
||||
highlightedNumbers={[3, 4, 5, 6, 7, 8]}
|
||||
customStyle={{ background: 'inherit', fontSize: '115%' }}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
href="https://backstage.io/docs/auth/add-auth-provider"
|
||||
>
|
||||
Read More
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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, useState } from 'react';
|
||||
import {
|
||||
ApiRef,
|
||||
SessionApi,
|
||||
useApi,
|
||||
IconComponent,
|
||||
SessionState,
|
||||
} 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';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: IconComponent;
|
||||
apiRef: ApiRef<SessionApi>;
|
||||
};
|
||||
|
||||
export const ProviderSettingsItem = ({
|
||||
title,
|
||||
description,
|
||||
icon: Icon,
|
||||
apiRef,
|
||||
}: Props) => {
|
||||
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}
|
||||
secondary={
|
||||
<Tooltip placement="top" arrow title={description}>
|
||||
<span>{description}</span>
|
||||
</Tooltip>
|
||||
}
|
||||
secondaryTypographyProps={{ noWrap: true, style: { width: '80%' } }}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<Tooltip
|
||||
placement="top"
|
||||
arrow
|
||||
title={signedIn ? `Sign out from ${title}` : `Sign in to ${title}`}
|
||||
>
|
||||
<ToggleButton
|
||||
size="small"
|
||||
value={title}
|
||||
selected={signedIn}
|
||||
onChange={() => (signedIn ? api.signOut() : api.signIn())}
|
||||
>
|
||||
<PowerButton color={signedIn ? 'primary' : undefined} />
|
||||
</ToggleButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export { AuthProviders } from './AuthProviders';
|
||||
export { DefaultProviderSettings } from './DefaultProviderSettings';
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 { CodeSnippet, EmptyState } from '@backstage/core';
|
||||
import { Button, Typography } from '@material-ui/core';
|
||||
|
||||
const EXAMPLE = `import { createPlugin } from '@backstage/core';
|
||||
|
||||
export default createPlugin({
|
||||
id: 'welcome',
|
||||
register({ router, featureFlags }) {
|
||||
featureFlags.register('enable-example-feature');
|
||||
},
|
||||
});
|
||||
`;
|
||||
|
||||
export const EmptyFlags = () => (
|
||||
<EmptyState
|
||||
missing="content"
|
||||
title="No Feature Flags"
|
||||
description="Feature Flags makes it possible for plugins to register features in Backstage for users to opt into. You can use this to split out logic in your code for manual A/B testing, etc."
|
||||
action={
|
||||
<>
|
||||
<Typography variant="body1">
|
||||
An example how how to add a feature flags is highlighted below:
|
||||
</Typography>
|
||||
<CodeSnippet
|
||||
text={EXAMPLE}
|
||||
language="typescript"
|
||||
showLineNumbers
|
||||
highlightedNumbers={[6]}
|
||||
customStyle={{ background: 'inherit', fontSize: '115%' }}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
href="https://backstage.io/docs/api/utility-apis"
|
||||
>
|
||||
Read More
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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, { useCallback, useState } from 'react';
|
||||
import {
|
||||
FeatureFlagName,
|
||||
featureFlagsApiRef,
|
||||
FeatureFlagsRegistryItem,
|
||||
FeatureFlagState,
|
||||
InfoCard,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
import { List } from '@material-ui/core';
|
||||
import { EmptyFlags } from './EmptyFlags';
|
||||
import { FlagItem } from './FeatureFlagsItem';
|
||||
|
||||
export const FeatureFlags = () => {
|
||||
const featureFlagsApi = useApi(featureFlagsApiRef);
|
||||
const featureFlags = featureFlagsApi.getRegisteredFlags();
|
||||
const initialFlagState = featureFlags.reduce(
|
||||
(result, featureFlag: FeatureFlagsRegistryItem) => {
|
||||
const state = featureFlagsApi.getFlags().get(featureFlag.name);
|
||||
|
||||
result[featureFlag.name] = state;
|
||||
return result;
|
||||
},
|
||||
{} as Record<FeatureFlagName, FeatureFlagState>,
|
||||
);
|
||||
|
||||
const [state, setState] = useState<Record<FeatureFlagName, FeatureFlagState>>(
|
||||
initialFlagState,
|
||||
);
|
||||
|
||||
const toggleFlag = useCallback(
|
||||
(flagName: FeatureFlagName) => {
|
||||
const newState = featureFlagsApi.getFlags().toggle(flagName);
|
||||
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
[flagName]: newState,
|
||||
}));
|
||||
featureFlagsApi.getFlags().save();
|
||||
},
|
||||
[featureFlagsApi],
|
||||
);
|
||||
|
||||
if (!featureFlags.length) {
|
||||
return <EmptyFlags />;
|
||||
}
|
||||
|
||||
return (
|
||||
<InfoCard>
|
||||
<List dense>
|
||||
{featureFlags.map(featureFlag => {
|
||||
const enabled = Boolean(state[featureFlag.name]);
|
||||
|
||||
return (
|
||||
<FlagItem
|
||||
key={featureFlag.name}
|
||||
flag={featureFlag}
|
||||
enabled={enabled}
|
||||
toggleHandler={toggleFlag}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</InfoCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 {
|
||||
ListItem,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText,
|
||||
Tooltip,
|
||||
} from '@material-ui/core';
|
||||
import CheckIcon from '@material-ui/icons/CheckCircle';
|
||||
import { ToggleButton } from '@material-ui/lab';
|
||||
import { FeatureFlagsRegistryItem } from '@backstage/core';
|
||||
|
||||
type Props = {
|
||||
flag: FeatureFlagsRegistryItem;
|
||||
enabled: boolean;
|
||||
toggleHandler: Function;
|
||||
};
|
||||
|
||||
export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => (
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary={flag.name}
|
||||
secondary={`Registered in ${flag.pluginId} plugin`}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<Tooltip placement="top" arrow title={enabled ? 'Disable' : 'Enable'}>
|
||||
<ToggleButton
|
||||
size="small"
|
||||
value="flag"
|
||||
selected={enabled}
|
||||
onChange={() => toggleHandler(flag.name)}
|
||||
>
|
||||
<CheckIcon color={enabled ? 'primary' : undefined} />
|
||||
</ToggleButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
@@ -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 { FeatureFlags } from './FeatureFlags';
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 { InfoCard } from '@backstage/core';
|
||||
import { Grid, List } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { PinButton } from './PinButton';
|
||||
import { Profile } from './Profile';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
|
||||
export const General = () => (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item md={12}>
|
||||
<Profile />
|
||||
</Grid>
|
||||
<Grid item md={12}>
|
||||
<InfoCard>
|
||||
<List dense>
|
||||
<ThemeToggle />
|
||||
<PinButton />
|
||||
</List>
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
@@ -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 { SidebarPinStateContext } from '@backstage/core';
|
||||
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { PinButton } from './PinButton';
|
||||
|
||||
describe('<PinButton />', () => {
|
||||
it('toggles the pin sidebar button', async () => {
|
||||
const mockToggleFn = jest.fn();
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<SidebarPinStateContext.Provider
|
||||
value={{ isPinned: false, toggleSidebarPinState: mockToggleFn }}
|
||||
>
|
||||
<PinButton />
|
||||
</SidebarPinStateContext.Provider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(rendered.getByText('Pin Sidebar')).toBeInTheDocument();
|
||||
|
||||
const pinButton = rendered.getByTitle('Pin Sidebar');
|
||||
fireEvent.click(pinButton);
|
||||
expect(mockToggleFn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
type PinIconProps = { isPinned: boolean };
|
||||
|
||||
const PinIcon = ({ isPinned }: PinIconProps) =>
|
||||
isPinned ? <LockIcon color="primary" /> : <LockOpenIcon />;
|
||||
|
||||
export const PinButton = () => {
|
||||
const { isPinned, toggleSidebarPinState } = useContext(
|
||||
SidebarPinStateContext,
|
||||
);
|
||||
|
||||
return (
|
||||
<ListItem>
|
||||
<ListItemText
|
||||
primary="Pin Sidebar"
|
||||
secondary="Prevent the sidebar from collapsing"
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<Tooltip
|
||||
placement="top"
|
||||
arrow
|
||||
title={`${isPinned ? 'Unpin' : 'Pin'} Sidebar`}
|
||||
>
|
||||
<ToggleButton
|
||||
size="small"
|
||||
value="pin"
|
||||
selected={isPinned}
|
||||
onChange={() => {
|
||||
toggleSidebarPinState();
|
||||
}}
|
||||
>
|
||||
<PinIcon isPinned={isPinned} />
|
||||
</ToggleButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 { InfoCard } from '@backstage/core';
|
||||
import { Grid, Typography } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { SignInAvatar } from './SignInAvatar';
|
||||
import { UserSettingsMenu } from './UserSettingsMenu';
|
||||
import { useUserProfile } from '../useUserProfileInfo';
|
||||
|
||||
export const Profile = () => {
|
||||
const { profile, displayName } = useUserProfile();
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item md={12}>
|
||||
<InfoCard title="Profile">
|
||||
<Grid container spacing={6}>
|
||||
<Grid item>
|
||||
<SignInAvatar size={96} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm container>
|
||||
<Grid item xs container direction="column" spacing={2}>
|
||||
<Grid item xs>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
{displayName}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{profile.email}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<UserSettingsMenu />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 }>(theme => ({
|
||||
avatar: {
|
||||
width: ({ size }) => size,
|
||||
height: ({ size }) => size,
|
||||
fontSize: ({ size }) => size * 0.7,
|
||||
border: `1px solid ${theme.palette.textSubtle}`,
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = { size?: number };
|
||||
|
||||
export const SignInAvatar = ({ size }: Props) => {
|
||||
const { iconSize } = sidebarConfig;
|
||||
const classes = useStyles(size ? { size } : { size: iconSize });
|
||||
const { profile } = useUserProfile();
|
||||
|
||||
return <Avatar src={profile.picture} className={classes.avatar} />;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
appThemeApiRef,
|
||||
AppThemeSelector,
|
||||
} from '@backstage/core';
|
||||
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { ThemeToggle } from './ThemeToggle';
|
||||
|
||||
const mockTheme = {
|
||||
id: 'light',
|
||||
title: 'Mock Theme',
|
||||
variant: 'light' as 'light', // wut?
|
||||
theme: lightTheme,
|
||||
};
|
||||
|
||||
const apiRegistry = ApiRegistry.from([
|
||||
[appThemeApiRef, AppThemeSelector.createWithStorage([mockTheme])],
|
||||
]);
|
||||
|
||||
describe('<ThemeToggle />', () => {
|
||||
it('toggles the theme select button', async () => {
|
||||
const themeApi = apiRegistry.get(appThemeApiRef);
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<ThemeToggle />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(rendered.getByText('Theme')).toBeInTheDocument();
|
||||
|
||||
const themeButton = rendered.getByTitle('Select Mock Theme');
|
||||
expect(themeApi?.getActiveThemeId()).toBe(undefined);
|
||||
fireEvent.click(themeButton);
|
||||
expect(themeApi?.getActiveThemeId()).toBe('light');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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, { cloneElement } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
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';
|
||||
|
||||
type ThemeIconProps = {
|
||||
id: string;
|
||||
activeId: string | undefined;
|
||||
icon: JSX.Element | undefined;
|
||||
};
|
||||
|
||||
const ThemeIcon = ({ id, activeId, icon }: ThemeIconProps) =>
|
||||
icon ? (
|
||||
cloneElement(icon, {
|
||||
color: activeId === id ? 'primary' : undefined,
|
||||
})
|
||||
) : (
|
||||
<AutoIcon color={activeId === id ? 'primary' : undefined} />
|
||||
);
|
||||
|
||||
type TooltipToggleButtonProps = {
|
||||
children: JSX.Element;
|
||||
title: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
// ToggleButtonGroup uses React.children.map instead of context
|
||||
// so wrapping with Tooltip breaks ToggleButton functionality.
|
||||
const TooltipToggleButton = ({
|
||||
children,
|
||||
title,
|
||||
value,
|
||||
...props
|
||||
}: TooltipToggleButtonProps) => (
|
||||
<Tooltip placement="top" arrow title={title}>
|
||||
<ToggleButton value={value} {...props}>
|
||||
{children}
|
||||
</ToggleButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
export const ThemeToggle = () => {
|
||||
const appThemeApi = useApi(appThemeApiRef);
|
||||
const themeId = useObservable(
|
||||
appThemeApi.activeThemeId$(),
|
||||
appThemeApi.getActiveThemeId(),
|
||||
);
|
||||
|
||||
const themeIds = appThemeApi.getInstalledThemes();
|
||||
|
||||
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 => {
|
||||
const themeIcon = themeIds.find(t => t.id === theme.id)?.icon;
|
||||
|
||||
return (
|
||||
<TooltipToggleButton
|
||||
key={theme.id}
|
||||
title={`Select ${theme.title}`}
|
||||
value={theme.variant}
|
||||
>
|
||||
<ThemeIcon id={theme.id} icon={themeIcon} activeId={themeId} />
|
||||
</TooltipToggleButton>
|
||||
);
|
||||
})}
|
||||
<Tooltip placement="top" arrow title="Select auto theme">
|
||||
<ToggleButton value="auto">
|
||||
<AutoIcon color={themeId === undefined ? 'primary' : undefined} />
|
||||
</ToggleButton>
|
||||
</Tooltip>
|
||||
</ToggleButtonGroup>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { UserSettingsMenu } from './UserSettingsMenu';
|
||||
|
||||
describe('<UserSettingsMenu />', () => {
|
||||
it('displays a menu button with a sign-out option', async () => {
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(<UserSettingsMenu />),
|
||||
);
|
||||
|
||||
const menuButton = rendered.getByLabelText('more');
|
||||
fireEvent.click(menuButton);
|
||||
|
||||
expect(rendered.getByText('Sign Out')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -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 aria-label="more" 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,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.
|
||||
*/
|
||||
|
||||
export { General } from './General';
|
||||
export { SignInAvatar } from './SignInAvatar';
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 { SidebarItem } from '@backstage/core';
|
||||
import { SignInAvatar } from './General';
|
||||
import { useUserProfile } from './useUserProfileInfo';
|
||||
import { settingsRouteRef } from '../plugin';
|
||||
|
||||
export const Settings = () => {
|
||||
const { displayName } = useUserProfile();
|
||||
const SidebarAvatar = () => <SignInAvatar />;
|
||||
|
||||
return (
|
||||
<SidebarItem
|
||||
text={displayName}
|
||||
to={settingsRouteRef.path}
|
||||
icon={SidebarAvatar}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { Content, Header, HeaderTabs, Page, pageTheme } from '@backstage/core';
|
||||
import { General } from './General';
|
||||
import { AuthProviders } from './AuthProviders';
|
||||
import { FeatureFlags } from './FeatureFlags';
|
||||
|
||||
type Props = {
|
||||
providerSettings?: JSX.Element;
|
||||
};
|
||||
|
||||
export const SettingsPage = ({ providerSettings }: Props) => {
|
||||
const [activeTab, setActiveTab] = useState<number>(0);
|
||||
const onTabChange = (index: number) => {
|
||||
setActiveTab(index);
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{ id: 'general', label: 'General' },
|
||||
{ id: 'auth-providers', label: 'Authentication Providers' },
|
||||
{ id: 'feature-flags', label: 'Feature Flags' },
|
||||
];
|
||||
|
||||
const content = [
|
||||
<General />,
|
||||
<AuthProviders providerSettings={providerSettings} />,
|
||||
<FeatureFlags />,
|
||||
];
|
||||
|
||||
return (
|
||||
<Page theme={pageTheme.home}>
|
||||
<Header title="Settings" />
|
||||
<HeaderTabs tabs={tabs} onChange={onTabChange} />
|
||||
<Content>{content[activeTab]}</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
appThemeApiRef,
|
||||
AppThemeSelector,
|
||||
configApiRef,
|
||||
ConfigReader,
|
||||
FeatureFlags,
|
||||
featureFlagsApiRef,
|
||||
Sidebar,
|
||||
SidebarDivider,
|
||||
SidebarSpace,
|
||||
} from '@backstage/core';
|
||||
import { MemoryRouter } from 'react-router';
|
||||
import { Settings } from './Settings';
|
||||
import { SettingsPage } from './SettingsPage';
|
||||
|
||||
export default {
|
||||
title: 'Settings',
|
||||
component: Settings,
|
||||
decorators: [
|
||||
(storyFn: () => JSX.Element) => (
|
||||
<MemoryRouter initialEntries={['/']}>{storyFn()}</MemoryRouter>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const SidebarItem = () => (
|
||||
<Sidebar>
|
||||
<SidebarSpace />
|
||||
<SidebarDivider />
|
||||
<Settings />
|
||||
</Sidebar>
|
||||
);
|
||||
|
||||
const createConfig = () =>
|
||||
ConfigReader.fromConfigs([
|
||||
{
|
||||
context: '',
|
||||
data: {
|
||||
auth: {
|
||||
providers: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const config = createConfig();
|
||||
|
||||
const apis = ApiRegistry.from([
|
||||
[configApiRef, config],
|
||||
[featureFlagsApiRef, new FeatureFlags()],
|
||||
[appThemeApiRef, AppThemeSelector.createWithStorage([])],
|
||||
]);
|
||||
|
||||
export const TheSettingsPage = () => (
|
||||
<div style={{ border: '1px solid #ddd' }}>
|
||||
<ApiProvider apis={apis}>
|
||||
<SettingsPage />
|
||||
</ApiProvider>
|
||||
</div>
|
||||
);
|
||||
@@ -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 };
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
export { plugin } from './plugin';
|
||||
export { Settings } from './components/Settings';
|
||||
export { SettingsPage as Router } from './components/SettingsPage';
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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, createRouteRef } from '@backstage/core';
|
||||
|
||||
export const settingsRouteRef = createRouteRef({
|
||||
path: '/settings',
|
||||
title: 'Settings',
|
||||
});
|
||||
|
||||
export const plugin = createPlugin({
|
||||
id: 'user-settings',
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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';
|
||||
Reference in New Issue
Block a user