From 07e08834a549d537f88134a7e4039591ba454d2c Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 18 Sep 2020 11:19:05 +0200 Subject: [PATCH 01/27] Move to a user-settings plugin --- packages/app/package.json | 2 +- packages/app/src/components/Root/Root.tsx | 5 +- packages/app/src/plugins.ts | 1 + packages/core/src/layout/Sidebar/index.ts | 2 - plugins/user-settings/.eslintrc.js | 3 + plugins/user-settings/README.md | 13 +++ plugins/user-settings/dev/index.tsx | 19 +++++ plugins/user-settings/package.json | 46 +++++++++++ .../src/components}/AppSettingsList.tsx | 0 .../src/components}/AuthProviderList.tsx | 3 +- .../components}/DefaultProviderSettings.tsx | 5 +- .../src/components}/FeatureFlagsItem.tsx | 6 +- .../src/components}/FeatureFlagsList.tsx | 0 .../src/components/OAuthProviderSettings.tsx | 80 ++++++++++++++++++ .../src/components/OIDCProviderSettings.tsx | 81 +++++++++++++++++++ .../src/components}/PinButton.tsx | 2 +- .../src/components}/ProviderSettingsItem.tsx | 3 +- .../src/components}/SettingsDialog.tsx | 2 +- .../src/components}/SignInAvatar.tsx | 2 +- .../src/components}/ThemeToggle.tsx | 2 +- .../src/components}/UserSettings.tsx | 5 +- .../src/components}/UserSettingsMenu.tsx | 2 +- .../user-settings/src/components}/index.ts | 3 +- .../src/components}/useUserProfileInfo.ts | 2 +- plugins/user-settings/src/index.ts | 17 ++++ plugins/user-settings/src/plugin.test.ts | 22 +++++ plugins/user-settings/src/plugin.ts | 30 +++++++ plugins/user-settings/src/setupTests.ts | 18 +++++ 28 files changed, 349 insertions(+), 27 deletions(-) create mode 100644 plugins/user-settings/.eslintrc.js create mode 100644 plugins/user-settings/README.md create mode 100644 plugins/user-settings/dev/index.tsx create mode 100644 plugins/user-settings/package.json rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/AppSettingsList.tsx (100%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/AuthProviderList.tsx (89%) rename {packages/core/src/layout/Sidebar => plugins/user-settings/src/components}/DefaultProviderSettings.tsx (93%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/FeatureFlagsItem.tsx (95%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/FeatureFlagsList.tsx (100%) create mode 100644 plugins/user-settings/src/components/OAuthProviderSettings.tsx create mode 100644 plugins/user-settings/src/components/OIDCProviderSettings.tsx rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/PinButton.tsx (96%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/ProviderSettingsItem.tsx (95%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/SettingsDialog.tsx (96%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/SignInAvatar.tsx (96%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/ThemeToggle.tsx (97%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/UserSettings.tsx (93%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/UserSettingsMenu.tsx (96%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/index.ts (83%) rename {packages/core/src/layout/Sidebar/Settings => plugins/user-settings/src/components}/useUserProfileInfo.ts (93%) create mode 100644 plugins/user-settings/src/index.ts create mode 100644 plugins/user-settings/src/plugin.test.ts create mode 100644 plugins/user-settings/src/plugin.ts create mode 100644 plugins/user-settings/src/setupTests.ts diff --git a/packages/app/package.json b/packages/app/package.json index e8dfb94046..07814cdb0d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -10,7 +10,6 @@ "@backstage/plugin-api-docs": "^0.1.1-alpha.24", "@backstage/plugin-catalog": "^0.1.1-alpha.24", "@backstage/plugin-circleci": "^0.1.1-alpha.24", - "@backstage/plugin-cloudbuild": "^0.1.1-alpha.24", "@backstage/plugin-cost-insights": "^0.1.1-alpha.24", "@backstage/plugin-explore": "^0.1.1-alpha.24", "@backstage/plugin-gcp-projects": "^0.1.1-alpha.24", @@ -28,6 +27,7 @@ "@backstage/plugin-tech-radar": "^0.1.1-alpha.24", "@backstage/plugin-techdocs": "^0.1.1-alpha.24", "@backstage/plugin-welcome": "^0.1.1-alpha.24", + "@backstage/plugin-user-settings": "^0.1.1-alpha.24", "@backstage/test-utils": "^0.1.1-alpha.24", "@backstage/theme": "^0.1.1-alpha.24", "@material-ui/core": "^4.11.0", diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 45ffca7801..9f5387cea8 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -35,11 +35,10 @@ import { SidebarDivider, SidebarSearchField, SidebarSpace, - SidebarUserSettings, - DefaultProviderSettings, } from '@backstage/core'; import { NavLink } from 'react-router-dom'; import { graphiQLRouteRef } from '@backstage/plugin-graphiql'; +import { UserSettings } from '@backstage/plugin-user-settings'; const useSidebarLogoStyles = makeStyles({ root: { @@ -103,7 +102,7 @@ const Root: FC<{}> = ({ children }) => ( /> - } /> + {children} diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 85c4977d67..57dc8a3443 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -36,3 +36,4 @@ export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects'; export { plugin as Kubernetes } from '@backstage/plugin-kubernetes'; export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; +export { plugin as UserSettings } from '@backstage/plugin-user-settings'; diff --git a/packages/core/src/layout/Sidebar/index.ts b/packages/core/src/layout/Sidebar/index.ts index a644c06e14..803306478d 100644 --- a/packages/core/src/layout/Sidebar/index.ts +++ b/packages/core/src/layout/Sidebar/index.ts @@ -31,5 +31,3 @@ export { sidebarConfig, } from './config'; export type { SidebarContextType } from './config'; -export { DefaultProviderSettings } from './DefaultProviderSettings'; -export * from './Settings'; diff --git a/plugins/user-settings/.eslintrc.js b/plugins/user-settings/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/user-settings/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md new file mode 100644 index 0000000000..23d3e48fee --- /dev/null +++ b/plugins/user-settings/README.md @@ -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. diff --git a/plugins/user-settings/dev/index.tsx b/plugins/user-settings/dev/index.tsx new file mode 100644 index 0000000000..264d6f801f --- /dev/null +++ b/plugins/user-settings/dev/index.tsx @@ -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(); diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json new file mode 100644 index 0000000000..97fbb23301 --- /dev/null +++ b/plugins/user-settings/package.json @@ -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" + ] +} diff --git a/packages/core/src/layout/Sidebar/Settings/AppSettingsList.tsx b/plugins/user-settings/src/components/AppSettingsList.tsx similarity index 100% rename from packages/core/src/layout/Sidebar/Settings/AppSettingsList.tsx rename to plugins/user-settings/src/components/AppSettingsList.tsx diff --git a/packages/core/src/layout/Sidebar/Settings/AuthProviderList.tsx b/plugins/user-settings/src/components/AuthProviderList.tsx similarity index 89% rename from packages/core/src/layout/Sidebar/Settings/AuthProviderList.tsx rename to plugins/user-settings/src/components/AuthProviderList.tsx index 34cf6fb837..659af7cf19 100644 --- a/packages/core/src/layout/Sidebar/Settings/AuthProviderList.tsx +++ b/plugins/user-settings/src/components/AuthProviderList.tsx @@ -15,8 +15,7 @@ */ import React from 'react'; -import List from '@material-ui/core/List'; -import ListSubheader from '@material-ui/core/ListSubheader'; +import { List, ListSubheader } from '@material-ui/core'; type Props = { providerSettings: React.ReactNode; diff --git a/packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx b/plugins/user-settings/src/components/DefaultProviderSettings.tsx similarity index 93% rename from packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx rename to plugins/user-settings/src/components/DefaultProviderSettings.tsx index 99c5225c83..646bbb0603 100644 --- a/packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx +++ b/plugins/user-settings/src/components/DefaultProviderSettings.tsx @@ -23,10 +23,11 @@ import { microsoftAuthApiRef, samlAuthApiRef, useApi, -} from '@backstage/core-api'; +} from '@backstage/core'; import Star from '@material-ui/icons/Star'; import React from 'react'; -import { ProviderSettingsItem } from './Settings'; +import { OAuthProviderSettings } from './OAuthProviderSettings'; +import { OIDCProviderSettings } from './OIDCProviderSettings'; export const DefaultProviderSettings = () => { const configApi = useApi(configApiRef); diff --git a/packages/core/src/layout/Sidebar/Settings/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlagsItem.tsx similarity index 95% rename from packages/core/src/layout/Sidebar/Settings/FeatureFlagsItem.tsx rename to plugins/user-settings/src/components/FeatureFlagsItem.tsx index 3e40ce69cc..bc327061ef 100644 --- a/packages/core/src/layout/Sidebar/Settings/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlagsItem.tsx @@ -15,11 +15,7 @@ */ import React from 'react'; -import { - FeatureFlagName, - useApi, - featureFlagsApiRef, -} from '@backstage/core-api'; +import { FeatureFlagName, useApi, featureFlagsApiRef } from '@backstage/core'; import { ListItem, ListItemSecondaryAction, diff --git a/packages/core/src/layout/Sidebar/Settings/FeatureFlagsList.tsx b/plugins/user-settings/src/components/FeatureFlagsList.tsx similarity index 100% rename from packages/core/src/layout/Sidebar/Settings/FeatureFlagsList.tsx rename to plugins/user-settings/src/components/FeatureFlagsList.tsx diff --git a/plugins/user-settings/src/components/OAuthProviderSettings.tsx b/plugins/user-settings/src/components/OAuthProviderSettings.tsx new file mode 100644 index 0000000000..2ecc5536ea --- /dev/null +++ b/plugins/user-settings/src/components/OAuthProviderSettings.tsx @@ -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; +}; + +export const OAuthProviderSettings: FC = ({ + 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 ( + api.getAccessToken()} + /> + ); +}; diff --git a/plugins/user-settings/src/components/OIDCProviderSettings.tsx b/plugins/user-settings/src/components/OIDCProviderSettings.tsx new file mode 100644 index 0000000000..1516b3514d --- /dev/null +++ b/plugins/user-settings/src/components/OIDCProviderSettings.tsx @@ -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; +}; + +export const OIDCProviderSettings: FC = ({ + 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 ( + api.getIdToken()} + /> + ); +}; diff --git a/packages/core/src/layout/Sidebar/Settings/PinButton.tsx b/plugins/user-settings/src/components/PinButton.tsx similarity index 96% rename from packages/core/src/layout/Sidebar/Settings/PinButton.tsx rename to plugins/user-settings/src/components/PinButton.tsx index 2727313ede..0858e9d4fb 100644 --- a/packages/core/src/layout/Sidebar/Settings/PinButton.tsx +++ b/plugins/user-settings/src/components/PinButton.tsx @@ -24,7 +24,7 @@ import { import LockIcon from '@material-ui/icons/Lock'; import LockOpenIcon from '@material-ui/icons/LockOpen'; import { ToggleButton } from '@material-ui/lab'; -import { SidebarPinStateContext } from '../Page'; +import { SidebarPinStateContext } from '@backstage/core'; export const SidebarPinButton = () => { const { isPinned, toggleSidebarPinState } = useContext( diff --git a/packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx b/plugins/user-settings/src/components/ProviderSettingsItem.tsx similarity index 95% rename from packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx rename to plugins/user-settings/src/components/ProviderSettingsItem.tsx index 4db7ea0823..e4a3900919 100644 --- a/packages/core/src/layout/Sidebar/Settings/ProviderSettingsItem.tsx +++ b/plugins/user-settings/src/components/ProviderSettingsItem.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import React, { FC, useState, useEffect } from 'react'; +import React from 'react'; +import { IconComponent, OAuthApi, OpenIdConnectApi } from '@backstage/core'; import { ListItem, ListItemIcon, diff --git a/packages/core/src/layout/Sidebar/Settings/SettingsDialog.tsx b/plugins/user-settings/src/components/SettingsDialog.tsx similarity index 96% rename from packages/core/src/layout/Sidebar/Settings/SettingsDialog.tsx rename to plugins/user-settings/src/components/SettingsDialog.tsx index 71c4899862..c121a0b9cb 100644 --- a/packages/core/src/layout/Sidebar/Settings/SettingsDialog.tsx +++ b/plugins/user-settings/src/components/SettingsDialog.tsx @@ -28,7 +28,7 @@ import { FeatureFlagsList } from './FeatureFlagsList'; import { SignInAvatar } from './SignInAvatar'; import { UserSettingsMenu } from './UserSettingsMenu'; import { useUserProfile } from './useUserProfileInfo'; -import { useApi, featureFlagsApiRef } from '@backstage/core-api'; +import { useApi, featureFlagsApiRef } from '@backstage/core'; const useStyles = makeStyles({ root: { diff --git a/packages/core/src/layout/Sidebar/Settings/SignInAvatar.tsx b/plugins/user-settings/src/components/SignInAvatar.tsx similarity index 96% rename from packages/core/src/layout/Sidebar/Settings/SignInAvatar.tsx rename to plugins/user-settings/src/components/SignInAvatar.tsx index f0430edfcf..11a5e6ed99 100644 --- a/packages/core/src/layout/Sidebar/Settings/SignInAvatar.tsx +++ b/plugins/user-settings/src/components/SignInAvatar.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { BackstageTheme } from '@backstage/theme'; import { makeStyles, Avatar } from '@material-ui/core'; import { useUserProfile } from './useUserProfileInfo'; -import { sidebarConfig } from '../config'; +import { sidebarConfig } from '@backstage/core'; const useStyles = makeStyles({ avatar: { diff --git a/packages/core/src/layout/Sidebar/Settings/ThemeToggle.tsx b/plugins/user-settings/src/components/ThemeToggle.tsx similarity index 97% rename from packages/core/src/layout/Sidebar/Settings/ThemeToggle.tsx rename to plugins/user-settings/src/components/ThemeToggle.tsx index a5e703089c..400a918d32 100644 --- a/packages/core/src/layout/Sidebar/Settings/ThemeToggle.tsx +++ b/plugins/user-settings/src/components/ThemeToggle.tsx @@ -19,7 +19,7 @@ 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-api'; +import { appThemeApiRef, useApi } from '@backstage/core'; import ToggleButton from '@material-ui/lab/ToggleButton'; import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup'; import { diff --git a/packages/core/src/layout/Sidebar/Settings/UserSettings.tsx b/plugins/user-settings/src/components/UserSettings.tsx similarity index 93% rename from packages/core/src/layout/Sidebar/Settings/UserSettings.tsx rename to plugins/user-settings/src/components/UserSettings.tsx index 85dbefff9b..a5d9bd7778 100644 --- a/packages/core/src/layout/Sidebar/Settings/UserSettings.tsx +++ b/plugins/user-settings/src/components/UserSettings.tsx @@ -18,15 +18,14 @@ import React, { useEffect, useContext } from 'react'; import { Popover } from '@material-ui/core'; import { SignInAvatar } from './SignInAvatar'; import { SettingsDialog } from './SettingsDialog'; -import { SidebarItem } from '../Items'; +import { SidebarItem, SidebarContext } from '@backstage/core'; import { useUserProfile } from './useUserProfileInfo'; -import { SidebarContext } from '../config'; type Props = { providerSettings?: React.ReactNode; }; -export const SidebarUserSettings = ({ providerSettings }: Props) => { +export const UserSettings = ({ providerSettings }: Props) => { const { isOpen: sidebarOpen } = useContext(SidebarContext); const { displayName } = useUserProfile(); const [open, setOpen] = React.useState(false); diff --git a/packages/core/src/layout/Sidebar/Settings/UserSettingsMenu.tsx b/plugins/user-settings/src/components/UserSettingsMenu.tsx similarity index 96% rename from packages/core/src/layout/Sidebar/Settings/UserSettingsMenu.tsx rename to plugins/user-settings/src/components/UserSettingsMenu.tsx index 151ddb6e75..40b101fe4d 100644 --- a/packages/core/src/layout/Sidebar/Settings/UserSettingsMenu.tsx +++ b/plugins/user-settings/src/components/UserSettingsMenu.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { identityApiRef, useApi } from '@backstage/core-api'; +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'; diff --git a/packages/core/src/layout/Sidebar/Settings/index.ts b/plugins/user-settings/src/components/index.ts similarity index 83% rename from packages/core/src/layout/Sidebar/Settings/index.ts rename to plugins/user-settings/src/components/index.ts index 15042dc85d..59a8b425e4 100644 --- a/packages/core/src/layout/Sidebar/Settings/index.ts +++ b/plugins/user-settings/src/components/index.ts @@ -14,5 +14,4 @@ * limitations under the License. */ -export { ProviderSettingsItem } from './ProviderSettingsItem'; -export { SidebarUserSettings } from './UserSettings'; +export { UserSettings } from './UserSettings'; diff --git a/packages/core/src/layout/Sidebar/Settings/useUserProfileInfo.ts b/plugins/user-settings/src/components/useUserProfileInfo.ts similarity index 93% rename from packages/core/src/layout/Sidebar/Settings/useUserProfileInfo.ts rename to plugins/user-settings/src/components/useUserProfileInfo.ts index 60dae294a5..428719160d 100644 --- a/packages/core/src/layout/Sidebar/Settings/useUserProfileInfo.ts +++ b/plugins/user-settings/src/components/useUserProfileInfo.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useApi, identityApiRef } from '@backstage/core-api'; +import { useApi, identityApiRef } from '@backstage/core'; export const useUserProfile = () => { const identityApi = useApi(identityApiRef); diff --git a/plugins/user-settings/src/index.ts b/plugins/user-settings/src/index.ts new file mode 100644 index 0000000000..311ae69a51 --- /dev/null +++ b/plugins/user-settings/src/index.ts @@ -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'; diff --git a/plugins/user-settings/src/plugin.test.ts b/plugins/user-settings/src/plugin.test.ts new file mode 100644 index 0000000000..810a2cda12 --- /dev/null +++ b/plugins/user-settings/src/plugin.test.ts @@ -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(); + }); +}); diff --git a/plugins/user-settings/src/plugin.ts b/plugins/user-settings/src/plugin.ts new file mode 100644 index 0000000000..5e5461f3b1 --- /dev/null +++ b/plugins/user-settings/src/plugin.ts @@ -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); + // }, +}); diff --git a/plugins/user-settings/src/setupTests.ts b/plugins/user-settings/src/setupTests.ts new file mode 100644 index 0000000000..4b4cdbdaaf --- /dev/null +++ b/plugins/user-settings/src/setupTests.ts @@ -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(); From 5d8e058e4d275425d0337306cdb7f268ef4b2871 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 18 Sep 2020 14:05:59 +0200 Subject: [PATCH 02/27] Display list of providers from config as default --- .../src/components/AuthProviderList.tsx | 6 +++--- ...Settings.tsx => ConfiguredProviderSettings.tsx} | 2 +- .../src/components/SettingsDialog.tsx | 14 +++++++------- .../user-settings/src/components/UserSettings.tsx | 11 ++++++++++- 4 files changed, 21 insertions(+), 12 deletions(-) rename plugins/user-settings/src/components/{DefaultProviderSettings.tsx => ConfiguredProviderSettings.tsx} (97%) diff --git a/plugins/user-settings/src/components/AuthProviderList.tsx b/plugins/user-settings/src/components/AuthProviderList.tsx index 659af7cf19..8f4d41319c 100644 --- a/plugins/user-settings/src/components/AuthProviderList.tsx +++ b/plugins/user-settings/src/components/AuthProviderList.tsx @@ -18,11 +18,11 @@ import React from 'react'; import { List, ListSubheader } from '@material-ui/core'; type Props = { - providerSettings: React.ReactNode; + providers: React.ReactNode; }; -export const AuthProvidersList = ({ providerSettings }: Props) => ( +export const AuthProvidersList = ({ providers }: Props) => ( Available Auth Providers}> - {providerSettings} + {providers} ); diff --git a/plugins/user-settings/src/components/DefaultProviderSettings.tsx b/plugins/user-settings/src/components/ConfiguredProviderSettings.tsx similarity index 97% rename from plugins/user-settings/src/components/DefaultProviderSettings.tsx rename to plugins/user-settings/src/components/ConfiguredProviderSettings.tsx index 646bbb0603..99351e2984 100644 --- a/plugins/user-settings/src/components/DefaultProviderSettings.tsx +++ b/plugins/user-settings/src/components/ConfiguredProviderSettings.tsx @@ -29,7 +29,7 @@ import React from 'react'; import { OAuthProviderSettings } from './OAuthProviderSettings'; import { OIDCProviderSettings } from './OIDCProviderSettings'; -export const DefaultProviderSettings = () => { +export const ConfiguredProviderSettings = () => { const configApi = useApi(configApiRef); const providersConfig = configApi.getOptionalConfig('auth.providers'); const providers = providersConfig?.keys() ?? []; diff --git a/plugins/user-settings/src/components/SettingsDialog.tsx b/plugins/user-settings/src/components/SettingsDialog.tsx index c121a0b9cb..69ec4fb8ee 100644 --- a/plugins/user-settings/src/components/SettingsDialog.tsx +++ b/plugins/user-settings/src/components/SettingsDialog.tsx @@ -29,6 +29,8 @@ import { SignInAvatar } from './SignInAvatar'; import { UserSettingsMenu } from './UserSettingsMenu'; import { useUserProfile } from './useUserProfileInfo'; import { useApi, featureFlagsApiRef } from '@backstage/core'; +import { ConfiguredProviderSettings } from './ConfiguredProviderSettings'; +import { ProviderSettings } from './UserSettings'; const useStyles = makeStyles({ root: { @@ -37,7 +39,7 @@ const useStyles = makeStyles({ }); type Props = { - providerSettings?: React.ReactNode; + providerSettings?: ProviderSettings; }; export const SettingsDialog = ({ providerSettings }: Props) => { @@ -46,6 +48,8 @@ export const SettingsDialog = ({ providerSettings }: Props) => { const featureFlagsApi = useApi(featureFlagsApiRef); const featureFlags = featureFlagsApi.getRegisteredFlags(); + const providers = providerSettings ?? ; + return ( { /> - {providerSettings && ( - <> - - - - )} + + {featureFlags.length > 0 && ( <> diff --git a/plugins/user-settings/src/components/UserSettings.tsx b/plugins/user-settings/src/components/UserSettings.tsx index a5d9bd7778..c91002d7d7 100644 --- a/plugins/user-settings/src/components/UserSettings.tsx +++ b/plugins/user-settings/src/components/UserSettings.tsx @@ -21,8 +21,17 @@ import { SettingsDialog } from './SettingsDialog'; import { SidebarItem, SidebarContext } from '@backstage/core'; import { useUserProfile } from './useUserProfileInfo'; +export type ProviderSettings = JSX.Element; + type Props = { - providerSettings?: React.ReactNode; + /** + * Available auth providers. Providers will be rendered as children + * of a MUI List. + * + * If undefined, providers that are configured in app-config.yaml + * will be displayed instead. + */ + providerSettings?: ProviderSettings; }; export const UserSettings = ({ providerSettings }: Props) => { From a1ec4786391c10e635726933d2ce8c5ed9122614 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 18 Sep 2020 14:54:59 +0200 Subject: [PATCH 03/27] Add icon to theme and use in ThemeToggle --- packages/core-api/src/apis/definitions/AppThemeApi.ts | 6 ++++++ packages/core-api/src/app/types.ts | 2 ++ packages/core/src/api-wrappers/createApp.tsx | 5 ++++- plugins/user-settings/src/components/ThemeToggle.tsx | 9 +-------- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/core-api/src/apis/definitions/AppThemeApi.ts b/packages/core-api/src/apis/definitions/AppThemeApi.ts index de70adabd9..3f580450f0 100644 --- a/packages/core-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/core-api/src/apis/definitions/AppThemeApi.ts @@ -17,6 +17,7 @@ import { createApiRef } from '../ApiRef'; import { BackstageTheme } from '@backstage/theme'; import { Observable } from '../../types'; +import { SvgIconProps } from '@material-ui/core'; /** * Describes a theme provided by the app. @@ -41,6 +42,11 @@ export type AppTheme = { * The specialized MaterialUI theme instance. */ theme: BackstageTheme; + + /** + * An Icon for the theme mode setting. + */ + icon?: React.ReactElement; }; /** diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 882ecd3d8b..8a1eb92e8e 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -112,11 +112,13 @@ export type AppOptions = { * title: 'Light Theme', * variant: 'light', * theme: lightTheme, + * icon: , * }, { * id: 'dark', * title: 'Dark Theme', * variant: 'dark', * theme: darkTheme, + * icon: , * }] * ``` */ diff --git a/packages/core/src/api-wrappers/createApp.tsx b/packages/core/src/api-wrappers/createApp.tsx index 7fe77423e2..c7d0f5ce01 100644 --- a/packages/core/src/api-wrappers/createApp.tsx +++ b/packages/core/src/api-wrappers/createApp.tsx @@ -22,7 +22,8 @@ import privateExports, { AppConfigLoader, } from '@backstage/core-api'; import { BrowserRouter, MemoryRouter } from 'react-router-dom'; - +import LightIcon from '@material-ui/icons/WbSunny'; +import DarkIcon from '@material-ui/icons/Brightness2'; import { ErrorPage } from '../layout/ErrorPage'; import { Progress } from '../components/Progress'; import { defaultApis } from './defaultApis'; @@ -110,12 +111,14 @@ export function createApp(options?: AppOptions) { title: 'Light Theme', variant: 'light', theme: lightTheme, + icon: , }, { id: 'dark', title: 'Dark Theme', variant: 'dark', theme: darkTheme, + icon: , }, ]; const configLoader = options?.configLoader ?? defaultConfigLoader; diff --git a/plugins/user-settings/src/components/ThemeToggle.tsx b/plugins/user-settings/src/components/ThemeToggle.tsx index 400a918d32..88eee3c6bf 100644 --- a/plugins/user-settings/src/components/ThemeToggle.tsx +++ b/plugins/user-settings/src/components/ThemeToggle.tsx @@ -16,8 +16,6 @@ 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'; @@ -37,11 +35,6 @@ export const SidebarThemeToggle = () => { ); const themeIds = appThemeApi.getInstalledThemes(); - // TODO(marcuseide): can these be put on the theme itself? - const themeIcons = { - dark: , - light: , - }; const handleSetTheme = ( _event: React.MouseEvent, @@ -71,7 +64,7 @@ export const SidebarThemeToggle = () => { arrow title={`Select ${theme.variant} theme`} > - {themeIcons[theme.variant]} + {themeIds.find(t => t.id === theme.id)!.icon ?? } ))} From a942d42008be8dfb05e36a3b022ab7b2f3fe8e8f Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 22 Sep 2020 13:24:03 +0200 Subject: [PATCH 04/27] Refactor toggling of featureFlags --- .../src/apis/definitions/FeatureFlagsApi.ts | 12 ++-- packages/core-api/src/app/App.tsx | 2 +- packages/core-api/src/app/FeatureFlags.tsx | 10 +-- .../src/components/FeatureFlagsItem.tsx | 64 +++++++------------ .../src/components/FeatureFlagsList.tsx | 64 +++++++++++++++---- 5 files changed, 89 insertions(+), 63 deletions(-) diff --git a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts index b6aac75620..dd9657d7d8 100644 --- a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts @@ -15,11 +15,8 @@ */ import { createApiRef } from '../ApiRef'; -import { - UserFlags, - FeatureFlagsRegistry, - FeatureFlagsRegistryItem, -} from '../../app/FeatureFlags'; +import { UserFlags, FeatureFlagsRegistry } from '../../app/FeatureFlags'; +import { FeatureFlagName } from '../../plugin'; /** * The feature flags API is used to toggle functionality to users across plugins and Backstage. @@ -55,6 +52,11 @@ export interface FeatureFlagsApi { getRegisteredFlags(): FeatureFlagsRegistry; } +export interface FeatureFlagsRegistryItem { + pluginId: string; + name: FeatureFlagName; +} + export const featureFlagsApiRef = createApiRef({ id: 'core.featureflags', description: 'Used to toggle functionality in features across Backstage', diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 9dd2fb138f..c222c3c420 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -30,12 +30,12 @@ import { SignInPageProps, } from './types'; import { BackstagePlugin } from '../plugin'; -import { FeatureFlagsRegistryItem } from './FeatureFlags'; import { featureFlagsApiRef, AppThemeApi, ConfigApi, identityApiRef, + FeatureFlagsRegistryItem, } from '../apis/definitions'; import { AppThemeProvider } from './AppThemeProvider'; diff --git a/packages/core-api/src/app/FeatureFlags.tsx b/packages/core-api/src/app/FeatureFlags.tsx index 3db2a18a02..7a69fa5456 100644 --- a/packages/core-api/src/app/FeatureFlags.tsx +++ b/packages/core-api/src/app/FeatureFlags.tsx @@ -15,7 +15,11 @@ */ import { FeatureFlagName } from '../plugin/types'; -import { FeatureFlagState, FeatureFlagsApi } from '../apis/definitions'; +import { + FeatureFlagState, + FeatureFlagsApi, + FeatureFlagsRegistryItem, +} from '../apis/definitions'; /** * Helper method for validating compatibility and flag name. @@ -129,10 +133,6 @@ export class UserFlags extends Map { * This acts as a holding data structure for feature flags * that plugins wish to register for use in Backstage. */ -export interface FeatureFlagsRegistryItem { - pluginId: string; - name: FeatureFlagName; -} export class FeatureFlagsRegistry extends Array { static from(entries: FeatureFlagsRegistryItem[]) { diff --git a/plugins/user-settings/src/components/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlagsItem.tsx index bc327061ef..760aa25a34 100644 --- a/plugins/user-settings/src/components/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlagsItem.tsx @@ -15,7 +15,6 @@ */ import React from 'react'; -import { FeatureFlagName, useApi, featureFlagsApiRef } from '@backstage/core'; import { ListItem, ListItemSecondaryAction, @@ -24,46 +23,31 @@ import { } from '@material-ui/core'; import CheckIcon from '@material-ui/icons/CheckCircle'; import { ToggleButton } from '@material-ui/lab'; - -export type Item = { - name: FeatureFlagName; - pluginId: string; -}; +import { FeatureFlagsRegistryItem } from '@backstage/core'; type Props = { - featureFlag: Item; + flag: FeatureFlagsRegistryItem; + enabled: boolean; + toggleHandler: Function; }; -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 ( - - - - - - - - - - - ); -}; +export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => ( + + + + toggleHandler(flag.name)} + > + + + + + + +); diff --git a/plugins/user-settings/src/components/FeatureFlagsList.tsx b/plugins/user-settings/src/components/FeatureFlagsList.tsx index 5687446511..0d7404c6fd 100644 --- a/plugins/user-settings/src/components/FeatureFlagsList.tsx +++ b/plugins/user-settings/src/components/FeatureFlagsList.tsx @@ -14,19 +14,59 @@ * 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'; +import React, { useState, useEffect } from 'react'; +import { List, ListSubheader } from '@material-ui/core'; +import { + useApi, + featureFlagsApiRef, + FeatureFlagName, + FeatureFlagState, + FeatureFlagsRegistryItem, +} from '@backstage/core'; +import { FlagItem } from './FeatureFlagsItem'; type Props = { - featureFlags: Item[]; + featureFlags: FeatureFlagsRegistryItem[]; }; -export const FeatureFlagsList = ({ featureFlags }: Props) => ( - Feature Flags}> - {featureFlags.map(featureFlag => ( - - ))} - -); +export const FeatureFlagsList = ({ featureFlags }: Props) => { + const featureFlagApi = useApi(featureFlagsApiRef); + const [state, setState] = useState>( + {}, + ); + + useEffect(() => { + featureFlags.map(featureFlag => { + setState({ + [featureFlag.name]: featureFlagApi.getFlags().get(featureFlag.name), + }); + }); + }, [featureFlagApi, featureFlags]); + + const toggleFlag = (flagName: FeatureFlagName) => { + const newState = featureFlagApi.getFlags().toggle(flagName); + + setState(prevState => ({ + ...prevState, + [flagName]: newState, + })); + featureFlagApi.getFlags().save(); + }; + + return ( + Feature Flags}> + {featureFlags.map(featureFlag => { + const enabled = Boolean(state[featureFlag.name]); + + return ( + + ); + })} + + ); +}; From cea6c4604ec1e434be3cf6989aa10eb476135dbf Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Thu, 24 Sep 2020 10:45:13 +0200 Subject: [PATCH 05/27] Add primary color to buttons that are active/selected --- .../src/components/FeatureFlagsItem.tsx | 2 +- .../src/components/PinButton.tsx | 2 +- .../src/components/ProviderSettingsItem.tsx | 18 ++++------- .../src/components/ThemeToggle.tsx | 31 +++++++++++++------ 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/plugins/user-settings/src/components/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlagsItem.tsx index 760aa25a34..7f7f990e48 100644 --- a/plugins/user-settings/src/components/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlagsItem.tsx @@ -45,7 +45,7 @@ export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => ( onChange={() => toggleHandler(flag.name)} > - + diff --git a/plugins/user-settings/src/components/PinButton.tsx b/plugins/user-settings/src/components/PinButton.tsx index 0858e9d4fb..be4024f954 100644 --- a/plugins/user-settings/src/components/PinButton.tsx +++ b/plugins/user-settings/src/components/PinButton.tsx @@ -37,7 +37,7 @@ export const SidebarPinButton = () => { arrow title={`${isPinned ? 'Unpin' : 'Pin'} Sidebar`} > - {isPinned ? : } + {isPinned ? : } ); diff --git a/plugins/user-settings/src/components/ProviderSettingsItem.tsx b/plugins/user-settings/src/components/ProviderSettingsItem.tsx index e4a3900919..018e6a1326 100644 --- a/plugins/user-settings/src/components/ProviderSettingsItem.tsx +++ b/plugins/user-settings/src/components/ProviderSettingsItem.tsx @@ -77,15 +77,9 @@ export const ProviderSettingsItem: FC = ({ selected={signedIn} onChange={() => (signedIn ? api.signOut() : api.signIn())} > - - - - - - - ); -}; + + + + + +); diff --git a/plugins/user-settings/src/components/ThemeToggle.tsx b/plugins/user-settings/src/components/ThemeToggle.tsx index 88eee3c6bf..9acfc3407f 100644 --- a/plugins/user-settings/src/components/ThemeToggle.tsx +++ b/plugins/user-settings/src/components/ThemeToggle.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ -import React from 'react'; +import React, { cloneElement } from 'react'; import { useObservable } from 'react-use'; import AutoIcon from '@material-ui/icons/BrightnessAuto'; -import { appThemeApiRef, useApi } from '@backstage/core'; +import { AppTheme, appThemeApiRef, useApi } from '@backstage/core'; import ToggleButton from '@material-ui/lab/ToggleButton'; import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup'; import { @@ -47,6 +47,23 @@ export const SidebarThemeToggle = () => { } }; + const ThemeIcon = ({ theme }: { theme: AppTheme }) => { + const themeIcon = themeIds.find(t => t.id === theme.id)?.icon; + const icon = themeIcon ? ( + cloneElement(themeIcon, { + color: themeId === theme.id ? 'primary' : undefined, + }) + ) : ( + + ); + + return ( + + {icon} + + ); + }; + return ( @@ -59,18 +76,12 @@ export const SidebarThemeToggle = () => { > {themeIds.map(theme => ( - - {themeIds.find(t => t.id === theme.id)!.icon ?? } - + ))} - + From 94fa82b741eea20f1935ac6ec8bd7de885bc8f50 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 25 Sep 2020 09:58:18 +0200 Subject: [PATCH 06/27] Move variant style handling for Infocard to seperate file and use some of it for TabbedCard --- .../core/src/layout/InfoCard/InfoCard.tsx | 74 +--------------- packages/core/src/layout/InfoCard/variants.ts | 86 +++++++++++++++++++ .../layout/TabbedCard/TabbedCard.stories.tsx | 19 ++++ .../core/src/layout/TabbedCard/TabbedCard.tsx | 62 +++++++++---- 4 files changed, 156 insertions(+), 85 deletions(-) create mode 100644 packages/core/src/layout/InfoCard/variants.ts diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx index 09d31735cd..6665023b9e 100644 --- a/packages/core/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core/src/layout/InfoCard/InfoCard.tsx @@ -28,6 +28,7 @@ import { import classNames from 'classnames'; import { ErrorBoundary } from '../ErrorBoundary'; import { BottomLink, BottomLinkProps } from '../BottomLink'; +import { getVariantStyles } from './variants'; const useStyles = makeStyles(theme => ({ noPadding: { @@ -59,51 +60,6 @@ const CardActionsTopRight = withStyles(theme => ({ }, }))(CardActions); -const VARIANT_STYLES = { - card: { - flex: { - display: 'flex', - flexDirection: 'column', - }, - fullHeight: { - display: 'flex', - flexDirection: 'column', - height: '100%', - }, - height100: { - display: 'flex', - flexDirection: 'column', - height: 'calc(100% - 10px)', // for pages without content header - marginBottom: '10px', - }, - contentheader: { - height: 'calc(100% - 40px)', // for pages with content header - }, - contentheadertabs: { - height: 'calc(100% - 97px)', // for pages with content header and tabs (Tingle) - }, - noShrink: { - flexShrink: 0, - }, - minheight300: { - minHeight: 300, - overflow: 'initial', - }, - }, - cardContent: { - fullHeight: { - flex: 1, - }, - height100: { - flex: 1, - }, - contentRow: { - display: 'flex', - flexDirection: 'row', - }, - }, -}; - /** * InfoCard is used to display a paper-styled block on the screen, similar to a panel. * @@ -163,32 +119,10 @@ export const InfoCard = ({ noPadding, }: Props): JSX.Element => { const classes = useStyles(); - - /** - * If variant is specified, we build up styles for that particular variant for both - * the Card and the CardContent (since these need to be synced) - */ - let calculatedStyle = {}; - let calculatedCardStyle = {}; - - if (variant) { - const variants = variant.split(/[\s]+/g); - variants.forEach(name => { - calculatedStyle = { - ...calculatedStyle, - ...VARIANT_STYLES.card[name as keyof typeof VARIANT_STYLES['card']], - }; - calculatedCardStyle = { - ...calculatedCardStyle, - ...VARIANT_STYLES.cardContent[ - name as keyof typeof VARIANT_STYLES['cardContent'] - ], - }; - }); - } + const { cardStyle, contentStyle } = getVariantStyles(variant); return ( - + {title && ( <> @@ -217,7 +151,7 @@ export const InfoCard = ({ className={classNames(cardClassName, { [classes.noPadding]: noPadding, })} - style={calculatedCardStyle} + style={contentStyle} > {children} diff --git a/packages/core/src/layout/InfoCard/variants.ts b/packages/core/src/layout/InfoCard/variants.ts new file mode 100644 index 0000000000..cc40bb372f --- /dev/null +++ b/packages/core/src/layout/InfoCard/variants.ts @@ -0,0 +1,86 @@ +/* + * 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. + */ + +const VARIANT_STYLES = { + card: { + flex: { + display: 'flex', + flexDirection: 'column', + }, + fullHeight: { + display: 'flex', + flexDirection: 'column', + height: '100%', + }, + height100: { + display: 'flex', + flexDirection: 'column', + height: 'calc(100% - 10px)', // for pages without content header + marginBottom: '10px', + }, + contentheader: { + height: 'calc(100% - 40px)', // for pages with content header + }, + contentheadertabs: { + height: 'calc(100% - 97px)', // for pages with content header and tabs (Tingle) + }, + noShrink: { + flexShrink: 0, + }, + minheight300: { + minHeight: 300, + overflow: 'initial', + }, + flat: { + boxShadow: 'none', + }, + }, + cardContent: { + fullHeight: { + flex: 1, + }, + height100: { + flex: 1, + }, + contentRow: { + display: 'flex', + flexDirection: 'row', + }, + }, +}; + +export const getVariantStyles = (variant?: string): Record => { + const cardStyle = {}; + const contentStyle = {}; + + if (variant) { + const variants = variant.split(/[\s]+/g); + variants.forEach(name => { + Object.assign( + cardStyle, + VARIANT_STYLES.card[name as keyof typeof VARIANT_STYLES['card']], + ); + Object.assign( + contentStyle, + VARIANT_STYLES.cardContent[ + name as keyof typeof VARIANT_STYLES['cardContent'] + ], + ); + }); + } + + return { cardStyle, contentStyle }; +}; diff --git a/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx b/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx index 23c2ab5b49..ba5399dad9 100644 --- a/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx +++ b/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx @@ -50,6 +50,25 @@ export const Default = () => { ); }; +export const WithSubheader = () => { + return ( + + +
Some content
+
+ +
Some content 2
+
+ +
Some content 3
+
+ +
Some content 4
+
+
+ ); +}; + const linkInfo = { title: 'Go to XYZ Location', link: '#' }; export const WithFooterLink = () => { diff --git a/packages/core/src/layout/TabbedCard/TabbedCard.tsx b/packages/core/src/layout/TabbedCard/TabbedCard.tsx index e1967b8533..7735d7eb8f 100644 --- a/packages/core/src/layout/TabbedCard/TabbedCard.tsx +++ b/packages/core/src/layout/TabbedCard/TabbedCard.tsx @@ -14,13 +14,12 @@ * limitations under the License. */ -import React, { FC, useState, ReactElement, ReactNode } from 'react'; +import React, { useState, ReactElement, ReactNode } from 'react'; import { Card, CardContent, CardHeader, Divider, - withStyles, makeStyles, Tabs, Tab, @@ -28,6 +27,7 @@ import { } from '@material-ui/core'; import { BottomLink, BottomLinkProps } from '../BottomLink'; import { ErrorBoundary } from '../ErrorBoundary'; +import { getVariantStyles } from '../InfoCard/variants'; const useTabsStyles = makeStyles(theme => ({ root: { @@ -38,31 +38,46 @@ const useTabsStyles = makeStyles(theme => ({ backgroundColor: theme.palette.info.main, height: theme.spacing(0.3), }, + header: { + padding: theme.spacing(2, 2, 2, 2.5), + }, + headerTitle: { + fontWeight: 700, + }, + headerSubheader: { + paddingTop: theme.spacing(1), + }, + noPadding: { + padding: 0, + '&:last-child': { + paddingBottom: 0, + }, + }, })); -const BoldHeader = withStyles(theme => ({ - root: { padding: theme.spacing(2, 2, 2, 2.5), display: 'inline-block' }, - title: { fontWeight: 700 }, - subheader: { paddingTop: theme.spacing(1) }, -}))(CardHeader); - type Props = { slackChannel?: string; children?: ReactElement[]; onChange?: (event: React.ChangeEvent<{}>, value: number | string) => void; title?: string; + subheader?: string; value?: number | string; deepLink?: BottomLinkProps; + variant?: string; + noPadding?: boolean; }; -const TabbedCard: FC = ({ +const TabbedCard = ({ slackChannel = '#backstage', children, title, + subheader, deepLink, value, onChange, -}) => { + variant, + noPadding, +}: Props) => { const tabsClasses = useTabsStyles(); const [selectedIndex, selectIndex] = useState(0); @@ -82,19 +97,36 @@ const TabbedCard: FC = ({ }); } + const { cardStyle, contentStyle } = getVariantStyles(variant); + return ( - + - {title && } + {title && ( + + )} {children} - {selectedTabContent} + + {selectedTabContent} + {deepLink && } @@ -118,7 +150,7 @@ type CardTabProps = TabProps & { children: ReactNode; }; -const CardTab: FC = ({ children, ...props }) => { +const CardTab = ({ children, ...props }: CardTabProps) => { const classes = useCardTabStyles(); return ; From 940398336160737aefa7b0ae49c77b757aeb5e56 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 25 Sep 2020 10:21:36 +0200 Subject: [PATCH 07/27] Use TabbedCard to tab between different setting categories --- .../src/components/AppSettingsList.tsx | 4 +- .../src/components/AuthProviderList.tsx | 6 +- .../src/components/FeatureFlagsList.tsx | 4 +- .../src/components/SettingsDialog.tsx | 70 +++++++++++++++---- .../src/components/UserSettings.tsx | 9 ++- 5 files changed, 70 insertions(+), 23 deletions(-) diff --git a/plugins/user-settings/src/components/AppSettingsList.tsx b/plugins/user-settings/src/components/AppSettingsList.tsx index 5dad178ccd..ab106cf28e 100644 --- a/plugins/user-settings/src/components/AppSettingsList.tsx +++ b/plugins/user-settings/src/components/AppSettingsList.tsx @@ -14,12 +14,12 @@ * limitations under the License. */ import React from 'react'; -import { List, ListSubheader } from '@material-ui/core'; +import { List } from '@material-ui/core'; import { SidebarThemeToggle } from './ThemeToggle'; import { SidebarPinButton } from './PinButton'; export const AppSettingsList = () => ( - App Settings}> + diff --git a/plugins/user-settings/src/components/AuthProviderList.tsx b/plugins/user-settings/src/components/AuthProviderList.tsx index 8f4d41319c..9a2096212d 100644 --- a/plugins/user-settings/src/components/AuthProviderList.tsx +++ b/plugins/user-settings/src/components/AuthProviderList.tsx @@ -15,14 +15,12 @@ */ import React from 'react'; -import { List, ListSubheader } from '@material-ui/core'; +import { List } from '@material-ui/core'; type Props = { providers: React.ReactNode; }; export const AuthProvidersList = ({ providers }: Props) => ( - Available Auth Providers}> - {providers} - + {providers} ); diff --git a/plugins/user-settings/src/components/FeatureFlagsList.tsx b/plugins/user-settings/src/components/FeatureFlagsList.tsx index 0d7404c6fd..20c91f3e9a 100644 --- a/plugins/user-settings/src/components/FeatureFlagsList.tsx +++ b/plugins/user-settings/src/components/FeatureFlagsList.tsx @@ -15,7 +15,7 @@ */ import React, { useState, useEffect } from 'react'; -import { List, ListSubheader } from '@material-ui/core'; +import { List } from '@material-ui/core'; import { useApi, featureFlagsApiRef, @@ -54,7 +54,7 @@ export const FeatureFlagsList = ({ featureFlags }: Props) => { }; return ( - Feature Flags}> + {featureFlags.map(featureFlag => { const enabled = Boolean(state[featureFlag.name]); diff --git a/plugins/user-settings/src/components/SettingsDialog.tsx b/plugins/user-settings/src/components/SettingsDialog.tsx index 69ec4fb8ee..5aa13d90e4 100644 --- a/plugins/user-settings/src/components/SettingsDialog.tsx +++ b/plugins/user-settings/src/components/SettingsDialog.tsx @@ -14,13 +14,13 @@ * limitations under the License. */ -import React from 'react'; +import React, { ChangeEvent, RefObject, useEffect, useState } from 'react'; import { Card, CardContent, CardHeader, makeStyles, - Divider, + PopoverActions, } from '@material-ui/core'; import { AppSettingsList } from './AppSettingsList'; import { AuthProvidersList } from './AuthProviderList'; @@ -28,28 +28,53 @@ import { FeatureFlagsList } from './FeatureFlagsList'; import { SignInAvatar } from './SignInAvatar'; import { UserSettingsMenu } from './UserSettingsMenu'; import { useUserProfile } from './useUserProfileInfo'; -import { useApi, featureFlagsApiRef } from '@backstage/core'; +import { + useApi, + featureFlagsApiRef, + TabbedCard, + CardTab, + InfoCard, +} from '@backstage/core'; import { ConfiguredProviderSettings } from './ConfiguredProviderSettings'; import { ProviderSettings } from './UserSettings'; +import { Alert } from '@material-ui/lab'; const useStyles = makeStyles({ root: { minWidth: 400, + maxWidth: 500, }, }); type Props = { + popoverActionRef: RefObject; providerSettings?: ProviderSettings; }; -export const SettingsDialog = ({ providerSettings }: Props) => { +export const SettingsDialog = ({ + popoverActionRef, + providerSettings, +}: Props) => { const classes = useStyles(); const { profile, displayName } = useUserProfile(); const featureFlagsApi = useApi(featureFlagsApiRef); const featureFlags = featureFlagsApi.getRegisteredFlags(); + const [selectedTab, setSelectedTab] = useState('auth'); const providers = providerSettings ?? ; + const handleChange = ( + _ev: ChangeEvent<{}>, + newSelectedTab: string | number, + ) => { + setSelectedTab(newSelectedTab); + }; + + // Update the position of the popover to handle different heights + useEffect(() => { + popoverActionRef?.current?.updatePosition(); + }, [selectedTab, popoverActionRef]); + return ( { subheader={profile.email} /> - - - - {featureFlags.length > 0 && ( - <> - - - - )} + + + + + + + + + {featureFlags.length > 0 ? ( + + ) : ( + // TODO(marcuseide): Replace with empty state component + No registered Feature Flags found + )} + + ); diff --git a/plugins/user-settings/src/components/UserSettings.tsx b/plugins/user-settings/src/components/UserSettings.tsx index c91002d7d7..b117d40acb 100644 --- a/plugins/user-settings/src/components/UserSettings.tsx +++ b/plugins/user-settings/src/components/UserSettings.tsx @@ -15,7 +15,7 @@ */ import React, { useEffect, useContext } from 'react'; -import { Popover } from '@material-ui/core'; +import { Popover, PopoverActions } from '@material-ui/core'; import { SignInAvatar } from './SignInAvatar'; import { SettingsDialog } from './SettingsDialog'; import { SidebarItem, SidebarContext } from '@backstage/core'; @@ -41,6 +41,7 @@ export const UserSettings = ({ providerSettings }: Props) => { const [anchorEl, setAnchorEl] = React.useState( undefined, ); + const popoverActionRef = React.useRef(null); const handleOpen = (event?: React.MouseEvent) => { setAnchorEl(event?.currentTarget ?? undefined); @@ -66,6 +67,7 @@ export const UserSettings = ({ providerSettings }: Props) => { icon={SidebarAvatar} /> { horizontal: 'left', }} > - + ); From c09145da0f138d398e079ed09c9fb75287941cc9 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 25 Sep 2020 10:23:17 +0200 Subject: [PATCH 08/27] Refactor tooltip placements --- .../src/components/FeatureFlagsItem.tsx | 18 +++--- .../src/components/OAuthProviderSettings.tsx | 1 + .../src/components/OIDCProviderSettings.tsx | 1 + .../src/components/PinButton.tsx | 35 ++++++----- .../src/components/ProviderSettingsItem.tsx | 60 +++++++++---------- .../src/components/ThemeToggle.tsx | 34 ++++++++--- 6 files changed, 81 insertions(+), 68 deletions(-) diff --git a/plugins/user-settings/src/components/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlagsItem.tsx index 7f7f990e48..92065f2ed6 100644 --- a/plugins/user-settings/src/components/FeatureFlagsItem.tsx +++ b/plugins/user-settings/src/components/FeatureFlagsItem.tsx @@ -38,16 +38,16 @@ export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => ( secondary={`Registered in ${flag.pluginId} plugin`} /> - toggleHandler(flag.name)} - > - + + toggleHandler(flag.name)} + > - - + + ); diff --git a/plugins/user-settings/src/components/OAuthProviderSettings.tsx b/plugins/user-settings/src/components/OAuthProviderSettings.tsx index 2ecc5536ea..669dd89822 100644 --- a/plugins/user-settings/src/components/OAuthProviderSettings.tsx +++ b/plugins/user-settings/src/components/OAuthProviderSettings.tsx @@ -71,6 +71,7 @@ export const OAuthProviderSettings: FC = ({ return ( = ({ return ( { SidebarPinStateContext, ); - const PinIcon = () => ( - - {isPinned ? : } - - ); + const PinIcon = () => + isPinned ? : ; return ( @@ -48,16 +41,22 @@ export const SidebarPinButton = () => { secondary="Prevent the sidebar from collapsing" /> - { - toggleSidebarPinState(); - }} + - - + { + toggleSidebarPinState(); + }} + > + + + ); diff --git a/plugins/user-settings/src/components/ProviderSettingsItem.tsx b/plugins/user-settings/src/components/ProviderSettingsItem.tsx index 018e6a1326..0fb3d3e97d 100644 --- a/plugins/user-settings/src/components/ProviderSettingsItem.tsx +++ b/plugins/user-settings/src/components/ProviderSettingsItem.tsx @@ -35,51 +35,47 @@ import { type OAuthProviderSidebarProps = { title: string; + description: string; icon: IconComponent; apiRef: ApiRef; }; export const ProviderSettingsItem: FC = ({ title, + description, 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 ( - - - - - - + signedIn, + api, + signInHandler, +}: Props) => ( + + + + + + {description} + + } + secondaryTypographyProps={{ noWrap: true, style: { width: '80%' } }} + /> + + (signedIn ? api.signOut() : api.signIn())} + onChange={() => (signedIn ? api.logout() : signInHandler())} > - - + + ); diff --git a/plugins/user-settings/src/components/ThemeToggle.tsx b/plugins/user-settings/src/components/ThemeToggle.tsx index 9acfc3407f..166d0454c5 100644 --- a/plugins/user-settings/src/components/ThemeToggle.tsx +++ b/plugins/user-settings/src/components/ThemeToggle.tsx @@ -47,21 +47,34 @@ export const SidebarThemeToggle = () => { } }; + // ToggleButtonGroup uses React.children.map instead of context + // so wrapping with Tooltip breaks ToggleButton functionality. + const TooltipToggleButton = ({ + children, + title, + value, + ...props + }: { + children: JSX.Element; + title: string; + value: string; + }) => ( + + + {children} + + + ); + const ThemeIcon = ({ theme }: { theme: AppTheme }) => { const themeIcon = themeIds.find(t => t.id === theme.id)?.icon; - const icon = themeIcon ? ( + return themeIcon ? ( cloneElement(themeIcon, { color: themeId === theme.id ? 'primary' : undefined, }) ) : ( ); - - return ( - - {icon} - - ); }; return ( @@ -75,9 +88,12 @@ export const SidebarThemeToggle = () => { onChange={handleSetTheme} > {themeIds.map(theme => ( - + - + ))} From 74d3579f49cc7ea5b008a4be483c19e304f514be Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Mon, 28 Sep 2020 09:16:27 +0200 Subject: [PATCH 09/27] Add tests --- plugins/user-settings/package.json | 1 + .../src/components/AppSettingsList.tsx | 8 +- .../ConfiguredProviderSettings.test.tsx | 80 +++++++++++++++++++ .../src/components/PinButton.test.tsx | 42 ++++++++++ .../src/components/PinButton.tsx | 2 +- .../src/components/SettingsDialog.test.tsx | 50 ++++++++++++ .../src/components/ThemeToggle.test.tsx | 58 ++++++++++++++ .../src/components/ThemeToggle.tsx | 5 +- .../src/components/UserSettings.test.tsx | 26 ++++++ .../src/components/UserSettingsMenu.test.tsx | 33 ++++++++ .../src/components/UserSettingsMenu.tsx | 2 +- plugins/user-settings/src/plugin.ts | 10 --- 12 files changed, 299 insertions(+), 18 deletions(-) create mode 100644 plugins/user-settings/src/components/ConfiguredProviderSettings.test.tsx create mode 100644 plugins/user-settings/src/components/PinButton.test.tsx create mode 100644 plugins/user-settings/src/components/SettingsDialog.test.tsx create mode 100644 plugins/user-settings/src/components/ThemeToggle.test.tsx create mode 100644 plugins/user-settings/src/components/UserSettings.test.tsx create mode 100644 plugins/user-settings/src/components/UserSettingsMenu.test.tsx diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 97fbb23301..a6bc784942 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -33,6 +33,7 @@ "devDependencies": { "@backstage/cli": "^0.1.1-alpha.21", "@backstage/dev-utils": "^0.1.1-alpha.21", + "@backstage/test-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", diff --git a/plugins/user-settings/src/components/AppSettingsList.tsx b/plugins/user-settings/src/components/AppSettingsList.tsx index ab106cf28e..3f2b455520 100644 --- a/plugins/user-settings/src/components/AppSettingsList.tsx +++ b/plugins/user-settings/src/components/AppSettingsList.tsx @@ -15,12 +15,12 @@ */ import React from 'react'; import { List } from '@material-ui/core'; -import { SidebarThemeToggle } from './ThemeToggle'; -import { SidebarPinButton } from './PinButton'; +import { ThemeToggle } from './ThemeToggle'; +import { PinButton } from './PinButton'; export const AppSettingsList = () => ( - - + + ); diff --git a/plugins/user-settings/src/components/ConfiguredProviderSettings.test.tsx b/plugins/user-settings/src/components/ConfiguredProviderSettings.test.tsx new file mode 100644 index 0000000000..d4d8c1cbc2 --- /dev/null +++ b/plugins/user-settings/src/components/ConfiguredProviderSettings.test.tsx @@ -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 { + ApiProvider, + ApiRegistry, + configApiRef, + ConfigReader, + googleAuthApiRef, +} from '@backstage/core'; +import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; +import { fireEvent } from '@testing-library/react'; +import * as React from 'react'; +import { ConfiguredProviderSettings } from './ConfiguredProviderSettings'; + +const mockSignInHandler = jest.fn().mockReturnValue(''); +const mockGoogleAuth = { + sessionState$: () => ({ + subscribe: () => ({ + unsubscribe: () => null, + }), + }), + getIdToken: mockSignInHandler, +}; + +const createConfig = () => + ConfigReader.fromConfigs([ + { + context: '', + data: { + auth: { + providers: { + google: { development: {} }, + }, + }, + }, + }, + ]); + +const config = createConfig(); + +const apiRegistry = ApiRegistry.from([ + [configApiRef, config], + [googleAuthApiRef, mockGoogleAuth], +]); + +describe('', () => { + it('displays a provider and calls its sign-in handler on click', async () => { + const rendered = await renderWithEffects( + wrapInTestApp( + + + , + ), + ); + + expect(rendered.getByText('Google')).toBeInTheDocument(); + expect( + rendered.getByText(googleAuthApiRef.description), + ).toBeInTheDocument(); + + const button = rendered.getByTitle('Sign in to Google'); + expect(mockSignInHandler).toHaveBeenCalledTimes(1); + fireEvent.click(button); + expect(mockSignInHandler).toHaveBeenCalledTimes(2); + }); +}); diff --git a/plugins/user-settings/src/components/PinButton.test.tsx b/plugins/user-settings/src/components/PinButton.test.tsx new file mode 100644 index 0000000000..a800193c06 --- /dev/null +++ b/plugins/user-settings/src/components/PinButton.test.tsx @@ -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 * as React from 'react'; +import { PinButton } from './PinButton'; + +describe('', () => { + it('toggles the pin sidebar button', async () => { + const mockToggleFn = jest.fn(); + const rendered = await renderWithEffects( + wrapInTestApp( + + + , + ), + ); + + expect(rendered.getByText('Pin Sidebar')).toBeInTheDocument(); + + const pinButton = rendered.getByTitle('Pin Sidebar'); + fireEvent.click(pinButton); + expect(mockToggleFn).toHaveBeenCalled(); + }); +}); diff --git a/plugins/user-settings/src/components/PinButton.tsx b/plugins/user-settings/src/components/PinButton.tsx index 8e50eae9d4..f8017ef215 100644 --- a/plugins/user-settings/src/components/PinButton.tsx +++ b/plugins/user-settings/src/components/PinButton.tsx @@ -26,7 +26,7 @@ import LockOpenIcon from '@material-ui/icons/LockOpen'; import { ToggleButton } from '@material-ui/lab'; import { SidebarPinStateContext } from '@backstage/core'; -export const SidebarPinButton = () => { +export const PinButton = () => { const { isPinned, toggleSidebarPinState } = useContext( SidebarPinStateContext, ); diff --git a/plugins/user-settings/src/components/SettingsDialog.test.tsx b/plugins/user-settings/src/components/SettingsDialog.test.tsx new file mode 100644 index 0000000000..d738c2ece0 --- /dev/null +++ b/plugins/user-settings/src/components/SettingsDialog.test.tsx @@ -0,0 +1,50 @@ +/* + * 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, + FeatureFlags, + featureFlagsApiRef, +} from '@backstage/core'; +import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; +import * as React from 'react'; +import { SettingsDialog } from './SettingsDialog'; + +const apiRegistry = ApiRegistry.from([ + [featureFlagsApiRef, new FeatureFlags()], +]); + +describe('', () => { + const mockRef = { current: null }; + + it('displays the users name and email, and the tabs and titles', async () => { + const rendered = await renderWithEffects( + wrapInTestApp( + + + , + ), + ); + + expect(rendered.getByText('Guest')).toBeInTheDocument(); + expect(rendered.getByText('guest@example.com')).toBeInTheDocument(); + expect(rendered.getByText('App Settings')).toBeInTheDocument(); + expect(rendered.getByText('Additional Settings')).toBeInTheDocument(); + expect(rendered.getByText('Auth Providers')).toBeInTheDocument(); + expect(rendered.getByText('Feature Flags')).toBeInTheDocument(); + }); +}); diff --git a/plugins/user-settings/src/components/ThemeToggle.test.tsx b/plugins/user-settings/src/components/ThemeToggle.test.tsx new file mode 100644 index 0000000000..df36a985f6 --- /dev/null +++ b/plugins/user-settings/src/components/ThemeToggle.test.tsx @@ -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 * as 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('', () => { + it('toggles the theme select button', async () => { + const themeApi = apiRegistry.get(appThemeApiRef); + const rendered = await renderWithEffects( + wrapInTestApp( + + + , + ), + ); + + 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'); + }); +}); diff --git a/plugins/user-settings/src/components/ThemeToggle.tsx b/plugins/user-settings/src/components/ThemeToggle.tsx index 166d0454c5..b041e1f76b 100644 --- a/plugins/user-settings/src/components/ThemeToggle.tsx +++ b/plugins/user-settings/src/components/ThemeToggle.tsx @@ -27,7 +27,7 @@ import { Tooltip, } from '@material-ui/core'; -export const SidebarThemeToggle = () => { +export const ThemeToggle = () => { const appThemeApi = useApi(appThemeApiRef); const themeId = useObservable( appThemeApi.activeThemeId$(), @@ -89,7 +89,8 @@ export const SidebarThemeToggle = () => { > {themeIds.map(theme => ( diff --git a/plugins/user-settings/src/components/UserSettings.test.tsx b/plugins/user-settings/src/components/UserSettings.test.tsx new file mode 100644 index 0000000000..1d43f4093e --- /dev/null +++ b/plugins/user-settings/src/components/UserSettings.test.tsx @@ -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 * as React from 'react'; +import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; +import { UserSettings } from './UserSettings'; + +describe('', () => { + it('renders', async () => { + const rendered = await renderWithEffects(wrapInTestApp()); + expect(rendered.getByText('G')).toBeInTheDocument(); + }); +}); diff --git a/plugins/user-settings/src/components/UserSettingsMenu.test.tsx b/plugins/user-settings/src/components/UserSettingsMenu.test.tsx new file mode 100644 index 0000000000..b1ad059611 --- /dev/null +++ b/plugins/user-settings/src/components/UserSettingsMenu.test.tsx @@ -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 * as React from 'react'; +import { UserSettingsMenu } from './UserSettingsMenu'; + +describe('', () => { + it('displays a menu button with a sign-out option', async () => { + const rendered = await renderWithEffects( + wrapInTestApp(), + ); + + const menuButton = rendered.getByLabelText('more'); + fireEvent.click(menuButton); + + expect(rendered.getByText('Sign Out')).toBeInTheDocument(); + }); +}); diff --git a/plugins/user-settings/src/components/UserSettingsMenu.tsx b/plugins/user-settings/src/components/UserSettingsMenu.tsx index 40b101fe4d..19c3ee4e2d 100644 --- a/plugins/user-settings/src/components/UserSettingsMenu.tsx +++ b/plugins/user-settings/src/components/UserSettingsMenu.tsx @@ -39,7 +39,7 @@ export const UserSettingsMenu = () => { return ( <> - + diff --git a/plugins/user-settings/src/plugin.ts b/plugins/user-settings/src/plugin.ts index 5e5461f3b1..4b556180f2 100644 --- a/plugins/user-settings/src/plugin.ts +++ b/plugins/user-settings/src/plugin.ts @@ -14,17 +14,7 @@ * 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); - // }, }); From 9c9031b7888052830ccbf21039a919acb3872d46 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Mon, 28 Sep 2020 09:21:03 +0200 Subject: [PATCH 10/27] Add instructions to readme --- plugins/user-settings/README.md | 41 +++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index 23d3e48fee..72da278b1e 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -4,10 +4,41 @@ Welcome to the user-settings plugin! _This plugin was created through the Backstage CLI_ -## Getting started +## About the plugin -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). +This plugin is intended to be used within the [``](https://backstage.io/storybook/?path=/story/sidebar--sample-sidebar). It provides an item to the sidebar that will render an Avatar of the current signed in user, and when clicked display a settings UI where the user can control different settings across the App. -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. +## Usage + +Add the component to the sidebar: + +```ts +import { UserSettings } from '@backstage/plugin-user-settings'; + + + + + +; +``` + +### Props + +**Auth Providers** + +By default, the plugin provides a list of configured authentication providers is fetched from `app-config.yaml` and displayed in the "Auth Providers" tab. + +If you want to supply your own custom list of Authentication Providers, use the `providerSettings` prop: + +```ts +const MyAuthProviders = () => ( + + + + +); + +} /> +``` + +> **Note that the list of providers expects to be rendered within a MUI [``](https://material-ui.com/components/lists/)** From 790cd5ac4943b13a3cc7b84bfc69abf26c8dd207 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Mon, 28 Sep 2020 10:26:42 +0200 Subject: [PATCH 11/27] Update storybook for Sidebar --- .../src/layout/Sidebar/Sidebar.stories.tsx | 67 +++++++++++++++---- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx index 61975a728a..1b646fab8a 100644 --- a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx @@ -22,14 +22,27 @@ import { SidebarDivider, SidebarSearchField, SidebarSpace, +<<<<<<< HEAD SidebarUserSettings, ProviderSettingsItem, +======= +>>>>>>> a4e66bae0... Update storybook for Sidebar } from '.'; import HomeOutlinedIcon from '@material-ui/icons/HomeOutlined'; import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; -import Star from '@material-ui/icons/Star'; import { MemoryRouter } from 'react-router-dom'; -import { githubAuthApiRef } from '@backstage/core-api'; +import { + ApiProvider, + ApiRegistry, + appThemeApiRef, + AppThemeSelector, + configApiRef, + ConfigReader, + FeatureFlags, + featureFlagsApiRef, +} from '@backstage/core-api'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { UserSettings } from '@backstage/plugin-user-settings'; export default { title: 'Sidebar', @@ -57,15 +70,45 @@ export const SampleSidebar = () => ( - - - } - /> + +); + +const createConfig = () => + ConfigReader.fromConfigs([ + { + context: '', + data: { + auth: { + providers: { + google: { development: {} }, + }, + }, + }, + }, + ]); + +const config = createConfig(); + +const apis = ApiRegistry.from([ + [configApiRef, config], + [featureFlagsApiRef, new FeatureFlags()], + [appThemeApiRef, AppThemeSelector.createWithStorage([])], +]); + +export const WithUserSettingsPlugin = () => ( + + {/* */} + + + + + + + + + + + + ); From d33fa314cd96fe6b815bc1ab223f5e950757dd2a Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 29 Sep 2020 09:26:57 +0200 Subject: [PATCH 12/27] Fix merge conflicts --- .../src/layout/Sidebar/Sidebar.stories.tsx | 5 - plugins/user-settings/package.json | 2 +- ...t.tsx => DefaultProviderSettings.test.tsx} | 9 +- ...ttings.tsx => DefaultProviderSettings.tsx} | 19 ++- .../src/components/OAuthProviderSettings.tsx | 81 ------------- .../src/components/OIDCProviderSettings.tsx | 82 ------------- .../src/components/ProviderSettingsItem.tsx | 110 ++++++++++-------- .../src/components/SettingsDialog.tsx | 6 +- 8 files changed, 80 insertions(+), 234 deletions(-) rename plugins/user-settings/src/components/{ConfiguredProviderSettings.test.tsx => DefaultProviderSettings.test.tsx} (90%) rename plugins/user-settings/src/components/{ConfiguredProviderSettings.tsx => DefaultProviderSettings.tsx} (83%) delete mode 100644 plugins/user-settings/src/components/OAuthProviderSettings.tsx delete mode 100644 plugins/user-settings/src/components/OIDCProviderSettings.tsx diff --git a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx index 1b646fab8a..14bf6e69ed 100644 --- a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx @@ -22,11 +22,6 @@ import { SidebarDivider, SidebarSearchField, SidebarSpace, -<<<<<<< HEAD - SidebarUserSettings, - ProviderSettingsItem, -======= ->>>>>>> a4e66bae0... Update storybook for Sidebar } from '.'; import HomeOutlinedIcon from '@material-ui/icons/HomeOutlined'; import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index a6bc784942..ad2c2b0dbd 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.1.1-alpha.21", + "version": "0.1.1-alpha.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/user-settings/src/components/ConfiguredProviderSettings.test.tsx b/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx similarity index 90% rename from plugins/user-settings/src/components/ConfiguredProviderSettings.test.tsx rename to plugins/user-settings/src/components/DefaultProviderSettings.test.tsx index d4d8c1cbc2..8f6e4b72dc 100644 --- a/plugins/user-settings/src/components/ConfiguredProviderSettings.test.tsx +++ b/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx @@ -24,7 +24,7 @@ import { import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; import * as React from 'react'; -import { ConfiguredProviderSettings } from './ConfiguredProviderSettings'; +import { DefaultProviderSettings } from './DefaultProviderSettings'; const mockSignInHandler = jest.fn().mockReturnValue(''); const mockGoogleAuth = { @@ -33,7 +33,7 @@ const mockGoogleAuth = { unsubscribe: () => null, }), }), - getIdToken: mockSignInHandler, + signIn: mockSignInHandler, }; const createConfig = () => @@ -62,7 +62,7 @@ describe('', () => { const rendered = await renderWithEffects( wrapInTestApp( - + , ), ); @@ -73,8 +73,7 @@ describe('', () => { ).toBeInTheDocument(); const button = rendered.getByTitle('Sign in to Google'); - expect(mockSignInHandler).toHaveBeenCalledTimes(1); fireEvent.click(button); - expect(mockSignInHandler).toHaveBeenCalledTimes(2); + expect(mockSignInHandler).toHaveBeenCalledTimes(1); }); }); diff --git a/plugins/user-settings/src/components/ConfiguredProviderSettings.tsx b/plugins/user-settings/src/components/DefaultProviderSettings.tsx similarity index 83% rename from plugins/user-settings/src/components/ConfiguredProviderSettings.tsx rename to plugins/user-settings/src/components/DefaultProviderSettings.tsx index 99351e2984..e6e5c21d86 100644 --- a/plugins/user-settings/src/components/ConfiguredProviderSettings.tsx +++ b/plugins/user-settings/src/components/DefaultProviderSettings.tsx @@ -21,15 +21,13 @@ import { 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'; +import { ProviderSettingsItem } from './ProviderSettingsItem'; -export const ConfiguredProviderSettings = () => { +export const DefaultProviderSettings = () => { const configApi = useApi(configApiRef); const providersConfig = configApi.getOptionalConfig('auth.providers'); const providers = providersConfig?.keys() ?? []; @@ -39,6 +37,7 @@ export const ConfiguredProviderSettings = () => { {providers.includes('google') && ( @@ -46,6 +45,7 @@ export const ConfiguredProviderSettings = () => { {providers.includes('microsoft') && ( @@ -53,6 +53,7 @@ export const ConfiguredProviderSettings = () => { {providers.includes('github') && ( @@ -60,6 +61,7 @@ export const ConfiguredProviderSettings = () => { {providers.includes('gitlab') && ( @@ -67,20 +69,15 @@ export const ConfiguredProviderSettings = () => { {providers.includes('okta') && ( )} - {providers.includes('saml') && ( - - )} {providers.includes('oauth2') && ( diff --git a/plugins/user-settings/src/components/OAuthProviderSettings.tsx b/plugins/user-settings/src/components/OAuthProviderSettings.tsx deleted file mode 100644 index 669dd89822..0000000000 --- a/plugins/user-settings/src/components/OAuthProviderSettings.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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; -}; - -export const OAuthProviderSettings: FC = ({ - 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 ( - api.getAccessToken()} - /> - ); -}; diff --git a/plugins/user-settings/src/components/OIDCProviderSettings.tsx b/plugins/user-settings/src/components/OIDCProviderSettings.tsx deleted file mode 100644 index 1e6005cb82..0000000000 --- a/plugins/user-settings/src/components/OIDCProviderSettings.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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; -}; - -export const OIDCProviderSettings: FC = ({ - 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 ( - api.getIdToken()} - /> - ); -}; diff --git a/plugins/user-settings/src/components/ProviderSettingsItem.tsx b/plugins/user-settings/src/components/ProviderSettingsItem.tsx index 0fb3d3e97d..7213ee2b83 100644 --- a/plugins/user-settings/src/components/ProviderSettingsItem.tsx +++ b/plugins/user-settings/src/components/ProviderSettingsItem.tsx @@ -13,9 +13,14 @@ * 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 React, { useEffect, useState } from 'react'; +import { + ApiRef, + SessionApi, + useApi, + IconComponent, + SessionState, +} from '@backstage/core'; import { ListItem, ListItemIcon, @@ -25,57 +30,70 @@ import { } 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 = { +type Props = { title: string; description: string; icon: IconComponent; apiRef: ApiRef; }; -export const ProviderSettingsItem: FC = ({ +export const ProviderSettingsItem = ({ title, description, icon: Icon, - signedIn, - api, - signInHandler, -}: Props) => ( - - - - - - {description} - - } - secondaryTypographyProps={{ noWrap: true, style: { width: '80%' } }} - /> - - - (signedIn ? api.logout() : signInHandler())} + 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 ( + + + + + + {description} + + } + secondaryTypographyProps={{ noWrap: true, style: { width: '80%' } }} + /> + + - - - - - -); + (signedIn ? api.signOut() : api.signIn())} + > + + + + + + ); +}; diff --git a/plugins/user-settings/src/components/SettingsDialog.tsx b/plugins/user-settings/src/components/SettingsDialog.tsx index 5aa13d90e4..82e744bec1 100644 --- a/plugins/user-settings/src/components/SettingsDialog.tsx +++ b/plugins/user-settings/src/components/SettingsDialog.tsx @@ -35,7 +35,7 @@ import { CardTab, InfoCard, } from '@backstage/core'; -import { ConfiguredProviderSettings } from './ConfiguredProviderSettings'; +import { DefaultProviderSettings } from './DefaultProviderSettings'; import { ProviderSettings } from './UserSettings'; import { Alert } from '@material-ui/lab'; @@ -61,7 +61,7 @@ export const SettingsDialog = ({ const featureFlags = featureFlagsApi.getRegisteredFlags(); const [selectedTab, setSelectedTab] = useState('auth'); - const providers = providerSettings ?? ; + const providers = providerSettings ?? ; const handleChange = ( _ev: ChangeEvent<{}>, @@ -107,7 +107,7 @@ export const SettingsDialog = ({ {featureFlags.length > 0 ? ( ) : ( - // TODO(marcuseide): Replace with empty state component + // TODO(marcuseide): Replace with empty state component? No registered Feature Flags found )} From dbe126f75e094249871adc94d894195893ec06c6 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 29 Sep 2020 14:03:55 +0200 Subject: [PATCH 13/27] Update dependencies --- plugins/user-settings/package.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index ad2c2b0dbd..1c5d489747 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.21", - "@backstage/theme": "^0.1.1-alpha.21", + "@backstage/core": "^0.1.1-alpha.23", + "@backstage/theme": "^0.1.1-alpha.23", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -31,15 +31,16 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.21", - "@backstage/dev-utils": "^0.1.1-alpha.21", - "@backstage/test-utils": "^0.1.1-alpha.21", + "@backstage/cli": "^0.1.1-alpha.23", + "@backstage/dev-utils": "^0.1.1-alpha.23", + "@backstage/test-utils": "^0.1.1-alpha.23", "@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" + "msw": "^0.20.5", + "node-fetch": "^2.6.1" }, "files": [ "dist" From b7e41a556524ec849c46ac17fb3d4a8a4d21a7f0 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 29 Sep 2020 14:45:09 +0200 Subject: [PATCH 14/27] Remove jest-fetch-mock --- plugins/user-settings/src/setupTests.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/user-settings/src/setupTests.ts b/plugins/user-settings/src/setupTests.ts index 4b4cdbdaaf..0bfa67b49a 100644 --- a/plugins/user-settings/src/setupTests.ts +++ b/plugins/user-settings/src/setupTests.ts @@ -14,5 +14,3 @@ * limitations under the License. */ import '@testing-library/jest-dom'; - -require('jest-fetch-mock').enableMocks(); From 44c8696e8fd69bc72b0aa9ce4d638105de3c252c Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 29 Sep 2020 16:47:17 +0200 Subject: [PATCH 15/27] Update app-template --- .../templates/default-app/packages/app/package.json.hbs | 1 + .../templates/default-app/packages/app/src/sidebar.tsx | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index a057826ba4..103e75f6e0 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -19,6 +19,7 @@ "@backstage/plugin-lighthouse": "^{{version}}", "@backstage/plugin-tech-radar": "^{{version}}", "@backstage/plugin-github-actions": "^{{version}}", + "@backstage/plugin-user-settings": "^{{version}}", "@backstage/test-utils": "^{{version}}", "@backstage/theme": "^{{version}}", "history": "^5.0.0", diff --git a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx index 9dd9ea64c3..8ffd042543 100644 --- a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx @@ -9,6 +9,7 @@ import { Link, makeStyles } from '@material-ui/core'; import { NavLink } from 'react-router-dom'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; +import { UserSettings } from '@backstage/plugin-user-settings' import { Sidebar, @@ -17,8 +18,6 @@ import { sidebarConfig, SidebarContext, SidebarSpace, - SidebarUserSettings, - DefaultProviderSettings, } from '@backstage/core'; export const AppSidebar = () => ( @@ -37,7 +36,7 @@ export const AppSidebar = () => ( - } /> + ); From 9ce6e44fdd3740d028b1d727cc4bef151ac2d54b Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 2 Oct 2020 08:57:56 +0200 Subject: [PATCH 16/27] Optimize featureflags handling --- .../src/components/FeatureFlagsList.tsx | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/plugins/user-settings/src/components/FeatureFlagsList.tsx b/plugins/user-settings/src/components/FeatureFlagsList.tsx index 20c91f3e9a..ec4ad51457 100644 --- a/plugins/user-settings/src/components/FeatureFlagsList.tsx +++ b/plugins/user-settings/src/components/FeatureFlagsList.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useCallback } from 'react'; import { List } from '@material-ui/core'; import { useApi, @@ -31,27 +31,32 @@ type Props = { export const FeatureFlagsList = ({ featureFlags }: Props) => { const featureFlagApi = useApi(featureFlagsApiRef); - const [state, setState] = useState>( - {}, + const initialFlagState = featureFlags.reduce( + (result, featureFlag: FeatureFlagsRegistryItem) => { + const state = featureFlagApi.getFlags().get(featureFlag.name); + + result[featureFlag.name] = state; + return result; + }, + {} as Record, ); - useEffect(() => { - featureFlags.map(featureFlag => { - setState({ - [featureFlag.name]: featureFlagApi.getFlags().get(featureFlag.name), - }); - }); - }, [featureFlagApi, featureFlags]); + const [state, setState] = useState>( + initialFlagState, + ); - const toggleFlag = (flagName: FeatureFlagName) => { - const newState = featureFlagApi.getFlags().toggle(flagName); + const toggleFlag = useCallback( + (flagName: FeatureFlagName) => { + const newState = featureFlagApi.getFlags().toggle(flagName); - setState(prevState => ({ - ...prevState, - [flagName]: newState, - })); - featureFlagApi.getFlags().save(); - }; + setState(prevState => ({ + ...prevState, + [flagName]: newState, + })); + featureFlagApi.getFlags().save(); + }, + [featureFlagApi], + ); return ( From 90b7d218212b882f123ed1065af314ff6a06ef67 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 2 Oct 2020 09:16:25 +0200 Subject: [PATCH 17/27] Refactor the updatePosition logic --- .../src/components/SettingsDialog.test.tsx | 14 ++++++++---- .../src/components/SettingsDialog.tsx | 22 ++++++------------- .../src/components/UserSettings.tsx | 8 +++++-- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/plugins/user-settings/src/components/SettingsDialog.test.tsx b/plugins/user-settings/src/components/SettingsDialog.test.tsx index d738c2ece0..fb32e9cfdb 100644 --- a/plugins/user-settings/src/components/SettingsDialog.test.tsx +++ b/plugins/user-settings/src/components/SettingsDialog.test.tsx @@ -21,7 +21,8 @@ import { featureFlagsApiRef, } from '@backstage/core'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; -import * as React from 'react'; +import { fireEvent } from '@testing-library/react'; +import React from 'react'; import { SettingsDialog } from './SettingsDialog'; const apiRegistry = ApiRegistry.from([ @@ -29,13 +30,13 @@ const apiRegistry = ApiRegistry.from([ ]); describe('', () => { - const mockRef = { current: null }; + const mockFn = jest.fn(); - it('displays the users name and email, and the tabs and titles', async () => { + it('displays the users name and email, and the tabs and titles and updates the position', async () => { const rendered = await renderWithEffects( wrapInTestApp( - + , ), ); @@ -46,5 +47,10 @@ describe('', () => { expect(rendered.getByText('Additional Settings')).toBeInTheDocument(); expect(rendered.getByText('Auth Providers')).toBeInTheDocument(); expect(rendered.getByText('Feature Flags')).toBeInTheDocument(); + + const flagButton = rendered.getByText('Feature Flags'); + expect(mockFn).toBeCalledTimes(1); + fireEvent.click(flagButton); + expect(mockFn).toBeCalledTimes(2); }); }); diff --git a/plugins/user-settings/src/components/SettingsDialog.tsx b/plugins/user-settings/src/components/SettingsDialog.tsx index 82e744bec1..c6d9cfa8c5 100644 --- a/plugins/user-settings/src/components/SettingsDialog.tsx +++ b/plugins/user-settings/src/components/SettingsDialog.tsx @@ -14,14 +14,8 @@ * limitations under the License. */ -import React, { ChangeEvent, RefObject, useEffect, useState } from 'react'; -import { - Card, - CardContent, - CardHeader, - makeStyles, - PopoverActions, -} from '@material-ui/core'; +import React, { ChangeEvent, useEffect, useState } from 'react'; +import { Card, CardContent, CardHeader, makeStyles } from '@material-ui/core'; import { AppSettingsList } from './AppSettingsList'; import { AuthProvidersList } from './AuthProviderList'; import { FeatureFlagsList } from './FeatureFlagsList'; @@ -47,14 +41,11 @@ const useStyles = makeStyles({ }); type Props = { - popoverActionRef: RefObject; providerSettings?: ProviderSettings; + updatePosition: () => void; }; -export const SettingsDialog = ({ - popoverActionRef, - providerSettings, -}: Props) => { +export const SettingsDialog = ({ providerSettings, updatePosition }: Props) => { const classes = useStyles(); const { profile, displayName } = useUserProfile(); const featureFlagsApi = useApi(featureFlagsApiRef); @@ -62,6 +53,7 @@ export const SettingsDialog = ({ const [selectedTab, setSelectedTab] = useState('auth'); const providers = providerSettings ?? ; + const ref = React.useRef(updatePosition); const handleChange = ( _ev: ChangeEvent<{}>, @@ -72,8 +64,8 @@ export const SettingsDialog = ({ // Update the position of the popover to handle different heights useEffect(() => { - popoverActionRef?.current?.updatePosition(); - }, [selectedTab, popoverActionRef]); + ref.current?.(); + }, [selectedTab]); return ( diff --git a/plugins/user-settings/src/components/UserSettings.tsx b/plugins/user-settings/src/components/UserSettings.tsx index b117d40acb..34d7fa02be 100644 --- a/plugins/user-settings/src/components/UserSettings.tsx +++ b/plugins/user-settings/src/components/UserSettings.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useEffect, useContext } from 'react'; +import React, { useEffect, useContext, useCallback } from 'react'; import { Popover, PopoverActions } from '@material-ui/core'; import { SignInAvatar } from './SignInAvatar'; import { SettingsDialog } from './SettingsDialog'; @@ -43,6 +43,10 @@ export const UserSettings = ({ providerSettings }: Props) => { ); const popoverActionRef = React.useRef(null); + const updatePosition = useCallback(() => { + popoverActionRef?.current?.updatePosition(); + }, []); + const handleOpen = (event?: React.MouseEvent) => { setAnchorEl(event?.currentTarget ?? undefined); setOpen(true); @@ -81,7 +85,7 @@ export const UserSettings = ({ providerSettings }: Props) => { }} > From fdd976a15ab3922065137b1ffad72321b0ccaef1 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Fri, 2 Oct 2020 10:35:16 +0200 Subject: [PATCH 18/27] Move small components out --- .../src/components/FeatureFlagsList.tsx | 4 +- .../src/components/PinButton.tsx | 10 +- .../src/components/ThemeToggle.tsx | 98 ++++++++++--------- 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/plugins/user-settings/src/components/FeatureFlagsList.tsx b/plugins/user-settings/src/components/FeatureFlagsList.tsx index ec4ad51457..2636d0e66f 100644 --- a/plugins/user-settings/src/components/FeatureFlagsList.tsx +++ b/plugins/user-settings/src/components/FeatureFlagsList.tsx @@ -25,9 +25,7 @@ import { } from '@backstage/core'; import { FlagItem } from './FeatureFlagsItem'; -type Props = { - featureFlags: FeatureFlagsRegistryItem[]; -}; +type Props = { featureFlags: FeatureFlagsRegistryItem[] }; export const FeatureFlagsList = ({ featureFlags }: Props) => { const featureFlagApi = useApi(featureFlagsApiRef); diff --git a/plugins/user-settings/src/components/PinButton.tsx b/plugins/user-settings/src/components/PinButton.tsx index f8017ef215..e36fc365f8 100644 --- a/plugins/user-settings/src/components/PinButton.tsx +++ b/plugins/user-settings/src/components/PinButton.tsx @@ -26,14 +26,16 @@ 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 ? : ; + export const PinButton = () => { const { isPinned, toggleSidebarPinState } = useContext( SidebarPinStateContext, ); - const PinIcon = () => - isPinned ? : ; - return ( { toggleSidebarPinState(); }} > - + diff --git a/plugins/user-settings/src/components/ThemeToggle.tsx b/plugins/user-settings/src/components/ThemeToggle.tsx index b041e1f76b..111bd838a8 100644 --- a/plugins/user-settings/src/components/ThemeToggle.tsx +++ b/plugins/user-settings/src/components/ThemeToggle.tsx @@ -17,7 +17,7 @@ import React, { cloneElement } from 'react'; import { useObservable } from 'react-use'; import AutoIcon from '@material-ui/icons/BrightnessAuto'; -import { AppTheme, appThemeApiRef, useApi } from '@backstage/core'; +import { appThemeApiRef, useApi } from '@backstage/core'; import ToggleButton from '@material-ui/lab/ToggleButton'; import ToggleButtonGroup from '@material-ui/lab/ToggleButtonGroup'; import { @@ -27,6 +27,42 @@ import { 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, + }) + ) : ( + + ); + +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) => ( + + + {children} + + +); + export const ThemeToggle = () => { const appThemeApi = useApi(appThemeApiRef); const themeId = useObservable( @@ -47,36 +83,6 @@ export const ThemeToggle = () => { } }; - // ToggleButtonGroup uses React.children.map instead of context - // so wrapping with Tooltip breaks ToggleButton functionality. - const TooltipToggleButton = ({ - children, - title, - value, - ...props - }: { - children: JSX.Element; - title: string; - value: string; - }) => ( - - - {children} - - - ); - - const ThemeIcon = ({ theme }: { theme: AppTheme }) => { - const themeIcon = themeIds.find(t => t.id === theme.id)?.icon; - return themeIcon ? ( - cloneElement(themeIcon, { - color: themeId === theme.id ? 'primary' : undefined, - }) - ) : ( - - ); - }; - return ( @@ -87,20 +93,24 @@ export const ThemeToggle = () => { value={themeId ?? 'auto'} onChange={handleSetTheme} > - {themeIds.map(theme => ( - - - - ))} - - + {themeIds.map(theme => { + const themeIcon = themeIds.find(t => t.id === theme.id)?.icon; + + return ( + + + + ); + })} + + - - + + From 8d68c6652a06e159422cd3322a1ffb1197cf5922 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 6 Oct 2020 13:06:58 +0200 Subject: [PATCH 19/27] Add settings page and remove popup --- packages/app/src/App.tsx | 2 + packages/app/src/components/Root/Root.tsx | 4 +- .../src/layout/Sidebar/Sidebar.stories.tsx | 2 - plugins/user-settings/package.json | 1 + ...AuthProviderList.tsx => AuthProviders.tsx} | 7 +- .../DefaultProviderSettings.test.tsx | 2 +- ...{FeatureFlagsList.tsx => FeatureFlags.tsx} | 31 ++--- .../{AppSettingsList.tsx => General.tsx} | 25 ++-- .../src/components/PinButton.test.tsx | 2 +- .../user-settings/src/components/Profile.tsx | 54 +++++++++ .../{UserSettings.test.tsx => Settings.tsx} | 26 +++-- .../src/components/SettingsDialog.test.tsx | 56 --------- .../src/components/SettingsDialog.tsx | 110 ------------------ .../src/components/SettingsPage.tsx | 64 ++++++++++ .../src/components/SignInAvatar.tsx | 14 +-- .../src/components/ThemeToggle.test.tsx | 2 +- .../src/components/UserSettings.tsx | 94 --------------- .../src/components/UserSettingsMenu.test.tsx | 2 +- plugins/user-settings/src/index.ts | 3 +- plugins/user-settings/src/plugin.ts | 7 +- 20 files changed, 198 insertions(+), 310 deletions(-) rename plugins/user-settings/src/components/{AuthProviderList.tsx => AuthProviders.tsx} (81%) rename plugins/user-settings/src/components/{FeatureFlagsList.tsx => FeatureFlags.tsx} (78%) rename plugins/user-settings/src/components/{AppSettingsList.tsx => General.tsx} (63%) create mode 100644 plugins/user-settings/src/components/Profile.tsx rename plugins/user-settings/src/components/{UserSettings.test.tsx => Settings.tsx} (55%) delete mode 100644 plugins/user-settings/src/components/SettingsDialog.test.tsx delete mode 100644 plugins/user-settings/src/components/SettingsDialog.tsx create mode 100644 plugins/user-settings/src/components/SettingsPage.tsx delete mode 100644 plugins/user-settings/src/components/UserSettings.tsx diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 667b14366f..81d08f58a9 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -33,6 +33,7 @@ import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse'; import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component'; +import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; import { Route, Routes, Navigate } from 'react-router'; import { EntityPage } from './components/catalog/EntityPage'; @@ -81,6 +82,7 @@ const AppRoutes = () => ( path="/register-component" element={} /> + } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 9f5387cea8..e947e5c91d 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -38,7 +38,7 @@ import { } from '@backstage/core'; import { NavLink } from 'react-router-dom'; import { graphiQLRouteRef } from '@backstage/plugin-graphiql'; -import { UserSettings } from '@backstage/plugin-user-settings'; +import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; const useSidebarLogoStyles = makeStyles({ root: { @@ -102,7 +102,7 @@ const Root: FC<{}> = ({ children }) => ( /> - + {children} diff --git a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx index 14bf6e69ed..ec5746d428 100644 --- a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx @@ -56,7 +56,6 @@ const handleSearch = (input: string) => { export const SampleSidebar = () => ( - {/* */} @@ -92,7 +91,6 @@ const apis = ApiRegistry.from([ export const WithUserSettingsPlugin = () => ( - {/* */} diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 1c5d489747..c744f757c5 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -28,6 +28,7 @@ "@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": { diff --git a/plugins/user-settings/src/components/AuthProviderList.tsx b/plugins/user-settings/src/components/AuthProviders.tsx similarity index 81% rename from plugins/user-settings/src/components/AuthProviderList.tsx rename to plugins/user-settings/src/components/AuthProviders.tsx index 9a2096212d..917e70b067 100644 --- a/plugins/user-settings/src/components/AuthProviderList.tsx +++ b/plugins/user-settings/src/components/AuthProviders.tsx @@ -16,11 +16,14 @@ import React from 'react'; import { List } from '@material-ui/core'; +import { InfoCard } from '@backstage/core'; type Props = { providers: React.ReactNode; }; -export const AuthProvidersList = ({ providers }: Props) => ( - {providers} +export const AuthProviders = ({ providers }: Props) => ( + + {providers} + ); diff --git a/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx b/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx index 8f6e4b72dc..bc4e0da363 100644 --- a/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx +++ b/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx @@ -23,7 +23,7 @@ import { } from '@backstage/core'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; -import * as React from 'react'; +import React from 'react'; import { DefaultProviderSettings } from './DefaultProviderSettings'; const mockSignInHandler = jest.fn().mockReturnValue(''); diff --git a/plugins/user-settings/src/components/FeatureFlagsList.tsx b/plugins/user-settings/src/components/FeatureFlags.tsx similarity index 78% rename from plugins/user-settings/src/components/FeatureFlagsList.tsx rename to plugins/user-settings/src/components/FeatureFlags.tsx index 2636d0e66f..d3f21a7c6e 100644 --- a/plugins/user-settings/src/components/FeatureFlagsList.tsx +++ b/plugins/user-settings/src/components/FeatureFlags.tsx @@ -22,12 +22,13 @@ import { FeatureFlagName, FeatureFlagState, FeatureFlagsRegistryItem, + InfoCard, } from '@backstage/core'; import { FlagItem } from './FeatureFlagsItem'; type Props = { featureFlags: FeatureFlagsRegistryItem[] }; -export const FeatureFlagsList = ({ featureFlags }: Props) => { +export const FeatureFlags = ({ featureFlags }: Props) => { const featureFlagApi = useApi(featureFlagsApiRef); const initialFlagState = featureFlags.reduce( (result, featureFlag: FeatureFlagsRegistryItem) => { @@ -57,19 +58,21 @@ export const FeatureFlagsList = ({ featureFlags }: Props) => { ); return ( - - {featureFlags.map(featureFlag => { - const enabled = Boolean(state[featureFlag.name]); + + + {featureFlags.map(featureFlag => { + const enabled = Boolean(state[featureFlag.name]); - return ( - - ); - })} - + return ( + + ); + })} + + ); }; diff --git a/plugins/user-settings/src/components/AppSettingsList.tsx b/plugins/user-settings/src/components/General.tsx similarity index 63% rename from plugins/user-settings/src/components/AppSettingsList.tsx rename to plugins/user-settings/src/components/General.tsx index 3f2b455520..00ac17dfe1 100644 --- a/plugins/user-settings/src/components/AppSettingsList.tsx +++ b/plugins/user-settings/src/components/General.tsx @@ -13,14 +13,25 @@ * 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 { List } from '@material-ui/core'; -import { ThemeToggle } from './ThemeToggle'; import { PinButton } from './PinButton'; +import { Profile } from './Profile'; +import { ThemeToggle } from './ThemeToggle'; -export const AppSettingsList = () => ( - - - - +export const General = () => ( + + + + + + + + + + + + + ); diff --git a/plugins/user-settings/src/components/PinButton.test.tsx b/plugins/user-settings/src/components/PinButton.test.tsx index a800193c06..76af8ce8d4 100644 --- a/plugins/user-settings/src/components/PinButton.test.tsx +++ b/plugins/user-settings/src/components/PinButton.test.tsx @@ -17,7 +17,7 @@ import { SidebarPinStateContext } from '@backstage/core'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; -import * as React from 'react'; +import React from 'react'; import { PinButton } from './PinButton'; describe('', () => { diff --git a/plugins/user-settings/src/components/Profile.tsx b/plugins/user-settings/src/components/Profile.tsx new file mode 100644 index 0000000000..5bb9485f8d --- /dev/null +++ b/plugins/user-settings/src/components/Profile.tsx @@ -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 ( + + + + + + + + + + + + {displayName} + + + {profile.email} + + + + + + + + + + + + ); +}; diff --git a/plugins/user-settings/src/components/UserSettings.test.tsx b/plugins/user-settings/src/components/Settings.tsx similarity index 55% rename from plugins/user-settings/src/components/UserSettings.test.tsx rename to plugins/user-settings/src/components/Settings.tsx index 1d43f4093e..ae141457df 100644 --- a/plugins/user-settings/src/components/UserSettings.test.tsx +++ b/plugins/user-settings/src/components/Settings.tsx @@ -14,13 +14,21 @@ * limitations under the License. */ -import * as React from 'react'; -import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; -import { UserSettings } from './UserSettings'; +import React from 'react'; +import { SidebarItem } from '@backstage/core'; +import { SignInAvatar } from './SignInAvatar'; +import { useUserProfile } from './useUserProfileInfo'; +import { settingsRouteRef } from '../plugin'; -describe('', () => { - it('renders', async () => { - const rendered = await renderWithEffects(wrapInTestApp()); - expect(rendered.getByText('G')).toBeInTheDocument(); - }); -}); +export const Settings = () => { + const { displayName } = useUserProfile(); + const SidebarAvatar = () => ; + + return ( + + ); +}; diff --git a/plugins/user-settings/src/components/SettingsDialog.test.tsx b/plugins/user-settings/src/components/SettingsDialog.test.tsx deleted file mode 100644 index fb32e9cfdb..0000000000 --- a/plugins/user-settings/src/components/SettingsDialog.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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, - FeatureFlags, - featureFlagsApiRef, -} from '@backstage/core'; -import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; -import { fireEvent } from '@testing-library/react'; -import React from 'react'; -import { SettingsDialog } from './SettingsDialog'; - -const apiRegistry = ApiRegistry.from([ - [featureFlagsApiRef, new FeatureFlags()], -]); - -describe('', () => { - const mockFn = jest.fn(); - - it('displays the users name and email, and the tabs and titles and updates the position', async () => { - const rendered = await renderWithEffects( - wrapInTestApp( - - - , - ), - ); - - expect(rendered.getByText('Guest')).toBeInTheDocument(); - expect(rendered.getByText('guest@example.com')).toBeInTheDocument(); - expect(rendered.getByText('App Settings')).toBeInTheDocument(); - expect(rendered.getByText('Additional Settings')).toBeInTheDocument(); - expect(rendered.getByText('Auth Providers')).toBeInTheDocument(); - expect(rendered.getByText('Feature Flags')).toBeInTheDocument(); - - const flagButton = rendered.getByText('Feature Flags'); - expect(mockFn).toBeCalledTimes(1); - fireEvent.click(flagButton); - expect(mockFn).toBeCalledTimes(2); - }); -}); diff --git a/plugins/user-settings/src/components/SettingsDialog.tsx b/plugins/user-settings/src/components/SettingsDialog.tsx deleted file mode 100644 index c6d9cfa8c5..0000000000 --- a/plugins/user-settings/src/components/SettingsDialog.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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, { ChangeEvent, useEffect, useState } from 'react'; -import { Card, CardContent, CardHeader, makeStyles } 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, - TabbedCard, - CardTab, - InfoCard, -} from '@backstage/core'; -import { DefaultProviderSettings } from './DefaultProviderSettings'; -import { ProviderSettings } from './UserSettings'; -import { Alert } from '@material-ui/lab'; - -const useStyles = makeStyles({ - root: { - minWidth: 400, - maxWidth: 500, - }, -}); - -type Props = { - providerSettings?: ProviderSettings; - updatePosition: () => void; -}; - -export const SettingsDialog = ({ providerSettings, updatePosition }: Props) => { - const classes = useStyles(); - const { profile, displayName } = useUserProfile(); - const featureFlagsApi = useApi(featureFlagsApiRef); - const featureFlags = featureFlagsApi.getRegisteredFlags(); - const [selectedTab, setSelectedTab] = useState('auth'); - - const providers = providerSettings ?? ; - const ref = React.useRef(updatePosition); - - const handleChange = ( - _ev: ChangeEvent<{}>, - newSelectedTab: string | number, - ) => { - setSelectedTab(newSelectedTab); - }; - - // Update the position of the popover to handle different heights - useEffect(() => { - ref.current?.(); - }, [selectedTab]); - - return ( - - } - action={} - title={displayName} - subheader={profile.email} - /> - - - - - - - - - - {featureFlags.length > 0 ? ( - - ) : ( - // TODO(marcuseide): Replace with empty state component? - No registered Feature Flags found - )} - - - - - ); -}; diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage.tsx new file mode 100644 index 0000000000..de76866653 --- /dev/null +++ b/plugins/user-settings/src/components/SettingsPage.tsx @@ -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, { useState } from 'react'; +import { + Content, + featureFlagsApiRef, + Header, + HeaderTabs, + Page, + pageTheme, + useApi, +} from '@backstage/core'; +import { General } from './General'; +import { AuthProviders } from './AuthProviders'; +import { DefaultProviderSettings } from './DefaultProviderSettings'; +import { FeatureFlags } from './FeatureFlags'; + +type Props = { + providerSettings?: JSX.Element; +}; + +export const SettingsPage = ({ providerSettings }: Props) => { + const [activeTab, setActiveTab] = useState(0); + const onTabChange = (index: number) => { + setActiveTab(index); + }; + const featureFlagsApi = useApi(featureFlagsApiRef); + const featureFlags = featureFlagsApi.getRegisteredFlags(); + const providers = providerSettings ?? ; + + const tabs = [ + { id: 'general', label: 'General' }, + { id: 'auth-providers', label: 'Authentication Providers' }, + { id: 'feature-flags', label: 'Feature Flags' }, + ]; + + const content = [ + , + , + , + ]; + + return ( + +
+ + {content[activeTab]} + + ); +}; diff --git a/plugins/user-settings/src/components/SignInAvatar.tsx b/plugins/user-settings/src/components/SignInAvatar.tsx index 11a5e6ed99..72c636ffe7 100644 --- a/plugins/user-settings/src/components/SignInAvatar.tsx +++ b/plugins/user-settings/src/components/SignInAvatar.tsx @@ -20,23 +20,21 @@ import { makeStyles, Avatar } from '@material-ui/core'; import { useUserProfile } from './useUserProfileInfo'; import { sidebarConfig } from '@backstage/core'; -const useStyles = makeStyles({ +const useStyles = makeStyles(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, displayName } = useUserProfile(); + const { profile } = useUserProfile(); - return ( - - {displayName[0]} - - ); + return ; }; diff --git a/plugins/user-settings/src/components/ThemeToggle.test.tsx b/plugins/user-settings/src/components/ThemeToggle.test.tsx index df36a985f6..79d9210826 100644 --- a/plugins/user-settings/src/components/ThemeToggle.test.tsx +++ b/plugins/user-settings/src/components/ThemeToggle.test.tsx @@ -23,7 +23,7 @@ import { import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { lightTheme } from '@backstage/theme'; import { fireEvent } from '@testing-library/react'; -import * as React from 'react'; +import React from 'react'; import { ThemeToggle } from './ThemeToggle'; const mockTheme = { diff --git a/plugins/user-settings/src/components/UserSettings.tsx b/plugins/user-settings/src/components/UserSettings.tsx deleted file mode 100644 index 34d7fa02be..0000000000 --- a/plugins/user-settings/src/components/UserSettings.tsx +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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, useCallback } from 'react'; -import { Popover, PopoverActions } from '@material-ui/core'; -import { SignInAvatar } from './SignInAvatar'; -import { SettingsDialog } from './SettingsDialog'; -import { SidebarItem, SidebarContext } from '@backstage/core'; -import { useUserProfile } from './useUserProfileInfo'; - -export type ProviderSettings = JSX.Element; - -type Props = { - /** - * Available auth providers. Providers will be rendered as children - * of a MUI List. - * - * If undefined, providers that are configured in app-config.yaml - * will be displayed instead. - */ - providerSettings?: ProviderSettings; -}; - -export const UserSettings = ({ providerSettings }: Props) => { - const { isOpen: sidebarOpen } = useContext(SidebarContext); - const { displayName } = useUserProfile(); - const [open, setOpen] = React.useState(false); - const [anchorEl, setAnchorEl] = React.useState( - undefined, - ); - const popoverActionRef = React.useRef(null); - - const updatePosition = useCallback(() => { - popoverActionRef?.current?.updatePosition(); - }, []); - - const handleOpen = (event?: React.MouseEvent) => { - setAnchorEl(event?.currentTarget ?? undefined); - setOpen(true); - }; - - const handleClose = () => { - setAnchorEl(undefined); - setOpen(false); - }; - - useEffect(() => { - if (!sidebarOpen && open) setOpen(false); - }, [open, sidebarOpen]); - - const SidebarAvatar = () => ; - - return ( - <> - - - - - - ); -}; diff --git a/plugins/user-settings/src/components/UserSettingsMenu.test.tsx b/plugins/user-settings/src/components/UserSettingsMenu.test.tsx index b1ad059611..c86d702e30 100644 --- a/plugins/user-settings/src/components/UserSettingsMenu.test.tsx +++ b/plugins/user-settings/src/components/UserSettingsMenu.test.tsx @@ -16,7 +16,7 @@ import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; -import * as React from 'react'; +import React from 'react'; import { UserSettingsMenu } from './UserSettingsMenu'; describe('', () => { diff --git a/plugins/user-settings/src/index.ts b/plugins/user-settings/src/index.ts index 311ae69a51..113930902a 100644 --- a/plugins/user-settings/src/index.ts +++ b/plugins/user-settings/src/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ export { plugin } from './plugin'; -export { UserSettings } from './components/UserSettings'; +export { Settings } from './components/Settings'; +export { SettingsPage as Router } from './components/SettingsPage'; diff --git a/plugins/user-settings/src/plugin.ts b/plugins/user-settings/src/plugin.ts index 4b556180f2..2f896c8197 100644 --- a/plugins/user-settings/src/plugin.ts +++ b/plugins/user-settings/src/plugin.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createPlugin } from '@backstage/core'; +import { createPlugin, createRouteRef } from '@backstage/core'; + +export const settingsRouteRef = createRouteRef({ + path: '/settings', + title: 'Settings', +}); export const plugin = createPlugin({ id: 'user-settings', From 11b9c82ac94dd2b73dcff7a8e5075edfd5eb6abf Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 6 Oct 2020 13:22:04 +0200 Subject: [PATCH 20/27] Move components into seperate folders --- plugins/user-settings/package.json | 2 +- .../{ => AuthProviders}/AuthProviders.tsx | 0 .../DefaultProviderSettings.test.tsx | 0 .../DefaultProviderSettings.tsx | 0 .../ProviderSettingsItem.tsx | 0 .../src/components/AuthProviders/index.ts | 18 ++++++++++++++++++ .../{ => FeatureFlags}/FeatureFlags.tsx | 0 .../{ => FeatureFlags}/FeatureFlagsItem.tsx | 0 .../src/components/{ => FeatureFlags}/index.ts | 2 +- .../src/components/{ => General}/General.tsx | 0 .../{ => General}/PinButton.test.tsx | 0 .../src/components/{ => General}/PinButton.tsx | 0 .../src/components/{ => General}/Profile.tsx | 2 +- .../components/{ => General}/SignInAvatar.tsx | 2 +- .../{ => General}/ThemeToggle.test.tsx | 0 .../components/{ => General}/ThemeToggle.tsx | 0 .../{ => General}/UserSettingsMenu.test.tsx | 0 .../{ => General}/UserSettingsMenu.tsx | 0 .../src/components/General/index.ts | 18 ++++++++++++++++++ .../user-settings/src/components/Settings.tsx | 2 +- .../src/components/SettingsPage.tsx | 3 +-- 21 files changed, 42 insertions(+), 7 deletions(-) rename plugins/user-settings/src/components/{ => AuthProviders}/AuthProviders.tsx (100%) rename plugins/user-settings/src/components/{ => AuthProviders}/DefaultProviderSettings.test.tsx (100%) rename plugins/user-settings/src/components/{ => AuthProviders}/DefaultProviderSettings.tsx (100%) rename plugins/user-settings/src/components/{ => AuthProviders}/ProviderSettingsItem.tsx (100%) create mode 100644 plugins/user-settings/src/components/AuthProviders/index.ts rename plugins/user-settings/src/components/{ => FeatureFlags}/FeatureFlags.tsx (100%) rename plugins/user-settings/src/components/{ => FeatureFlags}/FeatureFlagsItem.tsx (100%) rename plugins/user-settings/src/components/{ => FeatureFlags}/index.ts (92%) rename plugins/user-settings/src/components/{ => General}/General.tsx (100%) rename plugins/user-settings/src/components/{ => General}/PinButton.test.tsx (100%) rename plugins/user-settings/src/components/{ => General}/PinButton.tsx (100%) rename plugins/user-settings/src/components/{ => General}/Profile.tsx (96%) rename plugins/user-settings/src/components/{ => General}/SignInAvatar.tsx (95%) rename plugins/user-settings/src/components/{ => General}/ThemeToggle.test.tsx (100%) rename plugins/user-settings/src/components/{ => General}/ThemeToggle.tsx (100%) rename plugins/user-settings/src/components/{ => General}/UserSettingsMenu.test.tsx (100%) rename plugins/user-settings/src/components/{ => General}/UserSettingsMenu.tsx (100%) create mode 100644 plugins/user-settings/src/components/General/index.ts diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index c744f757c5..b811213f13 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -4,7 +4,7 @@ "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", - "private": true, + "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/plugins/user-settings/src/components/AuthProviders.tsx b/plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx similarity index 100% rename from plugins/user-settings/src/components/AuthProviders.tsx rename to plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx diff --git a/plugins/user-settings/src/components/DefaultProviderSettings.test.tsx b/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.test.tsx similarity index 100% rename from plugins/user-settings/src/components/DefaultProviderSettings.test.tsx rename to plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.test.tsx diff --git a/plugins/user-settings/src/components/DefaultProviderSettings.tsx b/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx similarity index 100% rename from plugins/user-settings/src/components/DefaultProviderSettings.tsx rename to plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx diff --git a/plugins/user-settings/src/components/ProviderSettingsItem.tsx b/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx similarity index 100% rename from plugins/user-settings/src/components/ProviderSettingsItem.tsx rename to plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx diff --git a/plugins/user-settings/src/components/AuthProviders/index.ts b/plugins/user-settings/src/components/AuthProviders/index.ts new file mode 100644 index 0000000000..2f6a2b5ab1 --- /dev/null +++ b/plugins/user-settings/src/components/AuthProviders/index.ts @@ -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'; diff --git a/plugins/user-settings/src/components/FeatureFlags.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx similarity index 100% rename from plugins/user-settings/src/components/FeatureFlags.tsx rename to plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx diff --git a/plugins/user-settings/src/components/FeatureFlagsItem.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx similarity index 100% rename from plugins/user-settings/src/components/FeatureFlagsItem.tsx rename to plugins/user-settings/src/components/FeatureFlags/FeatureFlagsItem.tsx diff --git a/plugins/user-settings/src/components/index.ts b/plugins/user-settings/src/components/FeatureFlags/index.ts similarity index 92% rename from plugins/user-settings/src/components/index.ts rename to plugins/user-settings/src/components/FeatureFlags/index.ts index 59a8b425e4..37c9fd1fd2 100644 --- a/plugins/user-settings/src/components/index.ts +++ b/plugins/user-settings/src/components/FeatureFlags/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { UserSettings } from './UserSettings'; +export { FeatureFlags } from './FeatureFlags'; diff --git a/plugins/user-settings/src/components/General.tsx b/plugins/user-settings/src/components/General/General.tsx similarity index 100% rename from plugins/user-settings/src/components/General.tsx rename to plugins/user-settings/src/components/General/General.tsx diff --git a/plugins/user-settings/src/components/PinButton.test.tsx b/plugins/user-settings/src/components/General/PinButton.test.tsx similarity index 100% rename from plugins/user-settings/src/components/PinButton.test.tsx rename to plugins/user-settings/src/components/General/PinButton.test.tsx diff --git a/plugins/user-settings/src/components/PinButton.tsx b/plugins/user-settings/src/components/General/PinButton.tsx similarity index 100% rename from plugins/user-settings/src/components/PinButton.tsx rename to plugins/user-settings/src/components/General/PinButton.tsx diff --git a/plugins/user-settings/src/components/Profile.tsx b/plugins/user-settings/src/components/General/Profile.tsx similarity index 96% rename from plugins/user-settings/src/components/Profile.tsx rename to plugins/user-settings/src/components/General/Profile.tsx index 5bb9485f8d..b0231d95b6 100644 --- a/plugins/user-settings/src/components/Profile.tsx +++ b/plugins/user-settings/src/components/General/Profile.tsx @@ -18,7 +18,7 @@ import { Grid, Typography } from '@material-ui/core'; import React from 'react'; import { SignInAvatar } from './SignInAvatar'; import { UserSettingsMenu } from './UserSettingsMenu'; -import { useUserProfile } from './useUserProfileInfo'; +import { useUserProfile } from '../useUserProfileInfo'; export const Profile = () => { const { profile, displayName } = useUserProfile(); diff --git a/plugins/user-settings/src/components/SignInAvatar.tsx b/plugins/user-settings/src/components/General/SignInAvatar.tsx similarity index 95% rename from plugins/user-settings/src/components/SignInAvatar.tsx rename to plugins/user-settings/src/components/General/SignInAvatar.tsx index 72c636ffe7..8a5bbd75bf 100644 --- a/plugins/user-settings/src/components/SignInAvatar.tsx +++ b/plugins/user-settings/src/components/General/SignInAvatar.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { BackstageTheme } from '@backstage/theme'; import { makeStyles, Avatar } from '@material-ui/core'; -import { useUserProfile } from './useUserProfileInfo'; +import { useUserProfile } from '../useUserProfileInfo'; import { sidebarConfig } from '@backstage/core'; const useStyles = makeStyles(theme => ({ diff --git a/plugins/user-settings/src/components/ThemeToggle.test.tsx b/plugins/user-settings/src/components/General/ThemeToggle.test.tsx similarity index 100% rename from plugins/user-settings/src/components/ThemeToggle.test.tsx rename to plugins/user-settings/src/components/General/ThemeToggle.test.tsx diff --git a/plugins/user-settings/src/components/ThemeToggle.tsx b/plugins/user-settings/src/components/General/ThemeToggle.tsx similarity index 100% rename from plugins/user-settings/src/components/ThemeToggle.tsx rename to plugins/user-settings/src/components/General/ThemeToggle.tsx diff --git a/plugins/user-settings/src/components/UserSettingsMenu.test.tsx b/plugins/user-settings/src/components/General/UserSettingsMenu.test.tsx similarity index 100% rename from plugins/user-settings/src/components/UserSettingsMenu.test.tsx rename to plugins/user-settings/src/components/General/UserSettingsMenu.test.tsx diff --git a/plugins/user-settings/src/components/UserSettingsMenu.tsx b/plugins/user-settings/src/components/General/UserSettingsMenu.tsx similarity index 100% rename from plugins/user-settings/src/components/UserSettingsMenu.tsx rename to plugins/user-settings/src/components/General/UserSettingsMenu.tsx diff --git a/plugins/user-settings/src/components/General/index.ts b/plugins/user-settings/src/components/General/index.ts new file mode 100644 index 0000000000..2015d345fe --- /dev/null +++ b/plugins/user-settings/src/components/General/index.ts @@ -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'; diff --git a/plugins/user-settings/src/components/Settings.tsx b/plugins/user-settings/src/components/Settings.tsx index ae141457df..f754f1e7c3 100644 --- a/plugins/user-settings/src/components/Settings.tsx +++ b/plugins/user-settings/src/components/Settings.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { SidebarItem } from '@backstage/core'; -import { SignInAvatar } from './SignInAvatar'; +import { SignInAvatar } from './General'; import { useUserProfile } from './useUserProfileInfo'; import { settingsRouteRef } from '../plugin'; diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage.tsx index de76866653..6ed8e33670 100644 --- a/plugins/user-settings/src/components/SettingsPage.tsx +++ b/plugins/user-settings/src/components/SettingsPage.tsx @@ -25,8 +25,7 @@ import { useApi, } from '@backstage/core'; import { General } from './General'; -import { AuthProviders } from './AuthProviders'; -import { DefaultProviderSettings } from './DefaultProviderSettings'; +import { AuthProviders, DefaultProviderSettings } from './AuthProviders'; import { FeatureFlags } from './FeatureFlags'; type Props = { From 346857e9c6607c8299896164272b22b7bbe47f8d Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 6 Oct 2020 13:22:30 +0200 Subject: [PATCH 21/27] Fix sidebar add app template --- .../src/layout/Sidebar/Sidebar.stories.tsx | 38 +------------------ .../default-app/packages/app/src/sidebar.tsx | 4 +- 2 files changed, 4 insertions(+), 38 deletions(-) diff --git a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx index ec5746d428..0710be4566 100644 --- a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx @@ -26,18 +26,8 @@ import { import HomeOutlinedIcon from '@material-ui/icons/HomeOutlined'; import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; import { MemoryRouter } from 'react-router-dom'; -import { - ApiProvider, - ApiRegistry, - appThemeApiRef, - AppThemeSelector, - configApiRef, - ConfigReader, - FeatureFlags, - featureFlagsApiRef, -} from '@backstage/core-api'; // eslint-disable-next-line import/no-extraneous-dependencies -import { UserSettings } from '@backstage/plugin-user-settings'; +import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; export default { title: 'Sidebar', @@ -67,28 +57,6 @@ export const SampleSidebar = () => ( ); -const createConfig = () => - ConfigReader.fromConfigs([ - { - context: '', - data: { - auth: { - providers: { - google: { development: {} }, - }, - }, - }, - }, - ]); - -const config = createConfig(); - -const apis = ApiRegistry.from([ - [configApiRef, config], - [featureFlagsApiRef, new FeatureFlags()], - [appThemeApiRef, AppThemeSelector.createWithStorage([])], -]); - export const WithUserSettingsPlugin = () => ( @@ -100,8 +68,6 @@ export const WithUserSettingsPlugin = () => ( - - - + ); diff --git a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx index 8ffd042543..901347645e 100644 --- a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx @@ -9,7 +9,7 @@ import { Link, makeStyles } from '@material-ui/core'; import { NavLink } from 'react-router-dom'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; -import { UserSettings } from '@backstage/plugin-user-settings' +import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; import { Sidebar, @@ -36,7 +36,7 @@ export const AppSidebar = () => ( - + ); From ae0de366896d8d2459c0718e8942429a556ad8d1 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 6 Oct 2020 13:24:39 +0200 Subject: [PATCH 22/27] bump version --- plugins/user-settings/package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index b811213f13..29f86c774b 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.1.1-alpha.23", + "version": "0.1.1-alpha.24", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.23", - "@backstage/theme": "^0.1.1-alpha.23", + "@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", @@ -32,9 +32,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.23", - "@backstage/dev-utils": "^0.1.1-alpha.23", - "@backstage/test-utils": "^0.1.1-alpha.23", + "@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", From d03a7646f575cf6d8905e92b652067f82d98393f Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 6 Oct 2020 15:00:41 +0200 Subject: [PATCH 23/27] Handle empties --- packages/app/package.json | 1 + .../AuthProviders/AuthProviders.tsx | 29 +++-- .../AuthProviders/DefaultProviderSettings.tsx | 114 +++++++++--------- .../AuthProviders/EmptyProviders.tsx | 59 +++++++++ .../components/FeatureFlags/EmptyFlags.tsx | 59 +++++++++ .../components/FeatureFlags/FeatureFlags.tsx | 30 +++-- .../src/components/SettingsPage.tsx | 19 +-- 7 files changed, 217 insertions(+), 94 deletions(-) create mode 100644 plugins/user-settings/src/components/AuthProviders/EmptyProviders.tsx create mode 100644 plugins/user-settings/src/components/FeatureFlags/EmptyFlags.tsx diff --git a/packages/app/package.json b/packages/app/package.json index 07814cdb0d..0815f00e3a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -10,6 +10,7 @@ "@backstage/plugin-api-docs": "^0.1.1-alpha.24", "@backstage/plugin-catalog": "^0.1.1-alpha.24", "@backstage/plugin-circleci": "^0.1.1-alpha.24", + "@backstage/plugin-cloudbuild": "^0.1.1-alpha.24", "@backstage/plugin-cost-insights": "^0.1.1-alpha.24", "@backstage/plugin-explore": "^0.1.1-alpha.24", "@backstage/plugin-gcp-projects": "^0.1.1-alpha.24", diff --git a/plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx b/plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx index 917e70b067..563bf44150 100644 --- a/plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx +++ b/plugins/user-settings/src/components/AuthProviders/AuthProviders.tsx @@ -16,14 +16,29 @@ import React from 'react'; import { List } from '@material-ui/core'; -import { InfoCard } from '@backstage/core'; +import { configApiRef, InfoCard, useApi } from '@backstage/core'; +import { EmptyProviders } from './EmptyProviders'; +import { DefaultProviderSettings } from './DefaultProviderSettings'; type Props = { - providers: React.ReactNode; + providerSettings?: JSX.Element; }; -export const AuthProviders = ({ providers }: Props) => ( - - {providers} - -); +export const AuthProviders = ({ providerSettings }: Props) => { + const configApi = useApi(configApiRef); + const providersConfig = configApi.getOptionalConfig('auth.providers'); + const configuredProviders = providersConfig?.keys() || []; + const providers = providerSettings ?? ( + + ); + + if (!providerSettings && !configuredProviders?.length) { + return ; + } + + return ( + + {providers} + + ); +}; diff --git a/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx b/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx index e6e5c21d86..a1dab1e7d4 100644 --- a/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx +++ b/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx @@ -14,74 +14,70 @@ * limitations under the License. */ import { - configApiRef, githubAuthApiRef, gitlabAuthApiRef, googleAuthApiRef, oauth2ApiRef, oktaAuthApiRef, microsoftAuthApiRef, - useApi, } from '@backstage/core'; import Star from '@material-ui/icons/Star'; import React from 'react'; import { ProviderSettingsItem } from './ProviderSettingsItem'; -export const DefaultProviderSettings = () => { - const configApi = useApi(configApiRef); - const providersConfig = configApi.getOptionalConfig('auth.providers'); - const providers = providersConfig?.keys() ?? []; - - return ( - <> - {providers.includes('google') && ( - - )} - {providers.includes('microsoft') && ( - - )} - {providers.includes('github') && ( - - )} - {providers.includes('gitlab') && ( - - )} - {providers.includes('okta') && ( - - )} - {providers.includes('oauth2') && ( - - )} - - ); +type Props = { + configuredProviders: string[]; }; + +export const DefaultProviderSettings = ({ configuredProviders }: Props) => ( + <> + {configuredProviders.includes('google') && ( + + )} + {configuredProviders.includes('microsoft') && ( + + )} + {configuredProviders.includes('github') && ( + + )} + {configuredProviders.includes('gitlab') && ( + + )} + {configuredProviders.includes('okta') && ( + + )} + {configuredProviders.includes('oauth2') && ( + + )} + +); diff --git a/plugins/user-settings/src/components/AuthProviders/EmptyProviders.tsx b/plugins/user-settings/src/components/AuthProviders/EmptyProviders.tsx new file mode 100644 index 0000000000..864efb9cd5 --- /dev/null +++ b/plugins/user-settings/src/components/AuthProviders/EmptyProviders.tsx @@ -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 = ` +import { createPlugin } from '@backstage/core'; + +export default createPlugin({ + id: 'welcome', + register({ router, featureFlags }) { + featureFlags.register('enable-example-feature'); + }, +}); +`; + +export const EmptyProviders = () => ( + + + An example how how to add a feature flags is highlighted below: + + + + + } + /> +); diff --git a/plugins/user-settings/src/components/FeatureFlags/EmptyFlags.tsx b/plugins/user-settings/src/components/FeatureFlags/EmptyFlags.tsx new file mode 100644 index 0000000000..438cb8b2a2 --- /dev/null +++ b/plugins/user-settings/src/components/FeatureFlags/EmptyFlags.tsx @@ -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 = ` +import { createPlugin } from '@backstage/core'; + +export default createPlugin({ + id: 'welcome', + register({ router, featureFlags }) { + featureFlags.register('enable-example-feature'); + }, +}); +`; + +export const EmptyFlags = () => ( + + + An example how how to add a feature flags is highlighted below: + + + + + } + /> +); diff --git a/plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx b/plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx index d3f21a7c6e..71f59c52b8 100644 --- a/plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx +++ b/plugins/user-settings/src/components/FeatureFlags/FeatureFlags.tsx @@ -14,25 +14,25 @@ * limitations under the License. */ -import React, { useState, useCallback } from 'react'; -import { List } from '@material-ui/core'; +import React, { useCallback, useState } from 'react'; import { - useApi, - featureFlagsApiRef, FeatureFlagName, - FeatureFlagState, + featureFlagsApiRef, FeatureFlagsRegistryItem, + FeatureFlagState, InfoCard, + useApi, } from '@backstage/core'; +import { List } from '@material-ui/core'; +import { EmptyFlags } from './EmptyFlags'; import { FlagItem } from './FeatureFlagsItem'; -type Props = { featureFlags: FeatureFlagsRegistryItem[] }; - -export const FeatureFlags = ({ featureFlags }: Props) => { - const featureFlagApi = useApi(featureFlagsApiRef); +export const FeatureFlags = () => { + const featureFlagsApi = useApi(featureFlagsApiRef); + const featureFlags = featureFlagsApi.getRegisteredFlags(); const initialFlagState = featureFlags.reduce( (result, featureFlag: FeatureFlagsRegistryItem) => { - const state = featureFlagApi.getFlags().get(featureFlag.name); + const state = featureFlagsApi.getFlags().get(featureFlag.name); result[featureFlag.name] = state; return result; @@ -46,17 +46,21 @@ export const FeatureFlags = ({ featureFlags }: Props) => { const toggleFlag = useCallback( (flagName: FeatureFlagName) => { - const newState = featureFlagApi.getFlags().toggle(flagName); + const newState = featureFlagsApi.getFlags().toggle(flagName); setState(prevState => ({ ...prevState, [flagName]: newState, })); - featureFlagApi.getFlags().save(); + featureFlagsApi.getFlags().save(); }, - [featureFlagApi], + [featureFlagsApi], ); + if (!featureFlags.length) { + return ; + } + return ( diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage.tsx index 6ed8e33670..86096e9a1f 100644 --- a/plugins/user-settings/src/components/SettingsPage.tsx +++ b/plugins/user-settings/src/components/SettingsPage.tsx @@ -15,17 +15,9 @@ */ import React, { useState } from 'react'; -import { - Content, - featureFlagsApiRef, - Header, - HeaderTabs, - Page, - pageTheme, - useApi, -} from '@backstage/core'; +import { Content, Header, HeaderTabs, Page, pageTheme } from '@backstage/core'; import { General } from './General'; -import { AuthProviders, DefaultProviderSettings } from './AuthProviders'; +import { AuthProviders } from './AuthProviders'; import { FeatureFlags } from './FeatureFlags'; type Props = { @@ -37,9 +29,6 @@ export const SettingsPage = ({ providerSettings }: Props) => { const onTabChange = (index: number) => { setActiveTab(index); }; - const featureFlagsApi = useApi(featureFlagsApiRef); - const featureFlags = featureFlagsApi.getRegisteredFlags(); - const providers = providerSettings ?? ; const tabs = [ { id: 'general', label: 'General' }, @@ -49,8 +38,8 @@ export const SettingsPage = ({ providerSettings }: Props) => { const content = [ , - , - , + , + , ]; return ( From 28f2d1e0307749fb26ddfbfd5d7484b9ad3bfe11 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 6 Oct 2020 15:02:23 +0200 Subject: [PATCH 24/27] Move story into this plugin --- .../src/layout/Sidebar/Sidebar.stories.tsx | 17 ---- packages/storybook/.storybook/main.js | 1 + .../SidebarWithSettings.stories.tsx | 81 +++++++++++++++++++ 3 files changed, 82 insertions(+), 17 deletions(-) create mode 100644 plugins/user-settings/src/components/SidebarWithSettings.stories.tsx diff --git a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx index 0710be4566..908fbba75c 100644 --- a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx @@ -26,8 +26,6 @@ import { import HomeOutlinedIcon from '@material-ui/icons/HomeOutlined'; import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; import { MemoryRouter } from 'react-router-dom'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; export default { title: 'Sidebar', @@ -56,18 +54,3 @@ export const SampleSidebar = () => ( ); - -export const WithUserSettingsPlugin = () => ( - - - - - - - - - - - - -); diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js index c83bb221be..2c58144acc 100644 --- a/packages/storybook/.storybook/main.js +++ b/packages/storybook/.storybook/main.js @@ -5,6 +5,7 @@ module.exports = { stories: [ '../../core/src/layout/**/*.stories.tsx', '../../core/src/components/**/*.stories.tsx', + '../../../plugins/**/src/**/*.stories.tsx', ], addons: [ '@storybook/addon-actions', diff --git a/plugins/user-settings/src/components/SidebarWithSettings.stories.tsx b/plugins/user-settings/src/components/SidebarWithSettings.stories.tsx new file mode 100644 index 0000000000..c3053e6851 --- /dev/null +++ b/plugins/user-settings/src/components/SidebarWithSettings.stories.tsx @@ -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 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) => ( + {storyFn()} + ), + ], +}; + +export const SidebarItem = () => ( + + + + + +); + +const createConfig = () => + ConfigReader.fromConfigs([ + { + context: '', + data: { + auth: { + providers: { + // google: { development: {} }, + }, + }, + }, + }, + ]); + +const config = createConfig(); + +const apis = ApiRegistry.from([ + [configApiRef, config], + [featureFlagsApiRef, new FeatureFlags()], + [appThemeApiRef, AppThemeSelector.createWithStorage([])], +]); + +export const TheSettingsPage = () => ( +
+ + + +
+); From b47299806e5753c904d9ddd546fbce29d9c15ad7 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 6 Oct 2020 15:19:56 +0200 Subject: [PATCH 25/27] Fix test --- ...ttings.test.tsx => AuthProviders.test.tsx} | 6 ++-- .../AuthProviders/EmptyProviders.tsx | 28 +++++++++---------- .../components/FeatureFlags/EmptyFlags.tsx | 5 ++-- .../SidebarWithSettings.stories.tsx | 4 +-- 4 files changed, 20 insertions(+), 23 deletions(-) rename plugins/user-settings/src/components/AuthProviders/{DefaultProviderSettings.test.tsx => AuthProviders.test.tsx} (92%) diff --git a/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.test.tsx b/plugins/user-settings/src/components/AuthProviders/AuthProviders.test.tsx similarity index 92% rename from plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.test.tsx rename to plugins/user-settings/src/components/AuthProviders/AuthProviders.test.tsx index bc4e0da363..4399b6bd3e 100644 --- a/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.test.tsx +++ b/plugins/user-settings/src/components/AuthProviders/AuthProviders.test.tsx @@ -24,7 +24,7 @@ import { import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; import React from 'react'; -import { DefaultProviderSettings } from './DefaultProviderSettings'; +import { AuthProviders } from './AuthProviders'; const mockSignInHandler = jest.fn().mockReturnValue(''); const mockGoogleAuth = { @@ -57,12 +57,12 @@ const apiRegistry = ApiRegistry.from([ [googleAuthApiRef, mockGoogleAuth], ]); -describe('', () => { +describe('', () => { it('displays a provider and calls its sign-in handler on click', async () => { const rendered = await renderWithEffects( wrapInTestApp( - + , ), ); diff --git a/plugins/user-settings/src/components/AuthProviders/EmptyProviders.tsx b/plugins/user-settings/src/components/AuthProviders/EmptyProviders.tsx index 864efb9cd5..653f449e92 100644 --- a/plugins/user-settings/src/components/AuthProviders/EmptyProviders.tsx +++ b/plugins/user-settings/src/components/AuthProviders/EmptyProviders.tsx @@ -18,38 +18,38 @@ 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'); - }, -}); +const EXAMPLE = `auth: + providers: + google: + development: + clientId: + $env: AUTH_GOOGLE_CLIENT_ID + clientSecret: + $env: AUTH_GOOGLE_CLIENT_SECRET `; export const EmptyProviders = () => ( - An example how how to add a feature flags is highlighted below: + Open app-config.yaml and make the changes as highlighted + below: diff --git a/plugins/user-settings/src/components/FeatureFlags/EmptyFlags.tsx b/plugins/user-settings/src/components/FeatureFlags/EmptyFlags.tsx index 438cb8b2a2..5df4df4450 100644 --- a/plugins/user-settings/src/components/FeatureFlags/EmptyFlags.tsx +++ b/plugins/user-settings/src/components/FeatureFlags/EmptyFlags.tsx @@ -18,8 +18,7 @@ import React from 'react'; import { CodeSnippet, EmptyState } from '@backstage/core'; import { Button, Typography } from '@material-ui/core'; -const EXAMPLE = ` -import { createPlugin } from '@backstage/core'; +const EXAMPLE = `import { createPlugin } from '@backstage/core'; export default createPlugin({ id: 'welcome', @@ -43,7 +42,7 @@ export const EmptyFlags = () => ( text={EXAMPLE} language="typescript" showLineNumbers - highlightedNumbers={[7]} + highlightedNumbers={[6]} customStyle={{ background: 'inherit', fontSize: '115%' }} />