Merge remote-tracking branch 'origin' into ndudnik/unregister-component
This commit is contained in:
@@ -12,15 +12,16 @@
|
||||
"@backstage/plugin-lighthouse": "^0.1.1-alpha.6",
|
||||
"@backstage/plugin-register-component": "^0.1.1-alpha.6",
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.6",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.6",
|
||||
"@backstage/plugin-tech-radar": "^0.1.1-alpha.6",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.6",
|
||||
"@backstage/theme": "^0.1.1-alpha.6",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.6",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-hot-loader": "^4.12.21",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use": "^14.2.0",
|
||||
"zen-observable": "^0.8.15"
|
||||
@@ -73,10 +74,10 @@
|
||||
}
|
||||
},
|
||||
"/catalog/api": {
|
||||
"target": "http://localhost:3003",
|
||||
"target": "http://localhost:7000",
|
||||
"changeOrigin": true,
|
||||
"pathRewrite": {
|
||||
"^/catalog/api/": "/"
|
||||
"^/catalog/api/": "/catalog/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import Root from './components/Root';
|
||||
import * as plugins from './plugins';
|
||||
import apis from './apis';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
|
||||
const app = createApp({
|
||||
apis,
|
||||
@@ -41,4 +42,4 @@ const App: FC<{}> = () => (
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
export default App;
|
||||
export default hot(App);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"version": "0.1.1-alpha.6",
|
||||
"main": "dist/index.esm.js",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"main:src": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -10,7 +11,7 @@
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"build": "backstage-cli build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const LOCATION_ANNOTATION = 'backstage.io/managed-by-location';
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { buildPackage, Output } from '../lib/packager';
|
||||
import { Command } from 'commander';
|
||||
|
||||
export default async (cmd: Command) => {
|
||||
let outputs = new Set<Output>();
|
||||
|
||||
const { outputs: outputsStr } = cmd as { outputs?: string };
|
||||
if (outputsStr) {
|
||||
for (const output of outputsStr.split(',') as (keyof typeof Output)[]) {
|
||||
if (output in Output) {
|
||||
outputs.add(Output[output]);
|
||||
} else {
|
||||
throw new Error(`Unknown output format: ${output}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
outputs = new Set([Output.types, Output.esm, Output.cjs]);
|
||||
}
|
||||
|
||||
await buildPackage({ outputs });
|
||||
};
|
||||
@@ -14,8 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { buildPackage } from '../../lib/packager';
|
||||
import { buildPackage, Output } from '../../lib/packager';
|
||||
|
||||
export default async () => {
|
||||
await buildPackage();
|
||||
await buildPackage({
|
||||
outputs: new Set([Output.esm, Output.types]),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -78,6 +78,12 @@ const main = (argv: string[]) => {
|
||||
.description('Diff an existing plugin with the creation template')
|
||||
.action(actionHandler(() => require('./commands/plugin/diff')));
|
||||
|
||||
program
|
||||
.command('build')
|
||||
.description('Build a package for publishing')
|
||||
.option('--outputs <formats>', 'List of formats to output [types,cjs,esm]')
|
||||
.action(actionHandler(() => require('./commands/build')));
|
||||
|
||||
program
|
||||
.command('lint')
|
||||
.option('--fix', 'Attempt to automatically fix violations')
|
||||
|
||||
@@ -24,11 +24,14 @@ import esbuild from 'rollup-plugin-esbuild';
|
||||
import imageFiles from 'rollup-plugin-image-files';
|
||||
import dts from 'rollup-plugin-dts';
|
||||
import json from '@rollup/plugin-json';
|
||||
import { RollupOptions } from 'rollup';
|
||||
import { RollupOptions, OutputOptions } from 'rollup';
|
||||
|
||||
import { BuildOptions, Output } from './types';
|
||||
import { paths } from '../paths';
|
||||
|
||||
export const makeConfigs = async (): Promise<RollupOptions[]> => {
|
||||
export const makeConfigs = async (
|
||||
options: BuildOptions,
|
||||
): Promise<RollupOptions[]> => {
|
||||
const typesInput = paths.resolveTargetRoot(
|
||||
'dist',
|
||||
relativePath(paths.targetRoot, paths.targetDir),
|
||||
@@ -43,13 +46,27 @@ export const makeConfigs = async (): Promise<RollupOptions[]> => {
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
input: 'src/index.ts',
|
||||
output: {
|
||||
const configs = new Array<RollupOptions>();
|
||||
|
||||
if (options.outputs.has(Output.cjs) || options.outputs.has(Output.esm)) {
|
||||
const output = new Array<OutputOptions>();
|
||||
|
||||
if (options.outputs.has(Output.cjs)) {
|
||||
output.push({
|
||||
file: 'dist/index.cjs.js',
|
||||
format: 'commonjs',
|
||||
});
|
||||
}
|
||||
if (options.outputs.has(Output.esm)) {
|
||||
output.push({
|
||||
file: 'dist/index.esm.js',
|
||||
format: 'module',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
configs.push({
|
||||
input: 'src/index.ts',
|
||||
output,
|
||||
plugins: [
|
||||
peerDepsExternal({
|
||||
includeDependencies: true,
|
||||
@@ -68,14 +85,19 @@ export const makeConfigs = async (): Promise<RollupOptions[]> => {
|
||||
target: 'es2019',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
});
|
||||
}
|
||||
|
||||
if (options.outputs.has(Output.types)) {
|
||||
configs.push({
|
||||
input: typesInput,
|
||||
output: {
|
||||
file: 'dist/index.d.ts',
|
||||
format: 'es',
|
||||
},
|
||||
plugins: [dts()],
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
return configs;
|
||||
};
|
||||
|
||||
@@ -15,3 +15,5 @@
|
||||
*/
|
||||
|
||||
export { buildPackage } from './packager';
|
||||
export { Output } from './types';
|
||||
export type { BuildOptions } from './types';
|
||||
|
||||
@@ -19,6 +19,7 @@ import chalk from 'chalk';
|
||||
import { relative as relativePath } from 'path';
|
||||
import { paths } from '../paths';
|
||||
import { makeConfigs } from './config';
|
||||
import { BuildOptions } from './types';
|
||||
|
||||
function formatErrorMessage(error: any) {
|
||||
let msg = '';
|
||||
@@ -80,7 +81,7 @@ async function build(config: RollupOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
export const buildPackage = async () => {
|
||||
const configs = await makeConfigs();
|
||||
export const buildPackage = async (options: BuildOptions) => {
|
||||
const configs = await makeConfigs(options);
|
||||
await Promise.all(configs.map(build));
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export enum Output {
|
||||
esm,
|
||||
cjs,
|
||||
types,
|
||||
}
|
||||
|
||||
export type BuildOptions = {
|
||||
outputs: Set<Output>;
|
||||
};
|
||||
@@ -20,7 +20,7 @@
|
||||
"main:src": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"build": "backstage-cli build --outputs types,esm",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
|
||||
@@ -143,6 +143,30 @@ export type OpenIdConnectApi = {
|
||||
logout(): Promise<void>;
|
||||
};
|
||||
|
||||
export type ProfileInfoOptions = {
|
||||
/**
|
||||
* If this is set to true, the user will not be prompted to log in,
|
||||
* and an empty profile will be returned if there is no existing session.
|
||||
*
|
||||
* This can be used to perform a check whether the user is logged in, or if you don't
|
||||
* want to force a user to be logged in, but provide functionality if they already are.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
optional?: boolean;
|
||||
};
|
||||
|
||||
export type ProfileInfoApi = {
|
||||
getProfile(options?: ProfileInfoOptions): Promise<ProfileInfo | undefined>;
|
||||
};
|
||||
|
||||
export type ProfileInfo = {
|
||||
provider: string;
|
||||
email: string;
|
||||
name?: string;
|
||||
picture?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides authentication towards Google APIs and identities.
|
||||
*
|
||||
@@ -151,7 +175,9 @@ export type OpenIdConnectApi = {
|
||||
* Note that the ID token payload is only guaranteed to contain the user's numerical Google ID,
|
||||
* email and expiration information. Do not rely on any other fields, as they might not be present.
|
||||
*/
|
||||
export const googleAuthApiRef = createApiRef<OAuthApi & OpenIdConnectApi>({
|
||||
export const googleAuthApiRef = createApiRef<
|
||||
OAuthApi & OpenIdConnectApi & ProfileInfoApi
|
||||
>({
|
||||
id: 'core.auth.google',
|
||||
description: 'Provides authentication towards Google APIs and identities',
|
||||
});
|
||||
|
||||
@@ -22,6 +22,9 @@ import {
|
||||
OpenIdConnectApi,
|
||||
IdTokenOptions,
|
||||
AccessTokenOptions,
|
||||
ProfileInfoApi,
|
||||
ProfileInfoOptions,
|
||||
ProfileInfo,
|
||||
} from '../../../definitions/auth';
|
||||
import { OAuthRequestApi, AuthProvider } from '../../../definitions';
|
||||
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
|
||||
@@ -39,6 +42,7 @@ type CreateOptions = {
|
||||
};
|
||||
|
||||
export type GoogleAuthResponse = {
|
||||
profile: ProfileInfo;
|
||||
accessToken: string;
|
||||
idToken: string;
|
||||
scope: string;
|
||||
@@ -53,7 +57,7 @@ const DEFAULT_PROVIDER = {
|
||||
|
||||
const SCOPE_PREFIX = 'https://www.googleapis.com/auth/';
|
||||
|
||||
class GoogleAuth implements OAuthApi, OpenIdConnectApi {
|
||||
class GoogleAuth implements OAuthApi, OpenIdConnectApi, ProfileInfoApi {
|
||||
static create({
|
||||
apiOrigin,
|
||||
basePath,
|
||||
@@ -69,6 +73,7 @@ class GoogleAuth implements OAuthApi, OpenIdConnectApi {
|
||||
oauthRequestApi: oauthRequestApi,
|
||||
sessionTransform(res: GoogleAuthResponse): GoogleSession {
|
||||
return {
|
||||
profile: res.profile,
|
||||
idToken: res.idToken,
|
||||
accessToken: res.accessToken,
|
||||
scopes: GoogleAuth.normalizeScopes(res.scope),
|
||||
@@ -123,6 +128,14 @@ class GoogleAuth implements OAuthApi, OpenIdConnectApi {
|
||||
await this.sessionManager.removeSession();
|
||||
}
|
||||
|
||||
async getProfile(options: ProfileInfoOptions = {}) {
|
||||
const session = await this.sessionManager.getSession(options);
|
||||
if (!session) {
|
||||
return undefined;
|
||||
}
|
||||
return session.profile;
|
||||
}
|
||||
|
||||
static normalizeScopes(scopes?: string | string[]): Set<string> {
|
||||
if (!scopes) {
|
||||
return new Set();
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ProfileInfo } from '../../../definitions';
|
||||
|
||||
export type GoogleSession = {
|
||||
profile: ProfileInfo;
|
||||
idToken: string;
|
||||
accessToken: string;
|
||||
scopes: Set<string>;
|
||||
|
||||
@@ -117,6 +117,10 @@ export class RefreshingAuthSessionManager<T> implements SessionManager<T> {
|
||||
window.location.reload(); // TODO(Rugvip): make this work without reload?
|
||||
}
|
||||
|
||||
async getCurrentSession() {
|
||||
return this.currentSession;
|
||||
}
|
||||
|
||||
private async collapsedSessionRefresh(): Promise<T> {
|
||||
if (this.refreshPromise) {
|
||||
return this.refreshPromise;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"main:src": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"build": "backstage-cli build --outputs types,esm",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
|
||||
@@ -14,48 +14,285 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import React, { FC, useState, useEffect } from 'react';
|
||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import { sidebarConfig } from './config';
|
||||
import { Avatar, Typography } from '@material-ui/core';
|
||||
import {
|
||||
Avatar,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
Popover,
|
||||
List,
|
||||
ListItemIcon,
|
||||
ListItemSecondaryAction,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { blueGrey } from '@material-ui/core/colors';
|
||||
import { useSetState } from 'react-use';
|
||||
import { Skeleton } from '@material-ui/lab';
|
||||
import { useApi, googleAuthApiRef, ProfileInfo } from '@backstage/core-api';
|
||||
import LogoutIcon from '@material-ui/icons/PowerSettingsNew';
|
||||
import ControlPointIcon from '@material-ui/icons/ControlPoint';
|
||||
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
|
||||
|
||||
const useStyles = makeStyles<Theme>(() => {
|
||||
const useStyles = makeStyles<Theme>(theme => {
|
||||
const { drawerWidthOpen, userBadgeDiameter } = sidebarConfig;
|
||||
return {
|
||||
root: {
|
||||
width: drawerWidthOpen,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: '#b5b5b5',
|
||||
paddingLeft: 18,
|
||||
paddingTop: 14,
|
||||
paddingBottom: 14,
|
||||
color: '#b5b5b5',
|
||||
},
|
||||
avatar: {
|
||||
width: userBadgeDiameter,
|
||||
height: userBadgeDiameter,
|
||||
marginRight: 8,
|
||||
},
|
||||
purple: {
|
||||
color: theme.palette.getContrastText(blueGrey[500]),
|
||||
backgroundColor: blueGrey[500],
|
||||
},
|
||||
listItemText: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const SessionListItem: FC<{
|
||||
classes: any;
|
||||
loading: boolean;
|
||||
title: string;
|
||||
icon: any;
|
||||
user: any;
|
||||
onSignIn: Function;
|
||||
onSignOut: Function;
|
||||
}> = ({
|
||||
classes,
|
||||
loading,
|
||||
title,
|
||||
icon,
|
||||
user,
|
||||
onSignIn,
|
||||
onSignOut,
|
||||
...props
|
||||
}) => {
|
||||
if (loading) {
|
||||
return (
|
||||
<ListItem {...props}>
|
||||
<ListItemIcon style={{ marginRight: 0 }}>
|
||||
<Skeleton variant="circle" width={40} height={40} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={<Skeleton component="span" width={120} />}
|
||||
secondary={<Skeleton component="span" width={60} />}
|
||||
/>
|
||||
<ListItemSecondaryAction>
|
||||
<IconButton>
|
||||
<Skeleton variant="circle" width={24} height={24} />
|
||||
</IconButton>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Not functional yet to sign in from the sidebar
|
||||
if (!user) {
|
||||
return (
|
||||
<ListItem {...props}>
|
||||
<ListItemIcon style={{ marginRight: 0 }}>{icon}</ListItemIcon>
|
||||
<ListItemText primary="Sign In" secondary={title} />
|
||||
<ListItemSecondaryAction>
|
||||
<Tooltip
|
||||
title={`Sign in with ${title}`}
|
||||
placement="bottom-end"
|
||||
PopperProps={{ style: { width: 120 } }}
|
||||
>
|
||||
<IconButton onClick={() => onSignIn()}>
|
||||
<ControlPointIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
const { id, avatarUrl, avatarAlt } = user;
|
||||
|
||||
return (
|
||||
<ListItem {...props}>
|
||||
<ListItemAvatar>
|
||||
<Avatar src={avatarUrl} alt={avatarAlt}>
|
||||
{avatarAlt && avatarAlt[0].toUpperCase()}
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
className={classes.listItemText}
|
||||
primary={
|
||||
<Typography className={classes.listItemText} variant="body2">
|
||||
{id}
|
||||
</Typography>
|
||||
}
|
||||
secondary={title}
|
||||
/>
|
||||
<ListItemSecondaryAction style={{ marginLeft: '30px' }}>
|
||||
<Tooltip
|
||||
title={`Sign out from ${title}`}
|
||||
placement="bottom-end"
|
||||
PopperProps={{ style: { width: 120 } }}
|
||||
>
|
||||
<IconButton onClick={() => onSignOut()}>
|
||||
<LogoutIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
const useGoogleLoginState = (open: boolean) => {
|
||||
const googleAuth = useApi(googleAuthApiRef);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [profile, setProfile] = useState<ProfileInfo>();
|
||||
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
|
||||
if (open) {
|
||||
googleAuth.getProfile().then(_profile => {
|
||||
if (!didCancel) {
|
||||
setProfile(_profile);
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
didCancel = true;
|
||||
};
|
||||
}, [open, googleAuth]);
|
||||
|
||||
if (loading) {
|
||||
return { loading: true };
|
||||
}
|
||||
return { loading: false, isLoggedIn: !!profile, profile };
|
||||
};
|
||||
|
||||
type Props = {
|
||||
imageUrl: string;
|
||||
name: string;
|
||||
hideName?: boolean;
|
||||
email: string;
|
||||
imageUrl?: string;
|
||||
name?: string;
|
||||
collapsedMode?: boolean;
|
||||
};
|
||||
|
||||
export const LoggedUserBadge: FC<Props> = ({
|
||||
imageUrl,
|
||||
name,
|
||||
hideName = false,
|
||||
email,
|
||||
collapsedMode = false,
|
||||
}) => {
|
||||
const [state, setState] = useSetState({
|
||||
open: false,
|
||||
anchorEl: null,
|
||||
});
|
||||
const googleAuth = useApi(googleAuthApiRef);
|
||||
const googleLogin = useGoogleLoginState(state.open);
|
||||
|
||||
const handleOpen = (event: {
|
||||
preventDefault: () => void;
|
||||
currentTarget: any;
|
||||
}) => {
|
||||
// This prevents ghost click.
|
||||
event.preventDefault();
|
||||
setState({
|
||||
open: true,
|
||||
anchorEl: event.currentTarget,
|
||||
});
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setState({
|
||||
open: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handleGoogleSignIn = () => {
|
||||
googleAuth.getIdToken();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleGoogleSignOut = () => {
|
||||
googleAuth.logout();
|
||||
};
|
||||
|
||||
const classes = useStyles();
|
||||
const avatarFallback = email.charAt(0).toUpperCase() + email.slice(1);
|
||||
const emailTrimmed = email.split('@')[0];
|
||||
const displayEmail =
|
||||
emailTrimmed.charAt(0).toUpperCase() + emailTrimmed.slice(1);
|
||||
const displayName = name ?? displayEmail;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Avatar alt={name} src={imageUrl} className={classes.avatar} />
|
||||
{!hideName && <Typography variant="subtitle2">{name}</Typography>}
|
||||
</div>
|
||||
<>
|
||||
<List dense>
|
||||
<ListItem className={classes.root} onClick={handleOpen}>
|
||||
<ListItemAvatar>
|
||||
{imageUrl ? (
|
||||
<Avatar alt={name} src={imageUrl} className={classes.avatar} />
|
||||
) : (
|
||||
<Avatar
|
||||
alt={name}
|
||||
className={`${classes.avatar} ${classes.purple}`}
|
||||
>
|
||||
{avatarFallback[0]}
|
||||
</Avatar>
|
||||
)}
|
||||
</ListItemAvatar>
|
||||
{!collapsedMode && (
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography className={classes.listItemText} variant="body2">
|
||||
{displayName}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</ListItem>
|
||||
</List>
|
||||
<Popover
|
||||
transitionDuration={0}
|
||||
open={state.open}
|
||||
anchorEl={state.anchorEl}
|
||||
anchorOrigin={{ horizontal: 'center', vertical: 'top' }}
|
||||
transformOrigin={{ horizontal: 'center', vertical: 'bottom' }}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<List dense>
|
||||
<SessionListItem
|
||||
classes={classes}
|
||||
loading={googleLogin.loading}
|
||||
title="Google"
|
||||
icon={AccountCircleIcon}
|
||||
user={
|
||||
googleLogin.isLoggedIn && {
|
||||
id: googleLogin.profile?.email,
|
||||
avatarUrl: googleLogin.profile?.picture ?? '',
|
||||
avatarAlt:
|
||||
googleLogin.profile?.picture ?? googleLogin.profile?.email,
|
||||
}
|
||||
}
|
||||
onSignIn={handleGoogleSignIn}
|
||||
onSignOut={handleGoogleSignOut}
|
||||
/>
|
||||
</List>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,15 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC, useContext } from 'react';
|
||||
import React, { FC, useContext, useEffect, useState } from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import People from '@material-ui/icons/People';
|
||||
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
|
||||
import { SidebarContext } from './config';
|
||||
import { SidebarItem } from './Items';
|
||||
import { LoggedUserBadge } from './LoggedUserBadge';
|
||||
import DoubleArrowIcon from '@material-ui/icons/DoubleArrow';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { SidebarPinStateContext } from './Page';
|
||||
import { useApi, googleAuthApiRef, ProfileInfo } from '@backstage/core-api';
|
||||
|
||||
const ARROW_BUTTON_SIZE = 20;
|
||||
const useStyles = makeStyles<BackstageTheme, { isPinned: boolean }>(theme => {
|
||||
@@ -58,18 +59,30 @@ export const SidebarUserBadge: FC<{}> = () => {
|
||||
SidebarPinStateContext,
|
||||
);
|
||||
const classes = useStyles({ isPinned });
|
||||
const googleAuth = useApi(googleAuthApiRef);
|
||||
const [profile, setProfile] = useState<ProfileInfo>();
|
||||
|
||||
useEffect(() => {
|
||||
// TODO(soapraj): How to observe if the user is logged in
|
||||
// TODO(soapraj): List all the providers supported by the app and let user log in from here
|
||||
googleAuth.getProfile({ optional: true }).then(googleProfile => {
|
||||
setProfile(googleProfile);
|
||||
});
|
||||
}, [googleAuth]);
|
||||
|
||||
const isUserLoggedIn = false;
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
{isUserLoggedIn ? (
|
||||
<LoggedUserBadge
|
||||
imageUrl="https://via.placeholder.com/200/200"
|
||||
name="Victor Viale"
|
||||
hideName={!isOpen}
|
||||
/>
|
||||
{profile ? (
|
||||
<>
|
||||
<LoggedUserBadge
|
||||
email={profile.email}
|
||||
imageUrl={profile.picture}
|
||||
name={profile.name}
|
||||
collapsedMode={!isOpen}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<SidebarItem icon={People} text="Log in" to="/login" disableSelected />
|
||||
<SidebarItem icon={AccountCircleIcon} text="" disableSelected />
|
||||
)}
|
||||
{isOpen && (
|
||||
<button
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"main:src": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"build": "backstage-cli build --outputs types,esm",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
|
||||
@@ -2,9 +2,13 @@ import {
|
||||
ApiRegistry,
|
||||
alertApiRef,
|
||||
errorApiRef,
|
||||
oauthRequestApiRef,
|
||||
OAuthRequestManager,
|
||||
googleAuthApiRef,
|
||||
AlertApiForwarder,
|
||||
ErrorApiForwarder,
|
||||
ErrorAlerter,
|
||||
GoogleAuth,
|
||||
} from '@backstage/core';
|
||||
|
||||
const builder = ApiRegistry.builder();
|
||||
@@ -13,4 +17,18 @@ const alertApi = builder.add(alertApiRef, new AlertApiForwarder());
|
||||
|
||||
builder.add(errorApiRef, new ErrorAlerter(alertApi, new ErrorApiForwarder()));
|
||||
|
||||
const oauthRequestApi = builder.add(
|
||||
oauthRequestApiRef,
|
||||
new OAuthRequestManager(),
|
||||
);
|
||||
|
||||
builder.add(
|
||||
googleAuthApiRef,
|
||||
GoogleAuth.create({
|
||||
apiOrigin: 'http://localhost:7000',
|
||||
basePath: '/auth/',
|
||||
oauthRequestApi,
|
||||
}),
|
||||
);
|
||||
|
||||
export const apis = builder.build();
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"main:src": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"build": "backstage-cli build --outputs types,esm",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"main:src": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"build": "backstage-cli build --outputs types,esm",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"main:src": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "backstage-cli plugin:build",
|
||||
"build": "backstage-cli build --outputs types,esm",
|
||||
"lint": "backstage-cli lint",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
|
||||
Reference in New Issue
Block a user