Merge branch 'master' of github.com:spotify/backstage into mob/prepare-from-catalog
* 'master' of github.com:spotify/backstage: (57 commits) fix: update readme with work-in-progress status fix: Techdocs to TechDocs fix: rename pulp-fiction to techdocs-core feat(techdocs): yarn create-plugin chore(backend-common): just tweaked the structure of useHotMemoize build(deps): bump @rollup/plugin-json from 4.0.3 to 4.1.0 (#1360) build(deps-dev): bump lerna from 3.22.0 to 3.22.1 (#1390) build(deps): bump graphql from 15.0.0 to 15.1.0 (#1391) core-api: switch IdentityApi id token access to async Adding name of signed in user to greeting message (#1387) Fix CircleCI plugin (#1384) packages/core: update to not use default exports for components auth-backend: clean up naming and types of TokenFactory time handling auth-backend: some review feedback of oidc bits auth-backend: remove logging from DatabaseKeyStore auth-backend: more docs for TokenFactory auth-backend: added tests for TokenFactory and fix keyDuration being ignored auth-backend: added tests for DatabaseKeyStore auth-backend: refactor identity to remove logic from storage layer auth-backend: document identity types ...
This commit is contained in:
@@ -14,7 +14,9 @@
|
||||
"@backstage/plugin-scaffolder": "^0.1.1-alpha.9",
|
||||
"@backstage/plugin-sentry": "^0.1.1-alpha.9",
|
||||
"@backstage/plugin-tech-radar": "^0.1.1-alpha.9",
|
||||
"@backstage/plugin-techdocs": "^0.1.1-alpha.9",
|
||||
"@backstage/plugin-welcome": "^0.1.1-alpha.9",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.9",
|
||||
"@backstage/theme": "^0.1.1-alpha.9",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
@@ -22,6 +24,7 @@
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-hot-loader": "^4.12.21",
|
||||
"react-router": "6.0.0-alpha.5",
|
||||
"react-router-dom": "6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0",
|
||||
"zen-observable": "^0.8.15"
|
||||
|
||||
@@ -15,23 +15,24 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import App from './App';
|
||||
|
||||
describe('App', () => {
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
value: jest.fn(() => {
|
||||
return {
|
||||
matches: true,
|
||||
addListener: jest.fn(),
|
||||
removeListener: jest.fn(),
|
||||
};
|
||||
}),
|
||||
it('should render', async () => {
|
||||
Object.defineProperty(process.env, 'APP_CONFIG', {
|
||||
configurable: true,
|
||||
value: [
|
||||
{
|
||||
data: {
|
||||
app: { title: 'Test' },
|
||||
},
|
||||
context: 'test',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
it('should render', () => {
|
||||
const rendered = render(<App />);
|
||||
|
||||
const rendered = await renderWithEffects(<App />);
|
||||
expect(rendered.baseElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,3 +23,4 @@ export { plugin as Circleci } from '@backstage/plugin-circleci';
|
||||
export { plugin as RegisterComponent } from '@backstage/plugin-register-component';
|
||||
export { plugin as Sentry } from '@backstage/plugin-sentry';
|
||||
export { plugin as GitopsProfiles } from '@backstage/plugin-gitops-profiles';
|
||||
export { plugin as TechDocs } from '@backstage/plugin-techdocs';
|
||||
|
||||
@@ -14,10 +14,38 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Find all active hot module APIs of all ancestors of a module, including the module itself
|
||||
function findAllAncestors(_module: NodeModule): NodeModule[] {
|
||||
const ancestors = new Array<NodeModule>();
|
||||
const parentIds = new Set<string | number>();
|
||||
|
||||
function add(id: string | number, m: NodeModule) {
|
||||
if (parentIds.has(id)) {
|
||||
return;
|
||||
}
|
||||
parentIds.add(id);
|
||||
ancestors.push(m);
|
||||
|
||||
for (const parentId of (m as any).parents) {
|
||||
const parent = require.cache[parentId];
|
||||
if (parent) {
|
||||
add(parentId, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add(_module.id, _module);
|
||||
|
||||
return ancestors;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function allows devs to cleanup
|
||||
* ongoing effects when module gets hot-reloaded
|
||||
* useHotCleanup allows cleanup of ongoing effects when a module is
|
||||
* hot-reloaded during development. The cleanup function will be called
|
||||
* whenever the module itself or any of its parent modules is hot-reloaded.
|
||||
*
|
||||
* Useful for cleaning intervals, timers, requests etc
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const intervalId = setInterval(doStuff, 1000);
|
||||
@@ -28,69 +56,69 @@
|
||||
*/
|
||||
export function useHotCleanup(_module: NodeModule, cancelEffect: () => void) {
|
||||
if (_module.hot) {
|
||||
_module.hot.addDisposeHandler(() => {
|
||||
cancelEffect();
|
||||
});
|
||||
const ancestors = findAllAncestors(_module);
|
||||
let cancelled = false;
|
||||
|
||||
const handler = () => {
|
||||
if (!cancelled) {
|
||||
cancelled = true;
|
||||
cancelEffect();
|
||||
}
|
||||
};
|
||||
|
||||
for (const m of ancestors) {
|
||||
m.hot?.addDisposeHandler(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CURRENT_HOT_MEMOIZE_INDEX_KEY = 'backstage.io/hmr-memoize-key';
|
||||
|
||||
/**
|
||||
* This function allows devs to preserve
|
||||
* some value between hot-reloads.
|
||||
* Useful for stateful parts of the backend
|
||||
* Memoizes a generated value across hot-module reloads. This is useful for
|
||||
* stateful parts of the backend, e.g. to retain a database.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const db = useHotMemoize(module, () => createDB(dbParams));
|
||||
* ```
|
||||
* @param _module Reference to the current module where you invoke the fn
|
||||
* @param valueFactory Fn that returns the value you want to memoize
|
||||
*
|
||||
* @warning Don't use inside conditionals or loops,
|
||||
* same rules as for hooks apply (https://reactjs.org/docs/hooks-rules.html)
|
||||
*
|
||||
* @param _module Reference to the current module where you invoke the fn
|
||||
* @param valueFactory Fn that returns the value you want to memoize
|
||||
*/
|
||||
export function useHotMemoize<T>(
|
||||
_module: NodeModule,
|
||||
valueFactory: () => T,
|
||||
): T {
|
||||
const CURRENT_HOT_MEMOIZE_INDEX_KEY = 'backstage.io/hmr-memoize-key';
|
||||
|
||||
if (!_module.hot) {
|
||||
// Just return value straight away
|
||||
return valueFactory();
|
||||
}
|
||||
|
||||
if (_module.hot && typeof _module.hot.data === 'undefined') {
|
||||
// First run, init the module data
|
||||
// When starting blank, reset the counter
|
||||
if (!_module.hot.data?.[CURRENT_HOT_MEMOIZE_INDEX_KEY]) {
|
||||
for (const ancestor of findAllAncestors(_module)) {
|
||||
ancestor.hot?.addDisposeHandler(data => {
|
||||
data[CURRENT_HOT_MEMOIZE_INDEX_KEY] = 1;
|
||||
});
|
||||
}
|
||||
|
||||
_module.hot.data = {
|
||||
[CURRENT_HOT_MEMOIZE_INDEX_KEY]: 0,
|
||||
..._module.hot.data,
|
||||
[CURRENT_HOT_MEMOIZE_INDEX_KEY]: 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Let's store data per module based on the order of the code invocation
|
||||
const index = _module.hot.data[CURRENT_HOT_MEMOIZE_INDEX_KEY];
|
||||
// Increasing the counter after each call
|
||||
_module.hot.data[CURRENT_HOT_MEMOIZE_INDEX_KEY] += 1;
|
||||
// Store data per module, based on the order of the code invocation
|
||||
const index = _module.hot.data[CURRENT_HOT_MEMOIZE_INDEX_KEY]++;
|
||||
const value = _module.hot.data[index] ?? valueFactory();
|
||||
|
||||
const prevValue = _module.hot.data[index];
|
||||
const createDisposeHandler = (value: any) => (data: {
|
||||
[key: number]: any;
|
||||
[indexKey: string]: number;
|
||||
}) => {
|
||||
// Preserving the value through the HMR process
|
||||
// Always add a handler that, upon a HMR event, reinstates the value.
|
||||
_module.hot.addDisposeHandler(data => {
|
||||
data[index] = value;
|
||||
// Decreasing the counter after each handler
|
||||
data[CURRENT_HOT_MEMOIZE_INDEX_KEY] =
|
||||
// First hot update is still different, need to populate the data
|
||||
typeof data[CURRENT_HOT_MEMOIZE_INDEX_KEY] === 'undefined'
|
||||
? _module.hot!.data[CURRENT_HOT_MEMOIZE_INDEX_KEY] - 1
|
||||
: data[CURRENT_HOT_MEMOIZE_INDEX_KEY] - 1;
|
||||
};
|
||||
});
|
||||
|
||||
if (prevValue) {
|
||||
_module.hot!.addDisposeHandler(createDisposeHandler(prevValue));
|
||||
return prevValue;
|
||||
}
|
||||
|
||||
const newValue = valueFactory();
|
||||
_module.hot.addDisposeHandler(createDisposeHandler(newValue));
|
||||
return newValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ import { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
database,
|
||||
config,
|
||||
}: PluginEnvironment) {
|
||||
return await createRouter({ logger, config });
|
||||
return await createRouter({ logger, config, database });
|
||||
}
|
||||
|
||||
@@ -178,8 +178,7 @@ export function createBackendConfig(
|
||||
context: paths.targetPath,
|
||||
entry: [
|
||||
'webpack/hot/poll?100',
|
||||
paths.targetEntry,
|
||||
...(paths.targetRunFile ? [paths.targetRunFile] : []),
|
||||
paths.targetRunFile ? paths.targetRunFile : paths.targetEntry,
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx'],
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
"lerna": "^3.20.2",
|
||||
"prettier": "^1.19.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"**/esbuild": "0.5.3"
|
||||
},
|
||||
"prettier": "@spotify/prettier-config",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
"@backstage/cli": "^{{version}}",
|
||||
"@backstage/core": "^{{version}}",
|
||||
"@backstage/test-utils": "^{{version}}",
|
||||
"@backstage/theme": "^{{version}}",
|
||||
"plugin-welcome": "0.0.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router": "6.0.0-alpha.5",
|
||||
"react-router-dom": "6.0.0-alpha.5",
|
||||
"react-use": "^14.2.0"
|
||||
},
|
||||
|
||||
@@ -8,47 +8,38 @@
|
||||
name="description"
|
||||
content="Backstage is an open platform for building developer portals"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="apple-touch-icon" href="<%= publicPath %>/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link
|
||||
rel="manifest"
|
||||
href="%PUBLIC_URL%/manifest.json"
|
||||
href="<%= publicPath %>/manifest.json"
|
||||
crossorigin="use-credentials"
|
||||
/>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="icon" href="<%= publicPath %>/favicon.ico" />
|
||||
<link rel="shortcut icon" href="<%= publicPath %>/favicon.ico" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="%PUBLIC_URL%/apple-touch-icon.png"
|
||||
href="<%= publicPath %>/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="%PUBLIC_URL%/favicon-32x32.png"
|
||||
href="<%= publicPath %>/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="%PUBLIC_URL%/favicon-16x16.png"
|
||||
href="<%= publicPath %>/favicon-16x16.png"
|
||||
/>
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="%PUBLIC_URL%/safari-pinned-tab.svg"
|
||||
href="<%= publicPath %>/safari-pinned-tab.svg"
|
||||
color="#5bbad5"
|
||||
/>
|
||||
<style>
|
||||
@@ -56,9 +47,9 @@
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
<title>Backstage</title>
|
||||
<title><%= app.title %></title>
|
||||
</head>
|
||||
<body style="margin: 0">
|
||||
<body style="margin: 0;">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { renderWithEffects } from '@backstage/test-utils';
|
||||
import App from './App';
|
||||
|
||||
describe('App', () => {
|
||||
it('should render', () => {
|
||||
const rendered = render(<App />);
|
||||
it('should render', async () => {
|
||||
Object.defineProperty(process.env, 'APP_CONFIG', {
|
||||
configurable: true,
|
||||
value: [
|
||||
{
|
||||
data: {
|
||||
app: { title: 'Test' },
|
||||
},
|
||||
context: 'test',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const rendered = await renderWithEffects(<App />);
|
||||
expect(rendered.baseElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,26 +1,7 @@
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { createApp } from '@backstage/core';
|
||||
import React, { FC } from 'react';
|
||||
import * as plugins from './plugins';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
'@global': {
|
||||
html: {
|
||||
height: '100%',
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
},
|
||||
body: {
|
||||
height: '100%',
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
'overscroll-behavior-y': 'none',
|
||||
},
|
||||
a: {
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const app = createApp({
|
||||
plugins: Object.values(plugins),
|
||||
});
|
||||
@@ -29,15 +10,12 @@ const AppProvider = app.getProvider();
|
||||
const AppRouter = app.getRouter();
|
||||
const AppRoutes = app.getRoutes();
|
||||
|
||||
const App: FC<{}> = () => {
|
||||
useStyles();
|
||||
return (
|
||||
<AppProvider>
|
||||
<AppRouter>
|
||||
<AppRoutes />
|
||||
</AppRouter>
|
||||
</AppProvider>
|
||||
);
|
||||
};
|
||||
const App: FC<{}> = () => (
|
||||
<AppProvider>
|
||||
<AppRouter>
|
||||
<AppRoutes />
|
||||
</AppRouter>
|
||||
</AppProvider>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -35,7 +35,7 @@ export type IdentityApi = {
|
||||
* The ID token will be undefined if the signed in user does not have a verified
|
||||
* identity, such as a demo user or mocked user for e2e tests.
|
||||
*/
|
||||
getIdToken(): string | undefined;
|
||||
getIdToken(): Promise<string | undefined>;
|
||||
|
||||
// TODO: getProfile(): Promise<Profile> - We want this to be async when added, but needs more work.
|
||||
|
||||
|
||||
@@ -157,25 +157,47 @@ export type ProfileInfoOptions = {
|
||||
optional?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* This API provides access to profile information of the user from an auth provider.
|
||||
*/
|
||||
export type ProfileInfoApi = {
|
||||
getProfile(options?: ProfileInfoOptions): Promise<ProfileInfo | undefined>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Profile information of the user from an auth provider.
|
||||
*/
|
||||
export type ProfileInfo = {
|
||||
provider: string;
|
||||
/**
|
||||
* Email ID.
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* Display name that can be presented to the user.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* URL to an avatar image of the user.
|
||||
*/
|
||||
picture?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Session state values passed to subscribers of the SessionStateApi.
|
||||
*/
|
||||
export enum SessionState {
|
||||
SignedIn = 'SignedIn',
|
||||
SignedOut = 'SignedOut',
|
||||
}
|
||||
|
||||
/**
|
||||
* This API provides access to an sessionState$ observable which provides an update when the
|
||||
* user performs a sign in or sign out from an auth provider.
|
||||
*/
|
||||
export type SessionStateApi = {
|
||||
sessionState$(): Observable<SessionState>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides authentication towards Google APIs and identities.
|
||||
*
|
||||
|
||||
@@ -293,8 +293,6 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
if (!SignInPageComponent) {
|
||||
this.identityApi.setSignInResult({
|
||||
userId: 'guest',
|
||||
idToken: undefined,
|
||||
logout: async () => {},
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -24,7 +24,7 @@ import { SignInResult } from './types';
|
||||
export class AppIdentity implements IdentityApi {
|
||||
private hasIdentity = false;
|
||||
private userId?: string;
|
||||
private idToken?: string;
|
||||
private idTokenFunc?: () => Promise<string>;
|
||||
private logoutFunc?: () => Promise<void>;
|
||||
|
||||
getUserId(): string {
|
||||
@@ -36,13 +36,13 @@ export class AppIdentity implements IdentityApi {
|
||||
return this.userId!;
|
||||
}
|
||||
|
||||
getIdToken(): string | undefined {
|
||||
async getIdToken(): Promise<string | undefined> {
|
||||
if (!this.hasIdentity) {
|
||||
throw new Error(
|
||||
'Tried to access IdentityApi idToken before app was loaded',
|
||||
);
|
||||
}
|
||||
return this.idToken;
|
||||
return this.idTokenFunc?.();
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
@@ -64,7 +64,7 @@ export class AppIdentity implements IdentityApi {
|
||||
}
|
||||
this.hasIdentity = true;
|
||||
this.userId = result.userId;
|
||||
this.idToken = result.idToken;
|
||||
this.idTokenFunc = result.getIdToken;
|
||||
this.logoutFunc = result.logout;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ export type SignInResult = {
|
||||
*/
|
||||
userId: string;
|
||||
/**
|
||||
* ID token that will be returned by the IdentityApi
|
||||
* Function used to retrieve an ID token for the signed in user.
|
||||
*/
|
||||
idToken?: string;
|
||||
getIdToken?: () => Promise<string>;
|
||||
/**
|
||||
* Logout handler that will be called if the user requests a logout.
|
||||
*/
|
||||
|
||||
@@ -25,7 +25,7 @@ import privateExports, {
|
||||
import { BrowserRouter, MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { ErrorPage } from '../layout/ErrorPage';
|
||||
import Progress from '../components/Progress';
|
||||
import { Progress } from '../components/Progress';
|
||||
import { lightTheme, darkTheme } from '@backstage/theme';
|
||||
import { AppConfig, JsonObject } from '@backstage/config';
|
||||
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './AlertDisplay';
|
||||
export { AlertDisplay } from './AlertDisplay';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import CodeSnippet from './CodeSnippet';
|
||||
import { CodeSnippet } from './CodeSnippet';
|
||||
import { InfoCard } from '../../layout/InfoCard';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -18,7 +18,7 @@ import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import CodeSnippet from './CodeSnippet';
|
||||
import { CodeSnippet } from './CodeSnippet';
|
||||
|
||||
const JAVASCRIPT = `const greeting = "Hello";
|
||||
const world = "World";
|
||||
|
||||
@@ -31,7 +31,7 @@ const defaultProps = {
|
||||
showLineNumbers: false,
|
||||
};
|
||||
|
||||
const CodeSnippet: FC<Props> = props => {
|
||||
export const CodeSnippet: FC<Props> = props => {
|
||||
const { text, language, showLineNumbers } = {
|
||||
...defaultProps,
|
||||
...props,
|
||||
@@ -57,5 +57,3 @@ CodeSnippet.propTypes = {
|
||||
language: PropTypes.string.isRequired,
|
||||
showLineNumbers: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default CodeSnippet;
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default } from './CodeSnippet';
|
||||
export { CodeSnippet } from './CodeSnippet';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import CopyTextButton from '.';
|
||||
import { CopyTextButton } from '.';
|
||||
|
||||
export default {
|
||||
title: 'CopyTextButton',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import CopyTextButton from './CopyTextButton';
|
||||
import { CopyTextButton } from './CopyTextButton';
|
||||
import {
|
||||
ApiRegistry,
|
||||
errorApiRef,
|
||||
|
||||
@@ -56,7 +56,7 @@ const defaultProps = {
|
||||
tooltipText: 'Text copied to clipboard',
|
||||
};
|
||||
|
||||
const CopyTextButton: FC<Props> = props => {
|
||||
export const CopyTextButton: FC<Props> = props => {
|
||||
const { text, tooltipDelay, tooltipText } = {
|
||||
...defaultProps,
|
||||
...props,
|
||||
@@ -110,5 +110,3 @@ CopyTextButton.propTypes = {
|
||||
tooltipDelay: PropTypes.number,
|
||||
tooltipText: PropTypes.string,
|
||||
};
|
||||
|
||||
export default CopyTextButton;
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default } from './CopyTextButton';
|
||||
export { CopyTextButton } from './CopyTextButton';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import DismissableBanner from './DismissableBanner';
|
||||
import { DismissableBanner } from './DismissableBanner';
|
||||
import { Link, Typography } from '@material-ui/core';
|
||||
import {
|
||||
ApiProvider,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import DismissableBanner from './DismissableBanner';
|
||||
import { DismissableBanner } from './DismissableBanner';
|
||||
import {
|
||||
ApiRegistry,
|
||||
ApiProvider,
|
||||
|
||||
@@ -59,7 +59,7 @@ type Props = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const DismissableBanner: FC<Props> = ({ variant, message, id }) => {
|
||||
export const DismissableBanner: FC<Props> = ({ variant, message, id }) => {
|
||||
const classes = useStyles();
|
||||
const storageApi = useApi(storageApiRef);
|
||||
const notificationsStore = storageApi.forBucket('notifications');
|
||||
@@ -111,5 +111,3 @@ const DismissableBanner: FC<Props> = ({ variant, message, id }) => {
|
||||
</Snackbar>
|
||||
);
|
||||
};
|
||||
|
||||
export default DismissableBanner;
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default } from './DismissableBanner';
|
||||
export { DismissableBanner } from './DismissableBanner';
|
||||
|
||||
@@ -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 { FeatureCalloutCircular } from './FeatureCalloutCircular';
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import HorizontalScrollGrid from './HorizontalScrollGrid';
|
||||
import { HorizontalScrollGrid } from './HorizontalScrollGrid';
|
||||
|
||||
const cardContentStyle = { height: 0, padding: 150, margin: 20 };
|
||||
const containerStyle = { width: 800, height: 400, margin: 20 };
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import HorizontalScrollGrid from './HorizontalScrollGrid';
|
||||
import { HorizontalScrollGrid } from './HorizontalScrollGrid';
|
||||
import { Grid } from '@material-ui/core';
|
||||
|
||||
describe('<HorizontalScrollGrid />', () => {
|
||||
|
||||
@@ -181,7 +181,7 @@ function useSmoothScroll(
|
||||
return setScrollTarget;
|
||||
}
|
||||
|
||||
const HorizontalScrollGrid: FC<Props> = props => {
|
||||
export const HorizontalScrollGrid: FC<Props> = props => {
|
||||
const {
|
||||
scrollStep = 100,
|
||||
scrollSpeed = 50,
|
||||
@@ -245,5 +245,3 @@ const HorizontalScrollGrid: FC<Props> = props => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalScrollGrid;
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default } from './HorizontalScrollGrid';
|
||||
export { HorizontalScrollGrid } from './HorizontalScrollGrid';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Progress from '.';
|
||||
import { Progress } from '.';
|
||||
|
||||
export default {
|
||||
title: 'Progress',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React, { FC, useState, useEffect } from 'react';
|
||||
import { LinearProgress, LinearProgressProps } from '@material-ui/core';
|
||||
|
||||
const Progress: FC<LinearProgressProps> = props => {
|
||||
export const Progress: FC<LinearProgressProps> = props => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -31,5 +31,3 @@ const Progress: FC<LinearProgressProps> = props => {
|
||||
<div style={{ display: 'none' }} data-testid="progress" />
|
||||
);
|
||||
};
|
||||
|
||||
export default Progress;
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default } from './Progress';
|
||||
export { Progress } from './Progress';
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import CircleProgress, { getProgressColor } from './CircleProgress';
|
||||
import { CircleProgress, getProgressColor } from './CircleProgress';
|
||||
|
||||
describe('<CircleProgress />', () => {
|
||||
it('renders without exploding', () => {
|
||||
|
||||
@@ -77,7 +77,7 @@ export function getProgressColor(
|
||||
return palette.status.ok;
|
||||
}
|
||||
|
||||
const CircleProgress: FC<Props> = props => {
|
||||
export const CircleProgress: FC<Props> = props => {
|
||||
const classes = useStyles(props);
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
const { value, fractional, inverse, unit, max } = {
|
||||
@@ -104,5 +104,3 @@ const CircleProgress: FC<Props> = props => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CircleProgress;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import HorizontalProgress from './HorizontalProgress';
|
||||
import { HorizontalProgress } from './HorizontalProgress';
|
||||
|
||||
const containerStyle = { width: 300 };
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ type Props = {
|
||||
value: number;
|
||||
};
|
||||
|
||||
const HorizontalProgress: FC<Props> = ({ value }) => {
|
||||
export const HorizontalProgress: FC<Props> = ({ value }) => {
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
if (isNaN(value)) {
|
||||
return null;
|
||||
@@ -49,5 +49,3 @@ const HorizontalProgress: FC<Props> = ({ value }) => {
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalProgress;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ProgressCard from './ProgressCard';
|
||||
import { ProgressCard } from './ProgressCard';
|
||||
import { Grid } from '@material-ui/core';
|
||||
|
||||
const linkInfo = { title: 'Go to XYZ Location', link: '#' };
|
||||
|
||||
@@ -18,7 +18,7 @@ import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import ProgressCard from './ProgressCard';
|
||||
import { ProgressCard } from './ProgressCard';
|
||||
|
||||
const minProps = { title: 'Tingle upgrade', progress: 0.12 };
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import React, { FC } from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { InfoCard } from '../../layout/InfoCard';
|
||||
import { BottomLinkProps } from '../../layout/BottomLink';
|
||||
import CircleProgress from './CircleProgress';
|
||||
import { CircleProgress } from './CircleProgress';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
@@ -36,7 +36,7 @@ const useStyles = makeStyles({
|
||||
},
|
||||
});
|
||||
|
||||
const ProgressCard: FC<Props> = props => {
|
||||
export const ProgressCard: FC<Props> = props => {
|
||||
const classes = useStyles(props);
|
||||
const { title, subheader, progress, deepLink, variant } = props;
|
||||
|
||||
@@ -53,5 +53,3 @@ const ProgressCard: FC<Props> = props => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressCard;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
export { ProgressCard } from './ProgressCard';
|
||||
export { CircleProgress } from './CircleProgress';
|
||||
export { HorizontalProgress } from './HorizontalProgress';
|
||||
@@ -16,8 +16,8 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent, within } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import Stepper from './SimpleStepper';
|
||||
import Step from './SimpleStepperStep';
|
||||
import { SimpleStepper as Stepper } from './SimpleStepper';
|
||||
import { SimpleStepperStep as Step } from './SimpleStepperStep';
|
||||
|
||||
const getTextInSlide = (rendered: any, index: number) =>
|
||||
within(rendered.getByTestId(`step${index}`)).getByText;
|
||||
|
||||
@@ -40,13 +40,17 @@ export interface StepperProps {
|
||||
onStepChange?: (prevIndex: number, nextIndex: number) => void;
|
||||
}
|
||||
|
||||
const Stepper: FC<StepperProps> = ({ children, elevated, onStepChange }) => {
|
||||
export const SimpleStepper: FC<StepperProps> = ({
|
||||
children,
|
||||
elevated,
|
||||
onStepChange,
|
||||
}) => {
|
||||
const [stepIndex, setStepIndex] = useState<number>(0);
|
||||
const [stepHistory, setStepHistory] = useState<number[]>([0]);
|
||||
|
||||
const steps: React.ReactNode[] = [];
|
||||
let endStep;
|
||||
Children.forEach(children, (child) => {
|
||||
Children.forEach(children, child => {
|
||||
if (isValidElement(child)) {
|
||||
if (child.props.end) {
|
||||
endStep = child;
|
||||
@@ -80,5 +84,3 @@ const Stepper: FC<StepperProps> = ({ children, elevated, onStepChange }) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Stepper;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Button, makeStyles } from '@material-ui/core';
|
||||
import { StepActions } from './SimpleStepperStep';
|
||||
import { VerticalStepperContext } from './SimpleStepper';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
marginTop: theme.spacing(3),
|
||||
'& button': {
|
||||
@@ -71,7 +71,7 @@ export type SimpleStepperFooterProps = {
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
const SimpleStepperFooter: FC<SimpleStepperFooterProps> = ({
|
||||
export const SimpleStepperFooter: FC<SimpleStepperFooterProps> = ({
|
||||
actions = {},
|
||||
children,
|
||||
}) => {
|
||||
@@ -145,5 +145,3 @@ const SimpleStepperFooter: FC<SimpleStepperFooterProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SimpleStepperFooter;
|
||||
|
||||
@@ -21,9 +21,9 @@ import {
|
||||
Typography,
|
||||
makeStyles,
|
||||
} from '@material-ui/core';
|
||||
import SimpleStepperFooter from './SimpleStepperFooter';
|
||||
import { SimpleStepperFooter } from './SimpleStepperFooter';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
const useStyles = makeStyles(theme => ({
|
||||
end: {
|
||||
padding: theme.spacing(3),
|
||||
},
|
||||
@@ -53,7 +53,7 @@ export type StepProps = {
|
||||
actions?: StepActions;
|
||||
};
|
||||
|
||||
const Step: FC<StepProps> = ({
|
||||
export const SimpleStepperStep: FC<StepProps> = ({
|
||||
title,
|
||||
children,
|
||||
end,
|
||||
@@ -82,5 +82,3 @@ const Step: FC<StepProps> = ({
|
||||
</MuiStep>
|
||||
);
|
||||
};
|
||||
|
||||
export default Step;
|
||||
|
||||
@@ -14,7 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import SimpleStepper from './SimpleStepper';
|
||||
import SimpleStepperStep from './SimpleStepperStep';
|
||||
|
||||
export { SimpleStepper, SimpleStepperStep };
|
||||
export { SimpleStepper } from './SimpleStepper';
|
||||
export { SimpleStepperStep } from './SimpleStepperStep';
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
StatusRunning,
|
||||
StatusWarning,
|
||||
} from './Status';
|
||||
import Table from '../Table';
|
||||
import { Table } from '../Table';
|
||||
import { InfoCard } from '../../layout/InfoCard';
|
||||
|
||||
export default {
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
import React, { FC } from 'react';
|
||||
import { InfoCard } from '../../layout/InfoCard';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import StructuredMetadataTable from '.';
|
||||
import { StructuredMetadataTable } from './StructuredMetadataTable';
|
||||
|
||||
const cardContentStyle = { heightX: 200, width: 500 };
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
import StructuredMetadataTable from './StructuredMetadataTable';
|
||||
import { StructuredMetadataTable } from './StructuredMetadataTable';
|
||||
import { startCase } from 'lodash';
|
||||
|
||||
describe('<StructuredMetadataTable />', () => {
|
||||
|
||||
@@ -56,7 +56,7 @@ function renderMap(
|
||||
nested?: boolean,
|
||||
options?: any,
|
||||
) {
|
||||
const values = Object.keys(map).map((key) => {
|
||||
const values = Object.keys(map).map(key => {
|
||||
const value = toValue(map[key], true);
|
||||
const fmtKey =
|
||||
options && options.titleFormat
|
||||
@@ -98,7 +98,7 @@ function toValue(
|
||||
}
|
||||
|
||||
function mapToItems(info: { [key: string]: string }, options: any) {
|
||||
return Object.keys(info).map((key) => (
|
||||
return Object.keys(info).map(key => (
|
||||
<TableItem key={key} title={key} value={info[key]} options={options} />
|
||||
));
|
||||
}
|
||||
@@ -147,7 +147,8 @@ interface ComponentProps {
|
||||
dense?: boolean;
|
||||
options?: any;
|
||||
}
|
||||
export default class StructuredMetadataTable extends Component<ComponentProps> {
|
||||
|
||||
export class StructuredMetadataTable extends Component<ComponentProps> {
|
||||
render() {
|
||||
const { metadata, dense, options } = this.props;
|
||||
const metadataItems = mapToItems(metadata, options || {});
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default } from './StructuredMetadataTable';
|
||||
export { StructuredMetadataTable } from './StructuredMetadataTable';
|
||||
|
||||
@@ -49,7 +49,7 @@ const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const SupportButton: FC<Props> = ({
|
||||
export const SupportButton: FC<Props> = ({
|
||||
slackChannel = '#backstage',
|
||||
email = [],
|
||||
children,
|
||||
@@ -155,5 +155,3 @@ const SupportButton: FC<Props> = ({
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default SupportButton;
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default } from './SupportButton';
|
||||
export { SupportButton } from './SupportButton';
|
||||
|
||||
@@ -33,7 +33,7 @@ type SubvalueCellProps = {
|
||||
subvalue: React.ReactNode;
|
||||
};
|
||||
|
||||
const SubvalueCell: FC<SubvalueCellProps> = ({ value, subvalue }) => {
|
||||
export const SubvalueCell: FC<SubvalueCellProps> = ({ value, subvalue }) => {
|
||||
const classes = useSubvalueCellStyles();
|
||||
|
||||
return (
|
||||
@@ -43,5 +43,3 @@ const SubvalueCell: FC<SubvalueCellProps> = ({ value, subvalue }) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubvalueCell;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import Table, { SubvalueCell, TableColumn } from './';
|
||||
import { Table, SubvalueCell, TableColumn } from './';
|
||||
|
||||
export default {
|
||||
title: 'Table',
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import Table from './';
|
||||
import { Table } from './Table';
|
||||
|
||||
const minProps = {
|
||||
columns: [
|
||||
|
||||
@@ -162,7 +162,7 @@ export interface TableProps extends MaterialTableProps<{}> {
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
const Table: FC<TableProps> = ({
|
||||
export const Table: FC<TableProps> = ({
|
||||
columns,
|
||||
options,
|
||||
title,
|
||||
@@ -212,5 +212,3 @@ const Table: FC<TableProps> = ({
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Table;
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default } from './Table';
|
||||
export { Table } from './Table';
|
||||
export type { TableColumn } from './Table';
|
||||
export { default as SubvalueCell } from './SubvalueCell';
|
||||
export { SubvalueCell } from './SubvalueCell';
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { Tabs as default } from './Tabs';
|
||||
export { Tabs } from './Tabs';
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import TrendLine from '.';
|
||||
import Table from '../Table';
|
||||
import { Table } from '../Table';
|
||||
import { TrendLine } from './TrendLine';
|
||||
import { InfoCard } from '../../layout/InfoCard';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -19,7 +19,7 @@ import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import TrendLine from '.';
|
||||
import { TrendLine } from './TrendLine';
|
||||
|
||||
describe('TrendLine', () => {
|
||||
describe('when no data is present', () => {
|
||||
|
||||
@@ -27,7 +27,7 @@ function color(data: number[], theme: BackstageTheme): string | undefined {
|
||||
return theme.palette.status.error;
|
||||
}
|
||||
|
||||
const Trendline: FC<SparklinesProps & { title?: string }> = props => {
|
||||
export const TrendLine: FC<SparklinesProps & { title?: string }> = props => {
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
|
||||
if (!props.data) return null;
|
||||
@@ -38,5 +38,3 @@ const Trendline: FC<SparklinesProps & { title?: string }> = props => {
|
||||
</Sparklines>
|
||||
);
|
||||
};
|
||||
|
||||
export default Trendline;
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default } from './TrendLine';
|
||||
export { TrendLine } from './TrendLine';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import WarningPanel from '.';
|
||||
import { WarningPanel } from './WarningPanel';
|
||||
import { Link, Button } from '@material-ui/core';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -18,7 +18,7 @@ import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import WarningPanel from './WarningPanel';
|
||||
import { WarningPanel } from './WarningPanel';
|
||||
|
||||
const minProps = { title: 'Mock title', message: 'Some more info' };
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { Typography, makeStyles } from '@material-ui/core';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import ErrorOutline from '@material-ui/icons/ErrorOutline';
|
||||
|
||||
const useErrorOutlineStyles = makeStyles<BackstageTheme>((theme) => ({
|
||||
const useErrorOutlineStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
root: {
|
||||
marginRight: theme.spacing(1),
|
||||
fill: theme.palette.warningText,
|
||||
@@ -30,7 +30,7 @@ const ErrorOutlineStyled = () => {
|
||||
return <ErrorOutline classes={classes} />;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>((theme) => ({
|
||||
const useStyles = makeStyles<BackstageTheme>(theme => ({
|
||||
message: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -62,7 +62,7 @@ type Props = {
|
||||
title?: string;
|
||||
};
|
||||
|
||||
const WarningPanel: FC<Props> = (props) => {
|
||||
export const WarningPanel: FC<Props> = props => {
|
||||
const classes = useStyles(props);
|
||||
const { title, message, children } = props;
|
||||
return (
|
||||
@@ -82,5 +82,3 @@ const WarningPanel: FC<Props> = (props) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WarningPanel;
|
||||
|
||||
@@ -13,4 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { default } from './WarningPanel';
|
||||
export { WarningPanel } from './WarningPanel';
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 * from './AlertDisplay';
|
||||
export * from './Button';
|
||||
export * from './CodeSnippet';
|
||||
export * from './CopyTextButton';
|
||||
export * from './DismissableBanner';
|
||||
export * from './FeatureDiscovery';
|
||||
export * from './HorizontalScrollGrid';
|
||||
export * from './Lifecycle';
|
||||
export * from './Link';
|
||||
export * from './OAuthRequestDialog';
|
||||
export * from './Progress';
|
||||
export * from './ProgressBars';
|
||||
export * from './SimpleStepper';
|
||||
export * from './Status';
|
||||
export * from './StructuredMetadataTable';
|
||||
export * from './SupportButton';
|
||||
export * from './Table';
|
||||
export * from './Tabs';
|
||||
export * from './TrendLine';
|
||||
export * from './WarningPanel';
|
||||
@@ -17,28 +17,5 @@
|
||||
export * from '@backstage/core-api';
|
||||
|
||||
export * from './api-wrappers';
|
||||
export * from './components';
|
||||
export * from './layout';
|
||||
|
||||
export { default as CodeSnippet } from './components/CodeSnippet';
|
||||
export { default as DismissableBanner } from './components/DismissableBanner';
|
||||
export { AlertDisplay } from './components/AlertDisplay';
|
||||
export { default as HorizontalScrollGrid } from './components/HorizontalScrollGrid';
|
||||
export { default as ProgressCard } from './components/ProgressBars/ProgressCard';
|
||||
export { default as CircleProgress } from './components/ProgressBars/CircleProgress';
|
||||
export { default as HorizontalProgress } from './components/ProgressBars/HorizontalProgress';
|
||||
export { default as CopyTextButton } from './components/CopyTextButton';
|
||||
export { default as Progress } from './components/Progress';
|
||||
export * from './components/SimpleStepper';
|
||||
export { OAuthRequestDialog } from './components/OAuthRequestDialog';
|
||||
export { Lifecycle } from './components/Lifecycle';
|
||||
export { default as SupportButton } from './components/SupportButton';
|
||||
export { default as Table, SubvalueCell } from './components/Table';
|
||||
export type { TableColumn } from './components/Table/Table';
|
||||
export { default as StructuredMetadataTable } from './components/StructuredMetadataTable';
|
||||
export { default as TrendLine } from './components/TrendLine';
|
||||
export { FeatureCalloutCircular } from './components/FeatureDiscovery/FeatureCalloutCircular';
|
||||
export * from './components/Status';
|
||||
export * from './components/Button';
|
||||
export * from './components/Link';
|
||||
export { default as WarningPanel } from './components/WarningPanel';
|
||||
export { default as Tabs } from './components/Tabs';
|
||||
|
||||
@@ -22,7 +22,7 @@ import { ContentHeader } from '../ContentHeader/ContentHeader';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import { SignInPageProps, useApi, configApiRef } from '@backstage/core-api';
|
||||
import { useSignInProviders, SignInProviderId } from './providers';
|
||||
import Progress from '../../components/Progress';
|
||||
import { Progress } from '../../components/Progress';
|
||||
|
||||
export type Props = SignInPageProps & {
|
||||
providers: SignInProviderId[];
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { InfoCard } from '../InfoCard/InfoCard';
|
||||
import { ProviderComponent, ProviderLoader, SignInProvider } from './types';
|
||||
import { SignInResult } from '@backstage/core-api';
|
||||
|
||||
const ID_TOKEN_REGEX = /^[a-z0-9+/]+\.[a-z0-9+/]+\.[a-z0-9+/]+$/i;
|
||||
|
||||
@@ -43,12 +42,24 @@ const useFormStyles = makeStyles(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
type Data = {
|
||||
userId: string;
|
||||
idToken?: string;
|
||||
};
|
||||
|
||||
const Component: ProviderComponent = ({ onResult }) => {
|
||||
const classes = useFormStyles();
|
||||
const { register, handleSubmit, errors, formState } = useForm<SignInResult>({
|
||||
const { register, handleSubmit, errors, formState } = useForm<Data>({
|
||||
mode: 'onChange',
|
||||
});
|
||||
|
||||
const handleResult = ({ userId, idToken }: Data) => {
|
||||
onResult({
|
||||
userId,
|
||||
getIdToken: idToken ? async () => idToken : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid item>
|
||||
<InfoCard title="Custom User">
|
||||
@@ -58,7 +69,7 @@ const Component: ProviderComponent = ({ onResult }) => {
|
||||
This selection will not be stored.
|
||||
</Typography>
|
||||
|
||||
<form className={classes.form} onSubmit={handleSubmit(onResult)}>
|
||||
<form className={classes.form} onSubmit={handleSubmit(handleResult)}>
|
||||
<FormControl>
|
||||
<TextField
|
||||
name="userId"
|
||||
|
||||
@@ -35,12 +35,12 @@ const Component: ProviderComponent = ({ onResult }) => {
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
const idToken = await googleAuthApi.getIdToken({ instantPopup: true });
|
||||
await googleAuthApi.getIdToken({ instantPopup: true });
|
||||
const profile = await googleAuthApi.getProfile();
|
||||
|
||||
onResult({
|
||||
userId: parseUserId(profile!),
|
||||
idToken,
|
||||
getIdToken: () => googleAuthApi.getIdToken(),
|
||||
logout: async () => {
|
||||
await googleAuthApi.logout();
|
||||
},
|
||||
@@ -69,14 +69,11 @@ const Component: ProviderComponent = ({ onResult }) => {
|
||||
const loader: ProviderLoader = async apis => {
|
||||
const googleAuthApi = apis.get(googleAuthApiRef)!;
|
||||
|
||||
const [idToken, profile] = await Promise.all([
|
||||
googleAuthApi.getIdToken({ optional: true }),
|
||||
googleAuthApi.getProfile({ optional: true }),
|
||||
]);
|
||||
const profile = await googleAuthApi.getProfile({ optional: true });
|
||||
|
||||
return {
|
||||
userId: parseUserId(profile!),
|
||||
idToken,
|
||||
getIdToken: () => googleAuthApi.getIdToken(),
|
||||
logout: async () => {
|
||||
await googleAuthApi.logout();
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@ import {
|
||||
ApiRegistry,
|
||||
alertApiRef,
|
||||
errorApiRef,
|
||||
identityApiRef,
|
||||
oauthRequestApiRef,
|
||||
OAuthRequestManager,
|
||||
googleAuthApiRef,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
ErrorAlerter,
|
||||
GoogleAuth,
|
||||
GithubAuth,
|
||||
identityApiRef,
|
||||
} from '@backstage/core';
|
||||
|
||||
const builder = ApiRegistry.builder();
|
||||
|
||||
Reference in New Issue
Block a user