From 77d1e7382d660d902b49139bb52f4e9315f2d608 Mon Sep 17 00:00:00 2001 From: Raghunandan Date: Tue, 26 May 2020 14:04:46 +0200 Subject: [PATCH 01/23] add check for x-requested-with header --- .../src/providers/google/provider.test.ts | 32 +++++++++++++++++-- .../src/providers/google/provider.ts | 12 +++++-- plugins/auth-backend/src/providers/utils.ts | 9 ++++++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/plugins/auth-backend/src/providers/google/provider.test.ts b/plugins/auth-backend/src/providers/google/provider.test.ts index b692a35216..874b89d92c 100644 --- a/plugins/auth-backend/src/providers/google/provider.test.ts +++ b/plugins/auth-backend/src/providers/google/provider.test.ts @@ -52,11 +52,15 @@ describe('GoogleAuthProvider', () => { }); describe('start authentication handler', () => { - const mockResponse = ({} as unknown) as express.Response; + const mockResponse = ({ + send: jest.fn().mockReturnThis(), + status: jest.fn().mockReturnThis(), + } as unknown) as express.Response; const mockNext: express.NextFunction = jest.fn(); it('should initiate authenticate request with provided scopes', () => { const mockRequest = ({ + header: () => 'XMLHttpRequest', query: { scope: 'a,b', }, @@ -80,6 +84,7 @@ describe('GoogleAuthProvider', () => { it('should throw error if no scopes provided', () => { const mockRequest = ({ + header: () => 'XMLHttpRequest', query: {}, } as unknown) as express.Request; @@ -93,7 +98,9 @@ describe('GoogleAuthProvider', () => { }); describe('logout handler', () => { - const mockRequest = ({} as unknown) as express.Request; + const mockRequest = ({ + header: () => 'XMLHttpRequest', + } as unknown) as express.Request; it('should perform logout and respond with 200', () => { const mockResponse: any = ({ @@ -122,7 +129,9 @@ describe('GoogleAuthProvider', () => { }); describe('redirect frame handler', () => { - const mockRequest = ({} as unknown) as express.Request; + const mockRequest = ({ + header: () => 'XMLHttpRequest', + } as unknown) as express.Request; const mockResponse: any = ({ status: jest.fn().mockReturnThis(), send: jest.fn().mockReturnThis(), @@ -245,6 +254,7 @@ describe('GoogleAuthProvider', () => { it('should respond with a 401', () => { const mockRequest = ({ cookies: jest.fn(), + header: () => 'XMLHttpRequest', } as unknown) as express.Request; const googleAuthProvider = new GoogleAuthProvider( @@ -262,6 +272,7 @@ describe('GoogleAuthProvider', () => { describe('refresh token cookie, no scope', () => { const mockRequest = ({ + header: () => 'XMLHttpRequest', cookies: { 'google-refresh-token': 'REFRESH_TOKEN' }, query: {}, } as unknown) as express.Request; @@ -349,6 +360,7 @@ describe('GoogleAuthProvider', () => { describe('refresh token cookie and scope', () => { const mockRequest = ({ + header: () => 'XMLHttpRequest', cookies: { 'google-refresh-token': 'REFRESH_TOKEN' }, query: { scope: 'a,b', @@ -387,6 +399,20 @@ describe('GoogleAuthProvider', () => { scope: 'a,b', }); }); + + it('ensures x-requested-with header', () => { + const mockHeaderRequest = ({ + header: () => 'TEST', + } as unknown) as express.Request; + + googleAuthProvider.refresh(mockHeaderRequest, mockResponse); + expect(mockResponse.send).toBeCalledTimes(1); + expect(mockResponse.send).toBeCalledWith( + 'Invalid X-Requested-With header', + ); + expect(mockResponse.status).toBeCalledTimes(1); + expect(mockResponse.status).toBeCalledWith(401); + }); }); }); }); diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 096260e044..35c7b3f6e2 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -23,7 +23,7 @@ import { AuthProviderRouteHandlers, AuthProviderConfig, } from './../types'; -import { postMessageResponse } from './../utils'; +import { postMessageResponse, ensuresXRequestedWith } from './../utils'; import { InputError } from '@backstage/backend-common'; export const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000; @@ -95,7 +95,11 @@ export class GoogleAuthProvider })(req, res, next); } - async logout(_req: express.Request, res: express.Response) { + async logout(req: express.Request, res: express.Response) { + if (!ensuresXRequestedWith(req)) { + return res.status(401).send('Invalid X-Requested-With header'); + } + const options: CookieOptions = { maxAge: 0, secure: false, @@ -110,6 +114,10 @@ export class GoogleAuthProvider } async refresh(req: express.Request, res: express.Response) { + if (!ensuresXRequestedWith(req)) { + return res.status(401).send('Invalid X-Requested-With header'); + } + const refreshToken = req.cookies[`${this.providerConfig.provider}-refresh-token`]; diff --git a/plugins/auth-backend/src/providers/utils.ts b/plugins/auth-backend/src/providers/utils.ts index 7fb906ad7c..83229e55d4 100644 --- a/plugins/auth-backend/src/providers/utils.ts +++ b/plugins/auth-backend/src/providers/utils.ts @@ -39,3 +39,12 @@ export const postMessageResponse = ( `); }; + +export const ensuresXRequestedWith = (req: express.Request) => { + const requiredHeader = req.header('X-Requested-With'); + + if (!requiredHeader || requiredHeader !== 'XMLHttpRequest') { + return false; + } + return true; +}; From fccdb7e3ef4da63e158841cc735c07ad9c54b621 Mon Sep 17 00:00:00 2001 From: Raghunandan Date: Tue, 26 May 2020 14:55:24 +0200 Subject: [PATCH 02/23] add nonce check for the google auth dance in popup --- .../src/providers/google/provider.ts | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 35c7b3f6e2..6850c527f1 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -16,6 +16,7 @@ import passport from 'passport'; import express, { CookieOptions } from 'express'; +import crypto from 'crypto'; import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; import refresh from 'passport-oauth2-refresh'; import { @@ -27,6 +28,7 @@ import { postMessageResponse, ensuresXRequestedWith } from './../utils'; import { InputError } from '@backstage/backend-common'; export const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000; +const TEN_MINUTES_MS = 600 * 1000; export class GoogleAuthProvider implements AuthProvider, AuthProviderRouteHandlers { private readonly providerConfig: AuthProviderConfig; @@ -39,6 +41,19 @@ export class GoogleAuthProvider res: express.Response, next: express.NextFunction, ) { + const nonce = crypto.randomBytes(16).toString('base64'); + + const options: CookieOptions = { + maxAge: TEN_MINUTES_MS, + secure: false, + sameSite: 'none', + domain: 'localhost', + path: `/auth/google/handler`, + httpOnly: true, + }; + + res.cookie(`google-nonce`, nonce, options); + const scope = req.query.scope?.toString() ?? ''; if (!scope) { throw new InputError('missing scope parameter'); @@ -47,6 +62,7 @@ export class GoogleAuthProvider scope, accessType: 'offline', prompt: 'consent', + state: nonce, })(req, res, next); } @@ -55,6 +71,17 @@ export class GoogleAuthProvider res: express.Response, next: express.NextFunction, ) { + const cookieNonce = req.cookies[`google-nonce`]; + const stateNonce = req.query.state; + + if (!cookieNonce || !stateNonce) { + return res.status(401).send('Missing nonce'); + } + + if (cookieNonce !== stateNonce) { + return res.status(401).send('Invalid nonce'); + } + return passport.authenticate('google', (err, user) => { if (err) { return postMessageResponse(res, { @@ -79,15 +106,11 @@ export class GoogleAuthProvider secure: false, sameSite: 'none', domain: 'localhost', - path: `/auth/${this.providerConfig.provider}`, + path: `/auth/google`, httpOnly: true, }; - res.cookie( - `${this.providerConfig.provider}-refresh-token`, - refreshToken, - options, - ); + res.cookie(`google-refresh-token`, refreshToken, options); return postMessageResponse(res, { type: 'auth-result', payload: user, @@ -105,11 +128,11 @@ export class GoogleAuthProvider secure: false, sameSite: 'none', domain: 'localhost', - path: `/auth/${this.providerConfig.provider}`, + path: `/auth/google`, httpOnly: true, }; - res.cookie(`${this.providerConfig.provider}-refresh-token`, '', options); + res.cookie(`google-refresh-token`, '', options); return res.send('logout!'); } @@ -118,8 +141,7 @@ export class GoogleAuthProvider return res.status(401).send('Invalid X-Requested-With header'); } - const refreshToken = - req.cookies[`${this.providerConfig.provider}-refresh-token`]; + const refreshToken = req.cookies[`google-refresh-token`]; if (!refreshToken) { return res.status(401).send('Missing session cookie'); From 12d41d0f0a51b1ffa97ee95a70aff818c09ef7b7 Mon Sep 17 00:00:00 2001 From: Raghunandan Date: Tue, 26 May 2020 16:43:08 +0200 Subject: [PATCH 03/23] use providerFactory.provider instead of hardcoding google --- .../src/providers/google/provider.ts | 21 ++++++++++++------- plugins/auth-backend/src/providers/index.ts | 8 +++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 6850c527f1..a631f86fa6 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -48,11 +48,11 @@ export class GoogleAuthProvider secure: false, sameSite: 'none', domain: 'localhost', - path: `/auth/google/handler`, + path: `/auth/${this.providerConfig.provider}/handler`, httpOnly: true, }; - res.cookie(`google-nonce`, nonce, options); + res.cookie(`${this.providerConfig.provider}-nonce`, nonce, options); const scope = req.query.scope?.toString() ?? ''; if (!scope) { @@ -71,7 +71,7 @@ export class GoogleAuthProvider res: express.Response, next: express.NextFunction, ) { - const cookieNonce = req.cookies[`google-nonce`]; + const cookieNonce = req.cookies[`${this.providerConfig.provider}-nonce`]; const stateNonce = req.query.state; if (!cookieNonce || !stateNonce) { @@ -106,11 +106,15 @@ export class GoogleAuthProvider secure: false, sameSite: 'none', domain: 'localhost', - path: `/auth/google`, + path: `/auth/${this.providerConfig.provider}`, httpOnly: true, }; - res.cookie(`google-refresh-token`, refreshToken, options); + res.cookie( + `${this.providerConfig.provider}-refresh-token`, + refreshToken, + options, + ); return postMessageResponse(res, { type: 'auth-result', payload: user, @@ -128,11 +132,11 @@ export class GoogleAuthProvider secure: false, sameSite: 'none', domain: 'localhost', - path: `/auth/google`, + path: `/auth/${this.providerConfig.provider}`, httpOnly: true, }; - res.cookie(`google-refresh-token`, '', options); + res.cookie(`${this.providerConfig.provider}-refresh-token`, '', options); return res.send('logout!'); } @@ -141,7 +145,8 @@ export class GoogleAuthProvider return res.status(401).send('Invalid X-Requested-With header'); } - const refreshToken = req.cookies[`google-refresh-token`]; + const refreshToken = + req.cookies[`${this.providerConfig.provider}-refresh-token`]; if (!refreshToken) { return res.status(401).send('Missing session cookie'); diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 68fe61c7a6..1b33391c27 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -20,11 +20,11 @@ import { ProviderFactories } from './factories'; export const defaultRouter = (provider: AuthProviderRouteHandlers) => { const router = Router(); - router.get('/start', provider.start); - router.get('/handler/frame', provider.frameHandler); - router.get('/logout', provider.logout); + router.get('/start', provider.start.bind(provider)); + router.get('/handler/frame', provider.frameHandler.bind(provider)); + router.get('/logout', provider.logout.bind(provider)); if (provider.refresh) { - router.get('/refresh', provider.refresh); + router.get('/refresh', provider.refresh.bind(provider)); } return router; }; From 86518a0210e502fd152d2e886eb9893ba9ba673f Mon Sep 17 00:00:00 2001 From: Raghunandan Date: Tue, 26 May 2020 17:48:38 +0200 Subject: [PATCH 04/23] add test cases for nonce checks --- .../src/providers/google/provider.test.ts | 112 +++++++++++++++++- .../src/providers/google/provider.ts | 2 +- 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/plugins/auth-backend/src/providers/google/provider.test.ts b/plugins/auth-backend/src/providers/google/provider.test.ts index 874b89d92c..327bf0260b 100644 --- a/plugins/auth-backend/src/providers/google/provider.test.ts +++ b/plugins/auth-backend/src/providers/google/provider.test.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { GoogleAuthProvider, THOUSAND_DAYS_MS } from './provider'; +import { + GoogleAuthProvider, + THOUSAND_DAYS_MS, + TEN_MINUTES_MS, +} from './provider'; import passport from 'passport'; import express from 'express'; import * as utils from './../utils'; @@ -55,6 +59,7 @@ describe('GoogleAuthProvider', () => { const mockResponse = ({ send: jest.fn().mockReturnThis(), status: jest.fn().mockReturnThis(), + cookie: jest.fn().mockReturnThis(), } as unknown) as express.Response; const mockNext: express.NextFunction = jest.fn(); @@ -79,9 +84,33 @@ describe('GoogleAuthProvider', () => { scope: 'a,b', accessType: 'offline', prompt: 'consent', + state: expect.any(String), }); }); + it('should set a nonce cookie', () => { + const mockRequest = ({ + header: () => 'XMLHttpRequest', + query: { + scope: 'a,b', + }, + } as unknown) as express.Request; + + const googleAuthProvider = new GoogleAuthProvider( + googleAuthProviderConfig, + ); + googleAuthProvider.start(mockRequest, mockResponse, mockNext); + expect(mockResponse.cookie).toBeCalledTimes(1); + expect(mockResponse.cookie).toBeCalledWith( + 'google-nonce', + expect.any(String), + expect.objectContaining({ + maxAge: TEN_MINUTES_MS, + path: `/auth/${googleAuthProviderConfig.provider}/handler`, + }), + ); + }); + it('should throw error if no scopes provided', () => { const mockRequest = ({ header: () => 'XMLHttpRequest', @@ -129,9 +158,6 @@ describe('GoogleAuthProvider', () => { }); describe('redirect frame handler', () => { - const mockRequest = ({ - header: () => 'XMLHttpRequest', - } as unknown) as express.Request; const mockResponse: any = ({ status: jest.fn().mockReturnThis(), send: jest.fn().mockReturnThis(), @@ -140,6 +166,14 @@ describe('GoogleAuthProvider', () => { const mockNext: express.NextFunction = jest.fn(); it('should call authenticate and post a response', () => { + const mockRequest = ({ + header: () => 'XMLHttpRequest', + cookies: { 'google-nonce': 'NONCE' }, + query: { + state: 'NONCE', + }, + } as unknown) as express.Request; + const spyPostMessage = jest .spyOn(utils, 'postMessageResponse') .mockImplementation(() => jest.fn()); @@ -173,6 +207,14 @@ describe('GoogleAuthProvider', () => { }); it('should respond with a error message if no refresh token returned', () => { + const mockRequest = ({ + header: () => 'XMLHttpRequest', + cookies: { 'google-nonce': 'NONCE' }, + query: { + state: 'NONCE', + }, + } as unknown) as express.Request; + const spyPassport = jest .spyOn(passport, 'authenticate') .mockImplementation((_x, callbackFunc) => { @@ -199,6 +241,14 @@ describe('GoogleAuthProvider', () => { }); it('should respond with a error message if auth failed', () => { + const mockRequest = ({ + header: () => 'XMLHttpRequest', + cookies: { 'google-nonce': 'NONCE' }, + query: { + state: 'NONCE', + }, + } as unknown) as express.Request; + const spyPassport = jest .spyOn(passport, 'authenticate') .mockImplementation((_x, callbackFunc) => { @@ -223,6 +273,60 @@ describe('GoogleAuthProvider', () => { error: new Error('Google auth failed, Error: TokenError'), }); }); + + it('should respond with a error message if cookie nonce is missing', () => { + const mockRequest = ({ + header: () => 'XMLHttpRequest', + cookies: {}, + query: { state: 'NONCE' }, + } as unknown) as express.Request; + + const googleAuthProvider = new GoogleAuthProvider( + googleAuthProviderConfig, + ); + + googleAuthProvider.frameHandler(mockRequest, mockResponse, mockNext); + expect(mockResponse.send).toBeCalledTimes(1); + expect(mockResponse.send).toBeCalledWith('Missing nonce'); + expect(mockResponse.status).toBeCalledTimes(1); + expect(mockResponse.status).toBeCalledWith(401); + }); + + it('should respond with a error message if state nonce is missing', () => { + const mockRequest = ({ + header: () => 'XMLHttpRequest', + cookies: { 'google-nonce': 'NONCE' }, + query: {}, + } as unknown) as express.Request; + + const googleAuthProvider = new GoogleAuthProvider( + googleAuthProviderConfig, + ); + + googleAuthProvider.frameHandler(mockRequest, mockResponse, mockNext); + expect(mockResponse.send).toBeCalledTimes(1); + expect(mockResponse.send).toBeCalledWith('Missing nonce'); + expect(mockResponse.status).toBeCalledTimes(1); + expect(mockResponse.status).toBeCalledWith(401); + }); + + it('should respond with a error message if nonce mismatch', () => { + const mockRequest = ({ + header: () => 'XMLHttpRequest', + cookies: { 'google-nonce': 'NONCA' }, + query: { state: 'NONCEB' }, + } as unknown) as express.Request; + + const googleAuthProvider = new GoogleAuthProvider( + googleAuthProviderConfig, + ); + + googleAuthProvider.frameHandler(mockRequest, mockResponse, mockNext); + expect(mockResponse.send).toBeCalledTimes(1); + expect(mockResponse.send).toBeCalledWith('Invalid nonce'); + expect(mockResponse.status).toBeCalledTimes(1); + expect(mockResponse.status).toBeCalledWith(401); + }); }); describe('strategy handler', () => { diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index a631f86fa6..cb080e2fd3 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -28,7 +28,7 @@ import { postMessageResponse, ensuresXRequestedWith } from './../utils'; import { InputError } from '@backstage/backend-common'; export const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000; -const TEN_MINUTES_MS = 600 * 1000; +export const TEN_MINUTES_MS = 600 * 1000; export class GoogleAuthProvider implements AuthProvider, AuthProviderRouteHandlers { private readonly providerConfig: AuthProviderConfig; From f72d97dc171424785f9f7ae694c0c88241f4c52c Mon Sep 17 00:00:00 2001 From: Adil Alimbetov Date: Wed, 27 May 2020 14:13:41 +0600 Subject: [PATCH 05/23] Sidebar logo update (#814) * Testing logo ux * Updated logo on sidebar * removed unused vars * adjusted types on icon components --- CONTRIBUTING.md | 1 - packages/app/src/components/Root/LogoFull.tsx | 46 ++++++++++++++++++ packages/app/src/components/Root/LogoIcon.tsx | 47 +++++++++++++++++++ packages/app/src/components/Root/Root.tsx | 28 +++-------- packages/backend/src/plugins/sentry.ts | 2 +- .../cli/src/commands/create-app/createApp.ts | 4 +- .../commands/create-plugin/createPlugin.ts | 10 ++-- packages/cli/src/lib/bundler/bundle.ts | 2 +- packages/cli/src/lib/tasks.ts | 12 ++--- .../default-app/packages/app/src/App.tsx | 2 +- packages/core/src/api/apis/ApiProvider.tsx | 2 +- .../api/apis/implementations/lib/subjects.ts | 16 +++---- .../core/src/api/app/AppThemeProvider.tsx | 6 +-- .../core/src/api/app/FeatureFlags.test.tsx | 2 +- packages/core/src/api/app/FeatureFlags.tsx | 6 +-- .../components/CodeSnippet/CodeSnippet.tsx | 2 +- .../HorizontalScrollGrid.test.jsx | 2 +- .../LoginRequestListItem.tsx | 2 +- .../OAuthRequestDialog/OAuthRequestDialog.tsx | 6 +-- .../core/src/components/Status/Status.tsx | 14 +++--- .../StructuredMetadataTable.test.jsx | 10 ++-- .../core/src/layout/ErrorPage/ErrorPage.tsx | 2 +- packages/core/src/layout/Sidebar/Intro.tsx | 10 ++-- packages/core/src/layout/Sidebar/Items.tsx | 10 ++-- packages/core/src/layout/Sidebar/Page.tsx | 2 +- packages/storybook/.storybook/main.js | 2 +- plugins/auth-backend/src/run.ts | 2 +- plugins/catalog/src/data/mock-factory.ts | 8 ++-- .../src/components/Settings/Settings.tsx | 6 +-- .../BuildWithStepsPage/BuildWithStepsPage.tsx | 2 +- .../lib/ActionOutput/ActionOutput.tsx | 4 +- plugins/circleci/src/state/useAsyncPolling.ts | 2 +- plugins/circleci/src/state/useSettings.ts | 2 +- plugins/identity-backend/src/run.ts | 2 +- .../src/components/AuditView/index.test.tsx | 14 +++--- .../src/components/CreateAudit/index.test.tsx | 2 +- .../RegisterComponentForm.tsx | 2 +- plugins/sentry-backend/README.md | 2 +- .../src/components/ErrorCell/ErrorCell.tsx | 2 +- .../SentryIssuesTable/SentryIssuesTable.tsx | 8 ++-- plugins/sentry/src/data/mock-api.ts | 2 +- .../tech-radar/src/components/Radar/Radar.jsx | 8 ++-- .../components/RadarBubble/RadarBubble.jsx | 8 ++-- .../src/components/RadarGrid/RadarGrid.jsx | 2 +- .../components/RadarLegend/RadarLegend.jsx | 6 +-- .../src/components/RadarPlot/RadarPlot.jsx | 6 +-- 46 files changed, 208 insertions(+), 130 deletions(-) create mode 100644 packages/app/src/components/Root/LogoFull.tsx create mode 100644 packages/app/src/components/Root/LogoIcon.tsx diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4b31a74b2..43c1f0d7b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,6 @@ Contributions are welcome, and they are greatly appreciated! Every little bit he Backstage is released under the Apache2.0 License, and original creations contributed to this repo are accepted under the same license. - # Types of Contributions ## Report bugs diff --git a/packages/app/src/components/Root/LogoFull.tsx b/packages/app/src/components/Root/LogoFull.tsx new file mode 100644 index 0000000000..d2b1bf1080 --- /dev/null +++ b/packages/app/src/components/Root/LogoFull.tsx @@ -0,0 +1,46 @@ +/* + * 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, { FC } from 'react'; +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ + svg: { + width: 'auto', + height: 30, + }, + path: { + fill: '#7df3e1', + }, +}); +const LogoFull: FC<{}> = () => { + const classes = useStyles(); + + return ( + + + + ); +}; + +export default LogoFull; diff --git a/packages/app/src/components/Root/LogoIcon.tsx b/packages/app/src/components/Root/LogoIcon.tsx new file mode 100644 index 0000000000..d70be3dd32 --- /dev/null +++ b/packages/app/src/components/Root/LogoIcon.tsx @@ -0,0 +1,47 @@ +/* + * 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, { FC } from 'react'; +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ + svg: { + width: 'auto', + height: 28, + }, + path: { + fill: '#7df3e1', + }, +}); + +const LogoIcon: FC<{}> = () => { + const classes = useStyles(); + + return ( + + + + ); +}; + +export default LogoIcon; diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index c26336c18b..3c5ec4ae3a 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -16,12 +16,13 @@ import React, { FC, useContext } from 'react'; import PropTypes from 'prop-types'; -import { Link, makeStyles, Typography } from '@material-ui/core'; +import { Link, makeStyles } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ExploreIcon from '@material-ui/icons/Explore'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import AccountTreeIcon from '@material-ui/icons/AccountTree'; - +import LogoFull from './LogoFull'; +import LogoIcon from './LogoIcon'; import { Sidebar, SidebarPage, @@ -44,21 +45,9 @@ const useSidebarLogoStyles = makeStyles({ alignItems: 'center', marginBottom: -14, }, - logoContainer: { + link: { width: sidebarConfig.drawerWidthClosed, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, - title: { - fontSize: sidebarConfig.logoHeight, - fontWeight: 'bold', - marginLeft: 20, - whiteSpace: 'nowrap', - color: '#fff', - }, - titleDot: { - color: '#68c5b5', + marginLeft: 24, }, }); @@ -68,11 +57,8 @@ const SidebarLogo: FC<{}> = () => { return (
- - - {isOpen ? 'Backstage' : 'B'} - . - + + {isOpen ? : }
); diff --git a/packages/backend/src/plugins/sentry.ts b/packages/backend/src/plugins/sentry.ts index ddb7ddd540..34506ee3de 100644 --- a/packages/backend/src/plugins/sentry.ts +++ b/packages/backend/src/plugins/sentry.ts @@ -17,6 +17,6 @@ import { createRouter } from '@backstage/plugin-sentry-backend'; import { Logger } from 'winston'; -export default async function(logger: Logger) { +export default async function (logger: Logger) { return await createRouter(logger); } diff --git a/packages/cli/src/commands/create-app/createApp.ts b/packages/cli/src/commands/create-app/createApp.ts index e28cb376b9..a42271435b 100644 --- a/packages/cli/src/commands/create-app/createApp.ts +++ b/packages/cli/src/commands/create-app/createApp.ts @@ -62,7 +62,7 @@ async function buildApp(appDir: string) { await Task.forItem('executing', cmd, async () => { process.chdir(appDir); - await exec(cmd).catch((error) => { + await exec(cmd).catch(error => { process.stdout.write(error.stderr); process.stdout.write(error.stdout); throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); @@ -81,7 +81,7 @@ export async function moveApp( id: string, ) { await Task.forItem('moving', id, async () => { - await fs.move(tempDir, destination).catch((error) => { + await fs.move(tempDir, destination).catch(error => { throw new Error( `Failed to move app from ${tempDir} to ${destination}: ${error.message}`, ); diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 1441b16d05..3a91044500 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -107,7 +107,7 @@ export async function addPluginDependencyToApp( packageFileJson.dependencies = sortObjectByKeys(dependencies); const newContents = `${JSON.stringify(packageFileJson, null, 2)}\n`; - await fs.writeFile(packageFile, newContents, 'utf-8').catch((error) => { + await fs.writeFile(packageFile, newContents, 'utf-8').catch(error => { throw new Error( `Failed to add plugin as dependency to app: ${packageFile}: ${error.message}`, ); @@ -119,14 +119,14 @@ export async function addPluginToApp(rootDir: string, pluginName: string) { const pluginPackage = `@backstage/plugin-${pluginName}`; const pluginNameCapitalized = pluginName .split('-') - .map((name) => capitalize(name)) + .map(name => capitalize(name)) .join(''); const pluginExport = `export { plugin as ${pluginNameCapitalized} } from '${pluginPackage}';`; const pluginsFilePath = 'packages/app/src/plugins.ts'; const pluginsFile = resolvePath(rootDir, pluginsFilePath); await Task.forItem('processing', pluginsFilePath, async () => { - await addExportStatement(pluginsFile, pluginExport).catch((error) => { + await addExportStatement(pluginsFile, pluginExport).catch(error => { throw new Error( `Failed to import plugin in app: ${pluginsFile}: ${error.message}`, ); @@ -148,7 +148,7 @@ async function buildPlugin(pluginFolder: string) { await Task.forItem('executing', command, async () => { process.chdir(pluginFolder); - await exec(command).catch((error) => { + await exec(command).catch(error => { process.stdout.write(error.stderr); process.stdout.write(error.stdout); throw new Error(`Could not execute command ${chalk.cyan(command)}`); @@ -163,7 +163,7 @@ export async function movePlugin( id: string, ) { await Task.forItem('moving', id, async () => { - await fs.move(tempDir, destination).catch((error) => { + await fs.move(tempDir, destination).catch(error => { throw new Error( `Failed to move plugin from ${tempDir} to ${destination}: ${error.message}`, ); diff --git a/packages/cli/src/lib/bundler/bundle.ts b/packages/cli/src/lib/bundler/bundle.ts index f24da5b0c8..c7860c0f42 100644 --- a/packages/cli/src/lib/bundler/bundle.ts +++ b/packages/cli/src/lib/bundler/bundle.ts @@ -48,7 +48,7 @@ export async function buildBundle(options: BuildOptions) { const previousFileSizes = await measureFileSizesBeforeBuild(paths.targetDist); await fs.emptyDir(paths.targetDist); - const { stats } = await build(compiler, isCi).catch((error) => { + const { stats } = await build(compiler, isCi).catch(error => { console.log(chalk.red('Failed to compile.\n')); throw new Error(`Failed to compile.\n${error.message || error}`); }); diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index 721e1a355b..6b752a36cc 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -73,7 +73,7 @@ export async function templatingTask( destinationDir: string, context: any, ) { - const files = await recursive(templateDir).catch((error) => { + const files = await recursive(templateDir).catch(error => { throw new Error(`Failed to read template directory: ${error.message}`); }); @@ -89,7 +89,7 @@ export async function templatingTask( const compiled = handlebars.compile(template.toString()); const contents = compiled({ name: basename(destination), ...context }); - await fs.writeFile(destination, contents).catch((error) => { + await fs.writeFile(destination, contents).catch(error => { throw new Error( `Failed to create file: ${destination}: ${error.message}`, ); @@ -97,7 +97,7 @@ export async function templatingTask( }); } else { await Task.forItem('copying', basename(file), async () => { - await fs.copyFile(file, destinationFile).catch((error) => { + await fs.copyFile(file, destinationFile).catch(error => { const destination = destinationFile; throw new Error( `Failed to copy file to ${destination} : ${error.message}`, @@ -145,7 +145,7 @@ export async function installWithLocalDeps(dir: string) { await fs .writeJSON(pkgJsonPath, pkgJson, { encoding: 'utf8', spaces: 2 }) - .catch((error) => { + .catch(error => { throw new Error( `Failed to add resolutions to package.json: ${error.message}`, ); @@ -157,7 +157,7 @@ export async function installWithLocalDeps(dir: string) { } await Task.forItem('executing', 'yarn install', async () => { - await exec('yarn install', { cwd: dir }).catch((error) => { + await exec('yarn install', { cwd: dir }).catch(error => { process.stdout.write(error.stderr); process.stdout.write(error.stdout); throw new Error( @@ -193,7 +193,7 @@ export async function installWithLocalDeps(dir: string) { await fs .writeJSON(depJsonPath, depJson, { encoding: 'utf8', spaces: 2 }) - .catch((error) => { + .catch(error => { throw new Error( `Failed to add resolutions to package.json: ${error.message}`, ); diff --git a/packages/cli/templates/default-app/packages/app/src/App.tsx b/packages/cli/templates/default-app/packages/app/src/App.tsx index c32efd2a6b..84bc1b2e7d 100644 --- a/packages/cli/templates/default-app/packages/app/src/App.tsx +++ b/packages/cli/templates/default-app/packages/app/src/App.tsx @@ -4,7 +4,7 @@ import React, { FC } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; import * as plugins from './plugins'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ '@global': { html: { height: '100%', diff --git a/packages/core/src/api/apis/ApiProvider.tsx b/packages/core/src/api/apis/ApiProvider.tsx index 46f89cfa0d..1c5abf1c56 100644 --- a/packages/core/src/api/apis/ApiProvider.tsx +++ b/packages/core/src/api/apis/ApiProvider.tsx @@ -53,7 +53,7 @@ export function withApis(apis: TypesToApiRefs) { return function withApisWrapper

( WrappedComponent: React.ComponentType

, ) { - const Hoc: FC> = (props) => { + const Hoc: FC> = props => { const apiHolder = useContext(Context); if (!apiHolder) { diff --git a/packages/core/src/api/apis/implementations/lib/subjects.ts b/packages/core/src/api/apis/implementations/lib/subjects.ts index b33df70a65..5b424b7e55 100644 --- a/packages/core/src/api/apis/implementations/lib/subjects.ts +++ b/packages/core/src/api/apis/implementations/lib/subjects.ts @@ -33,7 +33,7 @@ export class PublishSubject private isClosed = false; private terminatingError?: Error; - private readonly observable = new ObservableImpl((subscriber) => { + private readonly observable = new ObservableImpl(subscriber => { if (this.isClosed) { if (this.terminatingError) { subscriber.error(this.terminatingError); @@ -61,7 +61,7 @@ export class PublishSubject if (this.isClosed) { throw new Error('PublishSubject is closed'); } - this.subscribers.forEach((subscriber) => subscriber.next(value)); + this.subscribers.forEach(subscriber => subscriber.next(value)); } error(error: Error) { @@ -70,7 +70,7 @@ export class PublishSubject } this.isClosed = true; this.terminatingError = error; - this.subscribers.forEach((subscriber) => subscriber.error(error)); + this.subscribers.forEach(subscriber => subscriber.error(error)); } complete() { @@ -78,7 +78,7 @@ export class PublishSubject throw new Error('PublishSubject is closed'); } this.isClosed = true; - this.subscribers.forEach((subscriber) => subscriber.complete()); + this.subscribers.forEach(subscriber => subscriber.complete()); } subscribe(observer: ZenObservable.Observer): ZenObservable.Subscription; @@ -126,7 +126,7 @@ export class BehaviorSubject this.currentValue = value; } - private readonly observable = new ObservableImpl((subscriber) => { + private readonly observable = new ObservableImpl(subscriber => { if (this.isClosed) { if (this.terminatingError) { subscriber.error(this.terminatingError); @@ -157,7 +157,7 @@ export class BehaviorSubject throw new Error('BehaviorSubject is closed'); } this.currentValue = value; - this.subscribers.forEach((subscriber) => subscriber.next(value)); + this.subscribers.forEach(subscriber => subscriber.next(value)); } error(error: Error) { @@ -166,7 +166,7 @@ export class BehaviorSubject } this.isClosed = true; this.terminatingError = error; - this.subscribers.forEach((subscriber) => subscriber.error(error)); + this.subscribers.forEach(subscriber => subscriber.error(error)); } complete() { @@ -174,7 +174,7 @@ export class BehaviorSubject throw new Error('BehaviorSubject is closed'); } this.isClosed = true; - this.subscribers.forEach((subscriber) => subscriber.complete()); + this.subscribers.forEach(subscriber => subscriber.complete()); } subscribe(observer: ZenObservable.Observer): ZenObservable.Subscription; diff --git a/packages/core/src/api/app/AppThemeProvider.tsx b/packages/core/src/api/app/AppThemeProvider.tsx index 6e68fe9ba0..775d8293ba 100644 --- a/packages/core/src/api/app/AppThemeProvider.tsx +++ b/packages/core/src/api/app/AppThemeProvider.tsx @@ -27,20 +27,20 @@ function resolveTheme( themes: AppTheme[], ) { if (themeId !== undefined) { - const selectedTheme = themes.find((theme) => theme.id === themeId); + const selectedTheme = themes.find(theme => theme.id === themeId); if (selectedTheme) { return selectedTheme; } } if (shouldPreferDark) { - const darkTheme = themes.find((theme) => theme.variant === 'dark'); + const darkTheme = themes.find(theme => theme.variant === 'dark'); if (darkTheme) { return darkTheme; } } - const lightTheme = themes.find((theme) => theme.variant === 'light'); + const lightTheme = themes.find(theme => theme.variant === 'light'); if (lightTheme) { return lightTheme; } diff --git a/packages/core/src/api/app/FeatureFlags.test.tsx b/packages/core/src/api/app/FeatureFlags.test.tsx index ca4a819f13..99974f9b1d 100644 --- a/packages/core/src/api/app/FeatureFlags.test.tsx +++ b/packages/core/src/api/app/FeatureFlags.test.tsx @@ -147,7 +147,7 @@ describe('FeatureFlags', () => { it('should get the correct values', () => { const getByName = (name: string) => - featureFlags.getRegisteredFlags().find((flag) => flag.name === name); + featureFlags.getRegisteredFlags().find(flag => flag.name === name); expect(getByName('registered-flag-0')).toBeUndefined(); expect(getByName('registered-flag-1')).toEqual({ diff --git a/packages/core/src/api/app/FeatureFlags.tsx b/packages/core/src/api/app/FeatureFlags.tsx index 6af10f228e..11c084d3ed 100644 --- a/packages/core/src/api/app/FeatureFlags.tsx +++ b/packages/core/src/api/app/FeatureFlags.tsx @@ -127,12 +127,12 @@ export interface FeatureFlagsRegistryItem { export class FeatureFlagsRegistry extends Array { static from(entries: FeatureFlagsRegistryItem[]) { - Array.from(entries).forEach((entry) => validateFlagName(entry.name)); + Array.from(entries).forEach(entry => validateFlagName(entry.name)); return new FeatureFlagsRegistry(...entries); } push(...entries: FeatureFlagsRegistryItem[]): number { - Array.from(entries).forEach((entry) => validateFlagName(entry.name)); + Array.from(entries).forEach(entry => validateFlagName(entry.name)); return super.push(...entries); } @@ -143,7 +143,7 @@ export class FeatureFlagsRegistry extends Array { )[] ): FeatureFlagsRegistryItem[] { const _concat = super.concat(...entries); - Array.from(_concat).forEach((entry) => validateFlagName(entry.name)); + Array.from(_concat).forEach(entry => validateFlagName(entry.name)); return _concat; } diff --git a/packages/core/src/components/CodeSnippet/CodeSnippet.tsx b/packages/core/src/components/CodeSnippet/CodeSnippet.tsx index fc7c262882..c77dc5bd43 100644 --- a/packages/core/src/components/CodeSnippet/CodeSnippet.tsx +++ b/packages/core/src/components/CodeSnippet/CodeSnippet.tsx @@ -31,7 +31,7 @@ const defaultProps = { showLineNumbers: false, }; -const CodeSnippet: FC = (props) => { +const CodeSnippet: FC = props => { const { text, language, showLineNumbers } = { ...defaultProps, ...props, diff --git a/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.jsx b/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.jsx index 36079854fd..d7f1e2387a 100644 --- a/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.jsx +++ b/packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.jsx @@ -25,7 +25,7 @@ describe('', () => { jest.spyOn(window.performance, 'now').mockReturnValue(5); jest .spyOn(window, 'requestAnimationFrame') - .mockImplementation((cb) => cb(20)); + .mockImplementation(cb => cb(20)); }); afterEach(() => { diff --git a/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx index 8e3d98c998..d97956c1f0 100644 --- a/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx +++ b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx @@ -25,7 +25,7 @@ import { import React, { FC, useState } from 'react'; import { PendingAuthRequest } from '../../api'; -const useItemStyles = makeStyles((theme) => ({ +const useItemStyles = makeStyles(theme => ({ root: { paddingLeft: theme.spacing(3), }, diff --git a/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx index 617cce2aee..bde12ccb04 100644 --- a/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx +++ b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx @@ -29,7 +29,7 @@ import { useObservable } from 'react-use'; import LoginRequestListItem from './LoginRequestListItem'; import { useApi, oauthRequestApiRef } from '../../api'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ dialog: { paddingTop: theme.spacing(1), }, @@ -53,7 +53,7 @@ export const OAuthRequestDialog: FC = () => { ); const handleRejectAll = () => { - requests.forEach((request) => request.reject()); + requests.forEach(request => request.reject()); }; return ( @@ -69,7 +69,7 @@ export const OAuthRequestDialog: FC = () => { - {requests.map((request) => ( + {requests.map(request => ( ((theme) => ({ +const useStyles = makeStyles(theme => ({ status: { fontWeight: 500, '&::before': { @@ -63,26 +63,26 @@ const useStyles = makeStyles((theme) => ({ }, })); -export const StatusOK: FC<{}> = (props) => { +export const StatusOK: FC<{}> = props => { const classes = useStyles(props); return ; }; -export const StatusWarning: FC<{}> = (props) => { +export const StatusWarning: FC<{}> = props => { const classes = useStyles(props); return ( ); }; -export const StatusError: FC<{}> = (props) => { +export const StatusError: FC<{}> = props => { const classes = useStyles(props); return ( ); }; -export const StatusPending: FC<{}> = (props) => { +export const StatusPending: FC<{}> = props => { const classes = useStyles(props); return ( = (props) => { ); }; -export const StatusRunning: FC<{}> = (props) => { +export const StatusRunning: FC<{}> = props => { const classes = useStyles(props); return ( = (props) => { ); }; -export const StatusAborted: FC<{}> = (props) => { +export const StatusAborted: FC<{}> = props => { const classes = useStyles(props); return ( ', () => { , ); const keys = Object.keys(metadata); - keys.forEach((value) => { + keys.forEach(value => { expect(getByText(startCase(value))).toBeInTheDocument(); expect(getByText(metadata[value])).toBeInTheDocument(); }); @@ -49,7 +49,7 @@ describe('', () => { ); const keys = Object.keys(metadata); - keys.forEach((value) => { + keys.forEach(value => { expect(getByText(startCase(value))).toBeInTheDocument(); expect(getByText(metadata[value].toString())).toBeInTheDocument(); }); @@ -61,10 +61,10 @@ describe('', () => { , ); const keys = Object.keys(metadata); - keys.forEach((value) => { + keys.forEach(value => { expect(getByText(startCase(value))).toBeInTheDocument(); }); - metadata.arrayField.forEach((value) => { + metadata.arrayField.forEach(value => { expect(getByText(value)).toBeInTheDocument(); }); }); @@ -85,7 +85,7 @@ describe('', () => { ); const keys = Object.keys(metadata.config); - keys.forEach((value) => { + keys.forEach(value => { expect( getByText(startCase(value), { exact: false }), ).toBeInTheDocument(); diff --git a/packages/core/src/layout/ErrorPage/ErrorPage.tsx b/packages/core/src/layout/ErrorPage/ErrorPage.tsx index 14241c1ee7..747f2f6036 100644 --- a/packages/core/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core/src/layout/ErrorPage/ErrorPage.tsx @@ -26,7 +26,7 @@ interface IErrorPageProps { statusMessage: string; } -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ container: { padding: theme.spacing(8), }, diff --git a/packages/core/src/layout/Sidebar/Intro.tsx b/packages/core/src/layout/Sidebar/Intro.tsx index 595697c466..b4bb56588e 100644 --- a/packages/core/src/layout/Sidebar/Intro.tsx +++ b/packages/core/src/layout/Sidebar/Intro.tsx @@ -26,7 +26,7 @@ import { } from './config'; import { SidebarDivider } from './Items'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ introCard: { color: '#b5b5b5', // XXX (@koroeskohr): should I be using a Mui theme variable? @@ -74,7 +74,7 @@ type IntroCardProps = { onClose: () => void; }; -export const IntroCard: FC = (props) => { +export const IntroCard: FC = props => { const classes = useStyles(); const { text, onClose } = props; const handleClose = () => onClose(); @@ -109,7 +109,7 @@ type SidebarIntroCardProps = { onDismiss: () => void; }; -const SidebarIntroCard: FC = (props) => { +const SidebarIntroCard: FC = props => { const { text, onDismiss } = props; const [collapsing, setCollapsing] = useState(false); const startDismissing = () => { @@ -138,10 +138,10 @@ export const SidebarIntro: FC = () => { }); const dismissStarred = () => { - setDismissedIntro((state) => ({ ...state, starredItemsDismissed: true })); + setDismissedIntro(state => ({ ...state, starredItemsDismissed: true })); }; const dismissRecentlyViewed = () => { - setDismissedIntro((state) => ({ + setDismissedIntro(state => ({ ...state, recentlyViewedItemsDismissed: true, })); diff --git a/packages/core/src/layout/Sidebar/Items.tsx b/packages/core/src/layout/Sidebar/Items.tsx index bd5faa5631..4696f53a37 100644 --- a/packages/core/src/layout/Sidebar/Items.tsx +++ b/packages/core/src/layout/Sidebar/Items.tsx @@ -29,7 +29,7 @@ import { NavLink } from 'react-router-dom'; import { sidebarConfig, SidebarContext } from './config'; import { IconComponent } from '../../icons'; -const useStyles = makeStyles((theme) => { +const useStyles = makeStyles(theme => { const { selectedIndicatorWidth, drawerWidthClosed, @@ -139,7 +139,7 @@ export const SidebarItem: FC = ({ Boolean(match && !disableSelected)} + isActive={match => Boolean(match && !disableSelected)} exact to={to} onClick={onClick} @@ -153,7 +153,7 @@ export const SidebarItem: FC = ({ Boolean(match && !disableSelected)} + isActive={match => Boolean(match && !disableSelected)} exact to={to} onClick={onClick} @@ -175,11 +175,11 @@ type SidebarSearchFieldProps = { onSearch: (input: string) => void; }; -export const SidebarSearchField: FC = (props) => { +export const SidebarSearchField: FC = props => { const [input, setInput] = useState(''); const classes = useStyles(); - const handleEnter: KeyboardEventHandler = (ev) => { + const handleEnter: KeyboardEventHandler = ev => { if (ev.key === 'Enter') { props.onSearch(input); } diff --git a/packages/core/src/layout/Sidebar/Page.tsx b/packages/core/src/layout/Sidebar/Page.tsx index 79df2852d9..750f764c2d 100644 --- a/packages/core/src/layout/Sidebar/Page.tsx +++ b/packages/core/src/layout/Sidebar/Page.tsx @@ -44,7 +44,7 @@ export const SidebarPinStateContext = createContext( }, ); -export const SidebarPage: FC<{}> = (props) => { +export const SidebarPage: FC<{}> = props => { const [isPinned, setIsPinned] = useState(LocalStorage.getSidebarPinState()); useEffect(() => { diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js index 5f9bde16c3..b8f78f5d2c 100644 --- a/packages/storybook/.storybook/main.js +++ b/packages/storybook/.storybook/main.js @@ -12,7 +12,7 @@ module.exports = { '@storybook/addon-storysource', 'storybook-dark-mode/register', ], - webpackFinal: async (config) => { + webpackFinal: async config => { const coreSrc = path.resolve(__dirname, '../../core/src'); // Mirror config in packages/cli/src/lib/bundler diff --git a/plugins/auth-backend/src/run.ts b/plugins/auth-backend/src/run.ts index 0a684c379e..a2c2601258 100644 --- a/plugins/auth-backend/src/run.ts +++ b/plugins/auth-backend/src/run.ts @@ -22,7 +22,7 @@ const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 3003; const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); const logger = getRootLogger(); -startStandaloneServer({ port, enableCors, logger }).catch((err) => { +startStandaloneServer({ port, enableCors, logger }).catch(err => { logger.error(err); process.exit(1); }); diff --git a/plugins/catalog/src/data/mock-factory.ts b/plugins/catalog/src/data/mock-factory.ts index bf39fa436c..19f04195f9 100644 --- a/plugins/catalog/src/data/mock-factory.ts +++ b/plugins/catalog/src/data/mock-factory.ts @@ -20,7 +20,7 @@ const ARTIFICIAL_TIMEOUT = 800; let inMemoryStore = [...mock]; export const MockComponentFactory: ComponentFactory = { getAllComponents(): Promise { - return new Promise((resolve) => + return new Promise(resolve => setTimeout(() => resolve(inMemoryStore), ARTIFICIAL_TIMEOUT), ); }, @@ -28,7 +28,7 @@ export const MockComponentFactory: ComponentFactory = { return new Promise((resolve, reject) => setTimeout(() => { const mockComponent = inMemoryStore.find( - (component) => component.name === name, + component => component.name === name, ); if (mockComponent) return resolve(mockComponent); return reject({ code: 'Component not found!' }); @@ -36,10 +36,10 @@ export const MockComponentFactory: ComponentFactory = { ); }, removeComponentByName(name: string): Promise { - return new Promise((resolve) => + return new Promise(resolve => setTimeout(() => { inMemoryStore = inMemoryStore.filter( - (component) => component.name !== name, + component => component.name !== name, ); resolve(true); }, ARTIFICIAL_TIMEOUT), diff --git a/plugins/circleci/src/components/Settings/Settings.tsx b/plugins/circleci/src/components/Settings/Settings.tsx index e853bb46bb..f0ed3f2ceb 100644 --- a/plugins/circleci/src/components/Settings/Settings.tsx +++ b/plugins/circleci/src/components/Settings/Settings.tsx @@ -80,7 +80,7 @@ const Settings = () => { value={token} fullWidth variant="outlined" - onChange={(e) => setToken(e.target.value)} + onChange={e => setToken(e.target.value)} /> @@ -90,7 +90,7 @@ const Settings = () => { label="Owner" variant="outlined" value={owner} - onChange={(e) => setOwner(e.target.value)} + onChange={e => setOwner(e.target.value)} /> @@ -100,7 +100,7 @@ const Settings = () => { fullWidth variant="outlined" value={repo} - onChange={(e) => setRepo(e.target.value)} + onChange={e => setRepo(e.target.value)} /> diff --git a/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx index 8493913576..75d09610b5 100644 --- a/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -35,7 +35,7 @@ const BuildName: FC<{ build?: BuildWithSteps }> = ({ build }) => ( ); -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ neutral: {}, failed: { position: 'relative', diff --git a/plugins/circleci/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx b/plugins/circleci/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx index e8e1bfe958..dd526c1b5f 100644 --- a/plugins/circleci/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx +++ b/plugins/circleci/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx @@ -50,8 +50,8 @@ export const ActionOutput: FC<{ const [messages, setMessages] = useState([]); useEffect(() => { fetch(url) - .then((res) => res.json()) - .then((actionOutput) => { + .then(res => res.json()) + .then(actionOutput => { if (typeof actionOutput !== 'undefined') { setMessages( actionOutput.map(({ message }: { message: string }) => message), diff --git a/plugins/circleci/src/state/useAsyncPolling.ts b/plugins/circleci/src/state/useAsyncPolling.ts index 2f8de0c2fe..7ea0755368 100644 --- a/plugins/circleci/src/state/useAsyncPolling.ts +++ b/plugins/circleci/src/state/useAsyncPolling.ts @@ -26,7 +26,7 @@ export const useAsyncPolling = ( while (isPolling.current === true) { await pollingFn(); - await new Promise((resolve) => setTimeout(resolve, interval)); + await new Promise(resolve => setTimeout(resolve, interval)); } }; diff --git a/plugins/circleci/src/state/useSettings.ts b/plugins/circleci/src/state/useSettings.ts index 5482abeb8b..c8dc9ce39b 100644 --- a/plugins/circleci/src/state/useSettings.ts +++ b/plugins/circleci/src/state/useSettings.ts @@ -29,7 +29,7 @@ export function useSettings() { if ( stateFromStorage && Object.keys(stateFromStorage).some( - (k) => (settings as any)[k] !== stateFromStorage[k], + k => (settings as any)[k] !== stateFromStorage[k], ) ) dispatch({ diff --git a/plugins/identity-backend/src/run.ts b/plugins/identity-backend/src/run.ts index 0a684c379e..a2c2601258 100644 --- a/plugins/identity-backend/src/run.ts +++ b/plugins/identity-backend/src/run.ts @@ -22,7 +22,7 @@ const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 3003; const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); const logger = getRootLogger(); -startStandaloneServer({ port, enableCors, logger }).catch((err) => { +startStandaloneServer({ port, enableCors, logger }).catch(err => { logger.error(err); process.exit(1); }); diff --git a/plugins/lighthouse/src/components/AuditView/index.test.tsx b/plugins/lighthouse/src/components/AuditView/index.test.tsx index 330b88517a..4e261fcf39 100644 --- a/plugins/lighthouse/src/components/AuditView/index.test.tsx +++ b/plugins/lighthouse/src/components/AuditView/index.test.tsx @@ -49,7 +49,7 @@ describe('AuditView', () => { apis = ApiRegistry.from([ [lighthouseApiRef, new LighthouseRestApi('https://lighthouse')], ]); - id = websiteResponse.audits.find((a) => a.status === 'COMPLETED') + id = websiteResponse.audits.find(a => a.status === 'COMPLETED') ?.id as string; useParams.mockReturnValue({ id }); }); @@ -101,7 +101,7 @@ describe('AuditView', () => { await rendered.findByTestId('audit-sidebar'); - websiteResponse.audits.forEach((a) => { + websiteResponse.audits.forEach(a => { expect( rendered.queryByText(formatTime(a.timeCreated)), ).toBeInTheDocument(); @@ -119,14 +119,14 @@ describe('AuditView', () => { await rendered.findByTestId('audit-sidebar'); - const audit = websiteResponse.audits.find((a) => a.id === id) as Audit; + const audit = websiteResponse.audits.find(a => a.id === id) as Audit; const auditElement = rendered.getByText(formatTime(audit.timeCreated)); expect(auditElement.parentElement?.parentElement?.className).toContain( 'selected', ); const notSelectedAudit = websiteResponse.audits.find( - (a) => a.id !== id, + a => a.id !== id, ) as Audit; const notSelectedAuditElement = rendered.getByText( formatTime(notSelectedAudit.timeCreated), @@ -147,7 +147,7 @@ describe('AuditView', () => { await rendered.findByTestId('audit-sidebar'); - websiteResponse.audits.forEach((a) => { + websiteResponse.audits.forEach(a => { expect( rendered.getByText(formatTime(a.timeCreated)).parentElement ?.parentElement, @@ -186,7 +186,7 @@ describe('AuditView', () => { describe.skip('when a loading audit is accessed', () => { it('shows a loading view', async () => { - id = websiteResponse.audits.find((a) => a.status === 'RUNNING') + id = websiteResponse.audits.find(a => a.status === 'RUNNING') ?.id as string; useParams.mockReturnValueOnce({ id }); @@ -206,7 +206,7 @@ describe('AuditView', () => { describe.skip('when a failed audit is accessed', () => { it('shows an error message', async () => { - id = websiteResponse.audits.find((a) => a.status === 'FAILED') + id = websiteResponse.audits.find(a => a.status === 'FAILED') ?.id as string; useParams.mockReturnValueOnce({ id }); diff --git a/plugins/lighthouse/src/components/CreateAudit/index.test.tsx b/plugins/lighthouse/src/components/CreateAudit/index.test.tsx index b337698485..79897539c4 100644 --- a/plugins/lighthouse/src/components/CreateAudit/index.test.tsx +++ b/plugins/lighthouse/src/components/CreateAudit/index.test.tsx @@ -165,7 +165,7 @@ describe('CreateAudit', () => { fireEvent.click(rendered.getByText(/Create Audit/)); await wait(() => expect(rendered.getByLabelText(/URL/)).toBeEnabled()); - await new Promise((r) => setTimeout(r, 0)); + await new Promise(r => setTimeout(r, 0)); expect(errorApi.post).toHaveBeenCalledWith(expect.any(Error)); }); diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx index fe4ba7845a..d279afac25 100644 --- a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx @@ -28,7 +28,7 @@ import { BackstageTheme } from '@backstage/theme'; import { Progress } from '@backstage/core'; import { ComponentIdValidators } from '../../util/validate'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ form: { alignItems: 'flex-start', display: 'flex', diff --git a/plugins/sentry-backend/README.md b/plugins/sentry-backend/README.md index 412306dc8b..efd4c1b472 100644 --- a/plugins/sentry-backend/README.md +++ b/plugins/sentry-backend/README.md @@ -1,3 +1,3 @@ # sentry-backend -Simple plugin forwarding requests to [Sentry](https://sentry.io) API. +Simple plugin forwarding requests to [Sentry](https://sentry.io) API. diff --git a/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx b/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx index 408014b790..617bdb3ce9 100644 --- a/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx +++ b/plugins/sentry/src/components/ErrorCell/ErrorCell.tsx @@ -22,7 +22,7 @@ import { BackstageTheme } from '@backstage/theme'; function stripText(text: string, maxLength: number) { return text.length > maxLength ? `${text.substr(0, maxLength)}...` : text; } -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ root: { minWidth: 260, position: 'relative', diff --git a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx index 3e5ed98dba..e861c6bbe5 100644 --- a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx +++ b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx @@ -24,16 +24,16 @@ import { ErrorGraph } from '../ErrorGraph/ErrorGraph'; const columns: TableColumn[] = [ { title: 'Error', - render: (data) => , + render: data => , }, { title: 'Graph', - render: (data) => , + render: data => , }, { title: 'First seen', field: 'firstSeen', - render: (data) => { + render: data => { const { firstSeen } = data as SentryIssue; return format(firstSeen); }, @@ -41,7 +41,7 @@ const columns: TableColumn[] = [ { title: 'Last seen', field: 'lastSeen', - render: (data) => { + render: data => { const { lastSeen } = data as SentryIssue; return format(lastSeen); }, diff --git a/plugins/sentry/src/data/mock-api.ts b/plugins/sentry/src/data/mock-api.ts index e26cce1762..215e9f7734 100644 --- a/plugins/sentry/src/data/mock-api.ts +++ b/plugins/sentry/src/data/mock-api.ts @@ -33,7 +33,7 @@ function getMockIssues(number: number): SentryIssue[] { } export class MockSentryApi implements SentryApi { fetchIssues(): Promise { - return new Promise((resolve) => { + return new Promise(resolve => { setTimeout(() => resolve(getMockIssues(14)), 800); }); } diff --git a/plugins/tech-radar/src/components/Radar/Radar.jsx b/plugins/tech-radar/src/components/Radar/Radar.jsx index ee956bfb89..11f763f465 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.jsx +++ b/plugins/tech-radar/src/components/Radar/Radar.jsx @@ -105,14 +105,14 @@ export default class Radar extends React.Component { static adjustEntries(entries, activeEntry, quadrants, rings, radius) { let seed = 42; entries.forEach((entry, idx) => { - const quadrant = quadrants.find((q) => { + const quadrant = quadrants.find(q => { const match = typeof entry.quadrant === 'object' ? entry.quadrant.id : entry.quadrant; return q.id === match; }); - const ring = rings.find((r) => { + const ring = rings.find(r => { const match = typeof entry.ring === 'object' ? entry.ring.id : entry.ring; return r.id === match; @@ -190,7 +190,7 @@ export default class Radar extends React.Component { return ( { + ref={node => { this.node = node; }} width={width} @@ -205,7 +205,7 @@ export default class Radar extends React.Component { quadrants={quadrants} rings={rings} activeEntry={activeEntry} - onEntryMouseEnter={(entry) => this._setActiveEntry(entry)} + onEntryMouseEnter={entry => this._setActiveEntry(entry)} onEntryMouseLeave={() => this._clearActiveEntry()} /> diff --git a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.jsx b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.jsx index 83487d637e..072f80ca20 100644 --- a/plugins/tech-radar/src/components/RadarBubble/RadarBubble.jsx +++ b/plugins/tech-radar/src/components/RadarBubble/RadarBubble.jsx @@ -49,16 +49,16 @@ class RadarBubble extends React.PureComponent { this._updatePosition(); } - _setRect = (rect) => { + _setRect = rect => { this.rect = rect; }; - _setNode = (node) => { + _setNode = node => { this.node = node; }; - _setText = (text) => { + _setText = text => { this.text = text; }; - _setPath = (path) => { + _setPath = path => { this.path = path; }; diff --git a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.jsx b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.jsx index f48a8e9514..ce6c046242 100644 --- a/plugins/tech-radar/src/components/RadarGrid/RadarGrid.jsx +++ b/plugins/tech-radar/src/components/RadarGrid/RadarGrid.jsx @@ -84,7 +84,7 @@ class RadarGrid extends React.PureComponent { />, ]; - const ringNodes = rings.map((r) => r.outerRadius).map(makeRingNode); + const ringNodes = rings.map(r => r.outerRadius).map(makeRingNode); return axisNodes.concat(ringNodes); } diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.jsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.jsx index 5fb7b5a1ba..674dd34cbb 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.jsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.jsx @@ -88,7 +88,7 @@ class RadarLegend extends React.PureComponent {

{quadrant.name}

- {rings.map((ring) => + {rings.map(ring => RadarLegend._renderRing( ring, RadarLegend._getSegment(segments, quadrant, ring), @@ -117,7 +117,7 @@ class RadarLegend extends React.PureComponent {

(empty)

) : (
    - {entries.map((entry) => { + {entries.map(entry => { let node = {entry.title}; if (entry.url) { @@ -175,7 +175,7 @@ class RadarLegend extends React.PureComponent { return ( - {quadrants.map((quadrant) => + {quadrants.map(quadrant => RadarLegend._renderQuadrant( segments, quadrant, diff --git a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.jsx b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.jsx index afa4c4cd95..b93d20cbff 100644 --- a/plugins/tech-radar/src/components/RadarPlot/RadarPlot.jsx +++ b/plugins/tech-radar/src/components/RadarPlot/RadarPlot.jsx @@ -46,16 +46,16 @@ export default class RadarPlot extends React.PureComponent { rings={rings} entries={entries} onEntryMouseEnter={ - onEntryMouseEnter && ((entry) => onEntryMouseEnter(entry)) + onEntryMouseEnter && (entry => onEntryMouseEnter(entry)) } onEntryMouseLeave={ - onEntryMouseLeave && ((entry) => onEntryMouseLeave(entry)) + onEntryMouseLeave && (entry => onEntryMouseLeave(entry)) } /> - {entries.map((entry) => ( + {entries.map(entry => ( Date: Wed, 27 May 2020 10:46:26 +0200 Subject: [PATCH 06/23] Bump Helmet to 6.0.0 (#1019) * Bump Helmet to 6.0.0 * fix: failing tests Co-authored-by: Nikita Nek Dudnik --- packages/core/package.json | 2 +- .../ContentHeader/ContentHeader.test.tsx | 4 ++- .../layout/ContentHeader/ContentHeader.tsx | 2 +- .../core/src/layout/Header/Header.test.tsx | 4 ++- packages/core/src/layout/Header/Header.tsx | 7 ++--- yarn.lock | 30 +++++++++---------- 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index b1800f5db3..00e80e8e0b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -48,7 +48,7 @@ "react": "^16.12.0", "react-addons-text-content": "0.0.4", "react-dom": "^16.12.0", - "react-helmet": "5.2.1", + "react-helmet": "6.0.0", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-sparklines": "^1.7.0", diff --git a/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx b/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx index 6f5a1b24cc..703c27e32c 100644 --- a/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx +++ b/packages/core/src/layout/ContentHeader/ContentHeader.test.tsx @@ -20,7 +20,9 @@ import ContentHeader from './ContentHeader'; import { wrapInThemedTestApp } from '@backstage/test-utils'; jest.mock('react-helmet', () => { - return ({ defaultTitle }: any) =>
    defaultTitle: {defaultTitle}
    ; + return { + Helmet: ({ defaultTitle }: any) =>
    defaultTitle: {defaultTitle}
    , + }; }); describe('', () => { diff --git a/packages/core/src/layout/ContentHeader/ContentHeader.tsx b/packages/core/src/layout/ContentHeader/ContentHeader.tsx index 409cfe6b90..1a51d5d19b 100644 --- a/packages/core/src/layout/ContentHeader/ContentHeader.tsx +++ b/packages/core/src/layout/ContentHeader/ContentHeader.tsx @@ -20,7 +20,7 @@ import React, { ComponentType, Fragment, FC } from 'react'; import { Typography, makeStyles } from '@material-ui/core'; -import Helmet from 'react-helmet'; +import { Helmet } from 'react-helmet'; const useStyles = makeStyles(theme => ({ container: { diff --git a/packages/core/src/layout/Header/Header.test.tsx b/packages/core/src/layout/Header/Header.test.tsx index 85b15e33aa..afedf2ad35 100644 --- a/packages/core/src/layout/Header/Header.test.tsx +++ b/packages/core/src/layout/Header/Header.test.tsx @@ -20,7 +20,9 @@ import { wrapInThemedTestApp } from '@backstage/test-utils'; import Header from './Header'; jest.mock('react-helmet', () => { - return ({ defaultTitle }: any) =>
    defaultTitle: {defaultTitle}
    ; + return { + Helmet: ({ defaultTitle }: any) =>
    defaultTitle: {defaultTitle}
    , + }; }); describe('
    ', () => { diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index ab3d0080e4..9fc4807733 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -15,15 +15,14 @@ */ import React, { Fragment, ReactNode, CSSProperties, FC } from 'react'; -import Helmet from 'react-helmet'; +import { Helmet } from 'react-helmet'; import { Typography, Tooltip, makeStyles } from '@material-ui/core'; import { BackstageTheme } from '@backstage/theme'; import { Theme } from '../Page/Page'; -// import { Link } from 'shared/components'; import Waves from './Waves'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ header: { gridArea: 'pageHeader', padding: theme.spacing(3), @@ -175,7 +174,7 @@ export const Header: FC = ({ - {(theme) => ( + {theme => (
    diff --git a/yarn.lock b/yarn.lock index 772229aa98..55255c89d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16892,7 +16892,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -17298,7 +17298,7 @@ react-error-overlay@^6.0.3, react-error-overlay@^6.0.7: resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108" integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA== -react-fast-compare@^2.0.2, react-fast-compare@^2.0.4: +react-fast-compare@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== @@ -17326,15 +17326,15 @@ react-helmet-async@^1.0.2: react-fast-compare "^2.0.4" shallowequal "^1.1.0" -react-helmet@5.2.1: - version "5.2.1" - resolved "https://registry.npmjs.org/react-helmet/-/react-helmet-5.2.1.tgz#16a7192fdd09951f8e0fe22ffccbf9bb3e591ffa" - integrity sha512-CnwD822LU8NDBnjCpZ4ySh8L6HYyngViTZLfBBb3NjtrpN8m49clH8hidHouq20I51Y6TpCTISCBbqiY5GamwA== +react-helmet@6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/react-helmet/-/react-helmet-6.0.0.tgz#fcb93ebaca3ba562a686eb2f1f9d46093d83b5f8" + integrity sha512-My6S4sa0uHN/IuVUn0HFmasW5xj9clTkB9qmMngscVycQ5vVG51Qp44BEvLJ4lixupTwDlU9qX1/sCrMN4AEPg== dependencies: object-assign "^4.1.1" - prop-types "^15.5.4" - react-fast-compare "^2.0.2" - react-side-effect "^1.1.0" + prop-types "^15.7.2" + react-fast-compare "^2.0.4" + react-side-effect "^2.1.0" react-hook-form@^5.7.2: version "5.7.2" @@ -17471,12 +17471,10 @@ react-router@5.2.0, react-router@^5.1.2, react-router@^5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-side-effect@^1.1.0: - version "1.2.0" - resolved "https://registry.npmjs.org/react-side-effect/-/react-side-effect-1.2.0.tgz#0e940c78faba0c73b9b0eba9cd3dda8dfb7e7dae" - integrity sha512-v1ht1aHg5k/thv56DRcjw+WtojuuDHFUgGfc+bFHOWsF4ZK6C2V57DO0Or0GPsg6+LSTE0M6Ry/gfzhzSwbc5w== - dependencies: - shallowequal "^1.0.1" +react-side-effect@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.0.tgz#1ce4a8b4445168c487ed24dab886421f74d380d3" + integrity sha512-IgmcegOSi5SNX+2Snh1vqmF0Vg/CbkycU9XZbOHJlZ6kMzTmi3yc254oB1WCkgA7OQtIAoLmcSFuHTc/tlcqXg== react-sizeme@^2.6.7: version "2.6.12" @@ -18742,7 +18740,7 @@ shallow-equal@^1.1.0: resolved "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA== -shallowequal@^1.0.1, shallowequal@^1.1.0: +shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== From aeffe49ad3d9f39df7146ebe11b0c14acdce338b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Wed, 27 May 2020 10:53:59 +0200 Subject: [PATCH 07/23] Add Stories for Header variants (#1027) * Add Stories for Header variants * Set service type --- .../core/src/layout/Header/Header.stories.tsx | 94 +++++++++++++++++++ packages/core/src/layout/Header/Header.tsx | 2 +- .../core/src/layout/Page/PageThemeProvider.ts | 15 +++ .../ComponentPage/ComponentPage.tsx | 4 +- .../components/PluginHeader/PluginHeader.tsx | 2 +- 5 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 packages/core/src/layout/Header/Header.stories.tsx diff --git a/packages/core/src/layout/Header/Header.stories.tsx b/packages/core/src/layout/Header/Header.stories.tsx new file mode 100644 index 0000000000..e1198d7b65 --- /dev/null +++ b/packages/core/src/layout/Header/Header.stories.tsx @@ -0,0 +1,94 @@ +/* + * 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 Header from '.'; +import HeaderLabel from '../HeaderLabel'; +import Page, { pageTheme } from '../Page'; + +export default { + title: 'Header', + component: Header, +}; + +const labels = ( + <> + + + + +); + +export const Home = () => ( + +
    + {labels} +
    +
    +); + +export const HomeWithSubtitle = () => ( +
    + {labels} +
    +); + +export const Tool = () => ( + +
    + {labels} +
    +
    +); + +export const Service = () => ( + +
    + {labels} +
    +
    +); + +export const Website = () => ( + +
    + {labels} +
    +
    +); + +export const Library = () => ( + +
    + {labels} +
    +
    +); + +export const App = () => ( + +
    + {labels} +
    +
    +); + +export const Other = () => ( + +
    + {labels} +
    +
    +); diff --git a/packages/core/src/layout/Header/Header.tsx b/packages/core/src/layout/Header/Header.tsx index 9fc4807733..47fcc324a7 100644 --- a/packages/core/src/layout/Header/Header.tsx +++ b/packages/core/src/layout/Header/Header.tsx @@ -148,7 +148,7 @@ const SubtitleFragment: FC = ({ classes, subtitle }) => { } return ( - + {subtitle} ); diff --git a/packages/core/src/layout/Page/PageThemeProvider.ts b/packages/core/src/layout/Page/PageThemeProvider.ts index a767033029..9c31fcb4ac 100644 --- a/packages/core/src/layout/Page/PageThemeProvider.ts +++ b/packages/core/src/layout/Page/PageThemeProvider.ts @@ -89,4 +89,19 @@ export const pageTheme: Record = { tool: { gradient: gradients.purpleBlue, }, + service: { + gradient: gradients.green, + }, + website: { + gradient: gradients.purple, + }, + library: { + gradient: gradients.sunset, + }, + other: { + gradient: gradients.brown, + }, + app: { + gradient: gradients.redOrange, + }, }; diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx index 0cf7017173..a1e33597a2 100644 --- a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx +++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx @@ -83,8 +83,8 @@ const ComponentPage: FC = ({ }; return ( - -
    + +
    {confirmationDialogOpen && catalogRequest.value && ( diff --git a/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx b/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx index 4bab4b5921..9f94cff480 100644 --- a/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx +++ b/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx @@ -22,7 +22,7 @@ import SettingsIcon from '@material-ui/icons/Settings'; import { useSettings } from '../../state'; export type Props = { title?: string }; -export const PluginHeader: FC = ({ title = 'Circle CI' }) => { +export const PluginHeader: FC = ({ title = 'CircleCI' }) => { const [, { showSettings }] = useSettings(); const location = useLocation(); const notRoot = !location.pathname.match(/\/circleci\/?$/); From 3a27313f78ed7812686be89a2616580a6e88a0af Mon Sep 17 00:00:00 2001 From: mutugiii Date: Wed, 27 May 2020 12:06:47 +0300 Subject: [PATCH 08/23] Fix app entry point with extra comma (Fixes #1028) --- packages/app/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index b0ff8b14c7..56faf4402b 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -40,7 +40,7 @@ const App: FC<{}> = () => ( - , + From 1a6a72e3d5abacb8f9ad1a6836204bda556c1a5e Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 27 May 2020 11:07:28 +0200 Subject: [PATCH 09/23] bug: removing the extraneous comma --- packages/app/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index b0ff8b14c7..56faf4402b 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -40,7 +40,7 @@ const App: FC<{}> = () => ( - , + From e17ff776c12c58640008264720cab596d8e084f8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 27 May 2020 02:00:18 +0200 Subject: [PATCH 10/23] packages/core: move api dir into separate core-api package --- packages/core-api/.eslintrc.js | 8 +++ packages/core-api/README.md | 22 ++++++ packages/core-api/package.json | 71 +++++++++++++++++++ .../src}/apis/ApiAggregator.test.ts | 0 .../src}/apis/ApiAggregator.ts | 0 .../src}/apis/ApiProvider.test.tsx | 0 .../api => core-api/src}/apis/ApiProvider.tsx | 0 .../api => core-api/src}/apis/ApiRef.test.ts | 0 .../src/api => core-api/src}/apis/ApiRef.ts | 0 .../src}/apis/ApiRegistry.test.ts | 0 .../api => core-api/src}/apis/ApiRegistry.ts | 0 .../src}/apis/ApiTestRegistry.test.ts | 0 .../src}/apis/ApiTestRegistry.ts | 0 .../src}/apis/definitions/AlertApi.ts | 0 .../src}/apis/definitions/AppThemeApi.ts | 0 .../src}/apis/definitions/ErrorApi.ts | 0 .../src}/apis/definitions/FeatureFlagsApi.ts | 0 .../src}/apis/definitions/OAuthRequestApi.ts | 2 +- .../src}/apis/definitions/auth.ts | 0 .../src}/apis/definitions/index.ts | 0 .../src/api => core-api/src}/apis/helpers.ts | 0 .../AlertApi/AlertApiForwarder.ts | 4 +- .../apis/implementations/AlertApi/index.ts | 0 .../AppThemeApi/AppThemeSelector.test.ts | 0 .../AppThemeApi/AppThemeSelector.ts | 2 +- .../apis/implementations/AppThemeApi/index.ts | 0 .../implementations/ErrorApi/ErrorAlerter.ts | 2 +- .../ErrorApi/ErrorApiForwarder.ts | 4 +- .../apis/implementations/ErrorApi/index.ts | 0 .../OAuthRequestApi/MockOAuthApi.test.ts | 0 .../OAuthRequestApi/MockOAuthApi.ts | 0 .../OAuthPendingRequests.test.ts | 0 .../OAuthRequestApi/OAuthPendingRequests.ts | 2 +- .../OAuthRequestManager.test.ts | 0 .../OAuthRequestApi/OAuthRequestManager.ts | 2 +- .../implementations/OAuthRequestApi/index.ts | 0 .../auth/google/GoogleAuth.test.ts | 0 .../implementations/auth/google/GoogleAuth.ts | 6 +- .../apis/implementations/auth/google/index.ts | 0 .../apis/implementations/auth/google/types.ts | 0 .../src}/apis/implementations/auth/index.ts | 0 .../src}/apis/implementations/index.ts | 0 .../src/api => core-api/src}/apis/index.ts | 0 .../src/api => core-api/src}/apis/types.ts | 0 .../src/api => core-api/src}/app/App.tsx | 50 +------------ .../api => core-api/src}/app/AppContext.tsx | 0 .../src}/app/AppThemeProvider.tsx | 0 .../src}/app/FeatureFlags.test.tsx | 0 .../api => core-api/src}/app/FeatureFlags.tsx | 0 .../src/api => core-api/src}/app/index.ts | 1 - .../src/api => core-api/src}/app/types.ts | 2 +- packages/core-api/src/icons/icons.tsx | 39 ++++++++++ packages/core-api/src/icons/index.ts | 18 +++++ packages/core-api/src/icons/types.ts | 22 ++++++ packages/core-api/src/index.ts | 19 +++++ .../DefaultAuthConnector.test.ts | 2 +- .../lib/AuthConnector/DefaultAuthConnector.ts | 4 +- .../AuthConnector/MockAuthConnector.test.ts | 0 .../lib/AuthConnector/MockAuthConnector.ts | 0 .../src}/lib/AuthConnector/index.ts | 0 .../src}/lib/AuthConnector/types.ts | 0 .../AuthSessionStore.test.ts | 0 .../AuthSessionManager/AuthSessionStore.ts | 0 .../RefreshingAuthSessionManager.test.ts | 0 .../RefreshingAuthSessionManager.ts | 0 .../StaticAuthSessionManager.test.ts | 0 .../StaticAuthSessionManager.ts | 0 .../src}/lib/AuthSessionManager/common.ts | 0 .../src}/lib/AuthSessionManager/index.ts | 0 .../src}/lib/AuthSessionManager/types.ts | 0 packages/core-api/src/lib/index.ts | 20 ++++++ .../src}/lib/loginPopup.test.ts | 0 .../src}/lib/loginPopup.ts | 0 .../src}/lib/subjects.test.ts | 0 .../src}/lib/subjects.ts | 2 +- .../api => core-api/src}/plugin/Plugin.tsx | 0 .../src/api => core-api/src}/plugin/index.ts | 0 .../src/api => core-api/src}/plugin/types.ts | 0 .../lib/index.ts => core-api/src/private.ts} | 2 +- packages/core-api/src/public.ts | 22 ++++++ .../api => core-api/src}/routing/RouteRef.ts | 0 .../src/api => core-api/src}/routing/index.ts | 0 .../src/api => core-api/src}/routing/types.ts | 2 +- packages/core-api/src/setupTests.ts | 18 +++++ .../{core/src/api => core-api/src}/types.ts | 0 packages/core/package.json | 1 + packages/core/src/api/createApp.tsx | 69 ++++++++++++++++++ packages/core/src/api/index.ts | 6 +- .../components/AlertDisplay/AlertDisplay.tsx | 2 +- .../CopyTextButton/CopyTextButton.stories.tsx | 7 +- .../CopyTextButton/CopyTextButton.test.tsx | 7 +- .../CopyTextButton/CopyTextButton.tsx | 8 +-- .../LoginRequestListItem.tsx | 2 +- .../OAuthRequestDialog/OAuthRequestDialog.tsx | 2 +- packages/core/src/icons/icons.tsx | 4 +- packages/core/src/index.ts | 2 + .../src/layout/Sidebar/SidebarThemeToggle.tsx | 2 +- 97 files changed, 376 insertions(+), 84 deletions(-) create mode 100644 packages/core-api/.eslintrc.js create mode 100644 packages/core-api/README.md create mode 100644 packages/core-api/package.json rename packages/{core/src/api => core-api/src}/apis/ApiAggregator.test.ts (100%) rename packages/{core/src/api => core-api/src}/apis/ApiAggregator.ts (100%) rename packages/{core/src/api => core-api/src}/apis/ApiProvider.test.tsx (100%) rename packages/{core/src/api => core-api/src}/apis/ApiProvider.tsx (100%) rename packages/{core/src/api => core-api/src}/apis/ApiRef.test.ts (100%) rename packages/{core/src/api => core-api/src}/apis/ApiRef.ts (100%) rename packages/{core/src/api => core-api/src}/apis/ApiRegistry.test.ts (100%) rename packages/{core/src/api => core-api/src}/apis/ApiRegistry.ts (100%) rename packages/{core/src/api => core-api/src}/apis/ApiTestRegistry.test.ts (100%) rename packages/{core/src/api => core-api/src}/apis/ApiTestRegistry.ts (100%) rename packages/{core/src/api => core-api/src}/apis/definitions/AlertApi.ts (100%) rename packages/{core/src/api => core-api/src}/apis/definitions/AppThemeApi.ts (100%) rename packages/{core/src/api => core-api/src}/apis/definitions/ErrorApi.ts (100%) rename packages/{core/src/api => core-api/src}/apis/definitions/FeatureFlagsApi.ts (100%) rename packages/{core/src/api => core-api/src}/apis/definitions/OAuthRequestApi.ts (98%) rename packages/{core/src/api => core-api/src}/apis/definitions/auth.ts (100%) rename packages/{core/src/api => core-api/src}/apis/definitions/index.ts (100%) rename packages/{core/src/api => core-api/src}/apis/helpers.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/AlertApi/AlertApiForwarder.ts (91%) rename packages/{core/src/api => core-api/src}/apis/implementations/AlertApi/index.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/AppThemeApi/AppThemeSelector.test.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/AppThemeApi/AppThemeSelector.ts (97%) rename packages/{core/src/api => core-api/src}/apis/implementations/AppThemeApi/index.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/ErrorApi/ErrorAlerter.ts (94%) rename packages/{core/src/api => core-api/src}/apis/implementations/ErrorApi/ErrorApiForwarder.ts (91%) rename packages/{core/src/api => core-api/src}/apis/implementations/ErrorApi/index.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/OAuthRequestApi/MockOAuthApi.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts (98%) rename packages/{core/src/api => core-api/src}/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts (98%) rename packages/{core/src/api => core-api/src}/apis/implementations/OAuthRequestApi/index.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/auth/google/GoogleAuth.test.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/auth/google/GoogleAuth.ts (94%) rename packages/{core/src/api => core-api/src}/apis/implementations/auth/google/index.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/auth/google/types.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/auth/index.ts (100%) rename packages/{core/src/api => core-api/src}/apis/implementations/index.ts (100%) rename packages/{core/src/api => core-api/src}/apis/index.ts (100%) rename packages/{core/src/api => core-api/src}/apis/types.ts (100%) rename packages/{core/src/api => core-api/src}/app/App.tsx (80%) rename packages/{core/src/api => core-api/src}/app/AppContext.tsx (100%) rename packages/{core/src/api => core-api/src}/app/AppThemeProvider.tsx (100%) rename packages/{core/src/api => core-api/src}/app/FeatureFlags.test.tsx (100%) rename packages/{core/src/api => core-api/src}/app/FeatureFlags.tsx (100%) rename packages/{core/src/api => core-api/src}/app/index.ts (95%) rename packages/{core/src/api => core-api/src}/app/types.ts (97%) create mode 100644 packages/core-api/src/icons/icons.tsx create mode 100644 packages/core-api/src/icons/index.ts create mode 100644 packages/core-api/src/icons/types.ts create mode 100644 packages/core-api/src/index.ts rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthConnector/DefaultAuthConnector.test.ts (98%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthConnector/DefaultAuthConnector.ts (97%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthConnector/MockAuthConnector.test.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthConnector/MockAuthConnector.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthConnector/index.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthConnector/types.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthSessionManager/AuthSessionStore.test.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthSessionManager/AuthSessionStore.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthSessionManager/RefreshingAuthSessionManager.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthSessionManager/StaticAuthSessionManager.test.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthSessionManager/StaticAuthSessionManager.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthSessionManager/common.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthSessionManager/index.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/AuthSessionManager/types.ts (100%) create mode 100644 packages/core-api/src/lib/index.ts rename packages/{core/src/api/apis/implementations => core-api/src}/lib/loginPopup.test.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/loginPopup.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/subjects.test.ts (100%) rename packages/{core/src/api/apis/implementations => core-api/src}/lib/subjects.ts (99%) rename packages/{core/src/api => core-api/src}/plugin/Plugin.tsx (100%) rename packages/{core/src/api => core-api/src}/plugin/index.ts (100%) rename packages/{core/src/api => core-api/src}/plugin/types.ts (100%) rename packages/{core/src/api/apis/implementations/lib/index.ts => core-api/src/private.ts} (93%) create mode 100644 packages/core-api/src/public.ts rename packages/{core/src/api => core-api/src}/routing/RouteRef.ts (100%) rename packages/{core/src/api => core-api/src}/routing/index.ts (100%) rename packages/{core/src/api => core-api/src}/routing/types.ts (95%) create mode 100644 packages/core-api/src/setupTests.ts rename packages/{core/src/api => core-api/src}/types.ts (100%) create mode 100644 packages/core/src/api/createApp.tsx diff --git a/packages/core-api/.eslintrc.js b/packages/core-api/.eslintrc.js new file mode 100644 index 0000000000..d592a653c8 --- /dev/null +++ b/packages/core-api/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], + rules: { + // TODO: add prop types to JS and remove + 'react/prop-types': 0, + 'jest/expect-expect': 0, + }, +}; diff --git a/packages/core-api/README.md b/packages/core-api/README.md new file mode 100644 index 0000000000..5732b5ef23 --- /dev/null +++ b/packages/core-api/README.md @@ -0,0 +1,22 @@ +# @backstage/core-api + +This package provides the core API used by Backstage plugins and apps. + +## Installation + +Do not install this package directly, it is reexported by `@backstage/core`. Install that instead: + +```sh +$ npm install --save @backstage/core +``` + +or + +```sh +$ yarn add @backstage/core +``` + +## Documentation + +- [Backstage Readme](https://github.com/spotify/backstage/blob/master/README.md) +- [Backstage Documentation](https://github.com/spotify/backstage/blob/master/docs/README.md) diff --git a/packages/core-api/package.json b/packages/core-api/package.json new file mode 100644 index 0000000000..88d593dcf8 --- /dev/null +++ b/packages/core-api/package.json @@ -0,0 +1,71 @@ +{ + "name": "@backstage/core-api", + "description": "Core API used by Backstage plugins and apps", + "version": "0.1.1-alpha.6", + "private": false, + "publishConfig": { + "access": "public" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/spotify/backstage", + "directory": "packages/core" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "main": "dist/index.esm.js", + "main:src": "src/index.ts", + "types": "src/index.ts", + "scripts": { + "build": "backstage-cli plugin:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/theme": "^0.1.1-alpha.6", + "@material-ui/core": "^4.9.1", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "@types/classnames": "^2.2.9", + "@types/google-protobuf": "^3.7.2", + "@types/jest": "^25.2.2", + "@types/node": "^12.0.0", + "@types/react-helmet": "^5.0.15", + "@types/react-sparklines": "^1.7.0", + "@types/zen-observable": "^0.8.0", + "classnames": "^2.2.6", + "clsx": "^1.1.0", + "lodash": "^4.17.15", + "material-table": "^1.58.0", + "prop-types": "^15.7.2", + "rc-progress": "^3.0.0", + "react": "^16.12.0", + "react-addons-text-content": "0.0.4", + "react-dom": "^16.12.0", + "react-helmet": "5.2.1", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", + "react-sparklines": "^1.7.0", + "react-syntax-highlighter": "^12.2.1", + "react-use": "^14.2.0", + "zen-observable": "^0.8.15" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.6", + "@backstage/test-utils": "^0.1.1-alpha.6", + "@backstage/test-utils-core": "^0.1.1-alpha.6", + "@testing-library/jest-dom": "^5.7.0", + "@testing-library/react": "^9.3.2", + "@testing-library/user-event": "^10.2.4", + "jest-fetch-mock": "^3.0.3" + }, + "files": [ + "dist/**/*.{js,d.ts}" + ] +} diff --git a/packages/core/src/api/apis/ApiAggregator.test.ts b/packages/core-api/src/apis/ApiAggregator.test.ts similarity index 100% rename from packages/core/src/api/apis/ApiAggregator.test.ts rename to packages/core-api/src/apis/ApiAggregator.test.ts diff --git a/packages/core/src/api/apis/ApiAggregator.ts b/packages/core-api/src/apis/ApiAggregator.ts similarity index 100% rename from packages/core/src/api/apis/ApiAggregator.ts rename to packages/core-api/src/apis/ApiAggregator.ts diff --git a/packages/core/src/api/apis/ApiProvider.test.tsx b/packages/core-api/src/apis/ApiProvider.test.tsx similarity index 100% rename from packages/core/src/api/apis/ApiProvider.test.tsx rename to packages/core-api/src/apis/ApiProvider.test.tsx diff --git a/packages/core/src/api/apis/ApiProvider.tsx b/packages/core-api/src/apis/ApiProvider.tsx similarity index 100% rename from packages/core/src/api/apis/ApiProvider.tsx rename to packages/core-api/src/apis/ApiProvider.tsx diff --git a/packages/core/src/api/apis/ApiRef.test.ts b/packages/core-api/src/apis/ApiRef.test.ts similarity index 100% rename from packages/core/src/api/apis/ApiRef.test.ts rename to packages/core-api/src/apis/ApiRef.test.ts diff --git a/packages/core/src/api/apis/ApiRef.ts b/packages/core-api/src/apis/ApiRef.ts similarity index 100% rename from packages/core/src/api/apis/ApiRef.ts rename to packages/core-api/src/apis/ApiRef.ts diff --git a/packages/core/src/api/apis/ApiRegistry.test.ts b/packages/core-api/src/apis/ApiRegistry.test.ts similarity index 100% rename from packages/core/src/api/apis/ApiRegistry.test.ts rename to packages/core-api/src/apis/ApiRegistry.test.ts diff --git a/packages/core/src/api/apis/ApiRegistry.ts b/packages/core-api/src/apis/ApiRegistry.ts similarity index 100% rename from packages/core/src/api/apis/ApiRegistry.ts rename to packages/core-api/src/apis/ApiRegistry.ts diff --git a/packages/core/src/api/apis/ApiTestRegistry.test.ts b/packages/core-api/src/apis/ApiTestRegistry.test.ts similarity index 100% rename from packages/core/src/api/apis/ApiTestRegistry.test.ts rename to packages/core-api/src/apis/ApiTestRegistry.test.ts diff --git a/packages/core/src/api/apis/ApiTestRegistry.ts b/packages/core-api/src/apis/ApiTestRegistry.ts similarity index 100% rename from packages/core/src/api/apis/ApiTestRegistry.ts rename to packages/core-api/src/apis/ApiTestRegistry.ts diff --git a/packages/core/src/api/apis/definitions/AlertApi.ts b/packages/core-api/src/apis/definitions/AlertApi.ts similarity index 100% rename from packages/core/src/api/apis/definitions/AlertApi.ts rename to packages/core-api/src/apis/definitions/AlertApi.ts diff --git a/packages/core/src/api/apis/definitions/AppThemeApi.ts b/packages/core-api/src/apis/definitions/AppThemeApi.ts similarity index 100% rename from packages/core/src/api/apis/definitions/AppThemeApi.ts rename to packages/core-api/src/apis/definitions/AppThemeApi.ts diff --git a/packages/core/src/api/apis/definitions/ErrorApi.ts b/packages/core-api/src/apis/definitions/ErrorApi.ts similarity index 100% rename from packages/core/src/api/apis/definitions/ErrorApi.ts rename to packages/core-api/src/apis/definitions/ErrorApi.ts diff --git a/packages/core/src/api/apis/definitions/FeatureFlagsApi.ts b/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts similarity index 100% rename from packages/core/src/api/apis/definitions/FeatureFlagsApi.ts rename to packages/core-api/src/apis/definitions/FeatureFlagsApi.ts diff --git a/packages/core/src/api/apis/definitions/OAuthRequestApi.ts b/packages/core-api/src/apis/definitions/OAuthRequestApi.ts similarity index 98% rename from packages/core/src/api/apis/definitions/OAuthRequestApi.ts rename to packages/core-api/src/apis/definitions/OAuthRequestApi.ts index 5a0f399a6d..6fefbd1b9c 100644 --- a/packages/core/src/api/apis/definitions/OAuthRequestApi.ts +++ b/packages/core-api/src/apis/definitions/OAuthRequestApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { IconComponent } from '../../../icons'; +import { IconComponent } from '../../icons'; import { Observable } from '../../types'; import { createApiRef } from '../ApiRef'; diff --git a/packages/core/src/api/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts similarity index 100% rename from packages/core/src/api/apis/definitions/auth.ts rename to packages/core-api/src/apis/definitions/auth.ts diff --git a/packages/core/src/api/apis/definitions/index.ts b/packages/core-api/src/apis/definitions/index.ts similarity index 100% rename from packages/core/src/api/apis/definitions/index.ts rename to packages/core-api/src/apis/definitions/index.ts diff --git a/packages/core/src/api/apis/helpers.ts b/packages/core-api/src/apis/helpers.ts similarity index 100% rename from packages/core/src/api/apis/helpers.ts rename to packages/core-api/src/apis/helpers.ts diff --git a/packages/core/src/api/apis/implementations/AlertApi/AlertApiForwarder.ts b/packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts similarity index 91% rename from packages/core/src/api/apis/implementations/AlertApi/AlertApiForwarder.ts rename to packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts index 901d3b57cc..91a606059d 100644 --- a/packages/core/src/api/apis/implementations/AlertApi/AlertApiForwarder.ts +++ b/packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AlertApi, AlertMessage } from '../../../..'; -import { PublishSubject } from '../lib'; +import { AlertApi, AlertMessage } from '../..'; +import { PublishSubject } from '../../../lib'; import { Observable } from '../../../types'; /** diff --git a/packages/core/src/api/apis/implementations/AlertApi/index.ts b/packages/core-api/src/apis/implementations/AlertApi/index.ts similarity index 100% rename from packages/core/src/api/apis/implementations/AlertApi/index.ts rename to packages/core-api/src/apis/implementations/AlertApi/index.ts diff --git a/packages/core/src/api/apis/implementations/AppThemeApi/AppThemeSelector.test.ts b/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/AppThemeApi/AppThemeSelector.test.ts rename to packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts diff --git a/packages/core/src/api/apis/implementations/AppThemeApi/AppThemeSelector.ts b/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts similarity index 97% rename from packages/core/src/api/apis/implementations/AppThemeApi/AppThemeSelector.ts rename to packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts index 0f554ed7f1..43b52a254b 100644 --- a/packages/core/src/api/apis/implementations/AppThemeApi/AppThemeSelector.ts +++ b/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts @@ -15,7 +15,7 @@ */ import { AppThemeApi, AppTheme } from '../../definitions'; -import { BehaviorSubject } from '../lib'; +import { BehaviorSubject } from '../../../lib'; import { Observable } from '../../../types'; const STORAGE_KEY = 'theme'; diff --git a/packages/core/src/api/apis/implementations/AppThemeApi/index.ts b/packages/core-api/src/apis/implementations/AppThemeApi/index.ts similarity index 100% rename from packages/core/src/api/apis/implementations/AppThemeApi/index.ts rename to packages/core-api/src/apis/implementations/AppThemeApi/index.ts diff --git a/packages/core/src/api/apis/implementations/ErrorApi/ErrorAlerter.ts b/packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts similarity index 94% rename from packages/core/src/api/apis/implementations/ErrorApi/ErrorAlerter.ts rename to packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts index 903463d7de..162b28f334 100644 --- a/packages/core/src/api/apis/implementations/ErrorApi/ErrorAlerter.ts +++ b/packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ErrorApi, ErrorContext, AlertApi } from '../../../..'; +import { ErrorApi, ErrorContext, AlertApi } from '../..'; /** * Decorates an ErrorApi by also forwarding error messages diff --git a/packages/core/src/api/apis/implementations/ErrorApi/ErrorApiForwarder.ts b/packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts similarity index 91% rename from packages/core/src/api/apis/implementations/ErrorApi/ErrorApiForwarder.ts rename to packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts index 6729050401..2ebf9db7a8 100644 --- a/packages/core/src/api/apis/implementations/ErrorApi/ErrorApiForwarder.ts +++ b/packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ErrorApi, ErrorContext } from '../../../..'; -import { PublishSubject } from '../lib'; +import { ErrorApi, ErrorContext } from '../..'; +import { PublishSubject } from '../../../lib'; import { Observable } from '../../../types'; /** diff --git a/packages/core/src/api/apis/implementations/ErrorApi/index.ts b/packages/core-api/src/apis/implementations/ErrorApi/index.ts similarity index 100% rename from packages/core/src/api/apis/implementations/ErrorApi/index.ts rename to packages/core-api/src/apis/implementations/ErrorApi/index.ts diff --git a/packages/core/src/api/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts rename to packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts diff --git a/packages/core/src/api/apis/implementations/OAuthRequestApi/MockOAuthApi.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts similarity index 100% rename from packages/core/src/api/apis/implementations/OAuthRequestApi/MockOAuthApi.ts rename to packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts diff --git a/packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts rename to packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts diff --git a/packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts similarity index 98% rename from packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts rename to packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts index aa5609b1e9..6287f35015 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts +++ b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { BehaviorSubject } from '../lib'; +import { BehaviorSubject } from '../../../lib'; import { Observable } from '../../../types'; type RequestQueueEntry = { diff --git a/packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts rename to packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts diff --git a/packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts similarity index 98% rename from packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts rename to packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts index 1168d6abd4..23e9e1ddc4 100644 --- a/packages/core/src/api/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts +++ b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts @@ -21,7 +21,7 @@ import { AuthRequesterOptions, } from '../../definitions'; import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests'; -import { BehaviorSubject } from '../lib'; +import { BehaviorSubject } from '../../../lib'; import { Observable } from '../../../types'; /** diff --git a/packages/core/src/api/apis/implementations/OAuthRequestApi/index.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/index.ts similarity index 100% rename from packages/core/src/api/apis/implementations/OAuthRequestApi/index.ts rename to packages/core-api/src/apis/implementations/OAuthRequestApi/index.ts diff --git a/packages/core/src/api/apis/implementations/auth/google/GoogleAuth.test.ts b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/auth/google/GoogleAuth.test.ts rename to packages/core-api/src/apis/implementations/auth/google/GoogleAuth.test.ts diff --git a/packages/core/src/api/apis/implementations/auth/google/GoogleAuth.ts b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts similarity index 94% rename from packages/core/src/api/apis/implementations/auth/google/GoogleAuth.ts rename to packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts index dbcf53dbe0..e52e8f0397 100644 --- a/packages/core/src/api/apis/implementations/auth/google/GoogleAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts @@ -15,7 +15,7 @@ */ import GoogleIcon from '@material-ui/icons/AcUnit'; -import { DefaultAuthConnector } from '../../lib/AuthConnector'; +import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; import { GoogleSession } from './types'; import { OAuthApi, @@ -23,8 +23,8 @@ import { IdTokenOptions, } from '../../../definitions/auth'; import { OAuthRequestApi, AuthProvider } from '../../../definitions'; -import { SessionManager } from '../../lib/AuthSessionManager/types'; -import { RefreshingAuthSessionManager } from '../../lib/AuthSessionManager'; +import { SessionManager } from '../../../../lib/AuthSessionManager/types'; +import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; type CreateOptions = { // TODO(Rugvip): These two should be grabbed from global config when available, they're not unique to GoogleAuth diff --git a/packages/core/src/api/apis/implementations/auth/google/index.ts b/packages/core-api/src/apis/implementations/auth/google/index.ts similarity index 100% rename from packages/core/src/api/apis/implementations/auth/google/index.ts rename to packages/core-api/src/apis/implementations/auth/google/index.ts diff --git a/packages/core/src/api/apis/implementations/auth/google/types.ts b/packages/core-api/src/apis/implementations/auth/google/types.ts similarity index 100% rename from packages/core/src/api/apis/implementations/auth/google/types.ts rename to packages/core-api/src/apis/implementations/auth/google/types.ts diff --git a/packages/core/src/api/apis/implementations/auth/index.ts b/packages/core-api/src/apis/implementations/auth/index.ts similarity index 100% rename from packages/core/src/api/apis/implementations/auth/index.ts rename to packages/core-api/src/apis/implementations/auth/index.ts diff --git a/packages/core/src/api/apis/implementations/index.ts b/packages/core-api/src/apis/implementations/index.ts similarity index 100% rename from packages/core/src/api/apis/implementations/index.ts rename to packages/core-api/src/apis/implementations/index.ts diff --git a/packages/core/src/api/apis/index.ts b/packages/core-api/src/apis/index.ts similarity index 100% rename from packages/core/src/api/apis/index.ts rename to packages/core-api/src/apis/index.ts diff --git a/packages/core/src/api/apis/types.ts b/packages/core-api/src/apis/types.ts similarity index 100% rename from packages/core/src/api/apis/types.ts rename to packages/core-api/src/apis/types.ts diff --git a/packages/core/src/api/app/App.tsx b/packages/core-api/src/app/App.tsx similarity index 80% rename from packages/core/src/api/app/App.tsx rename to packages/core-api/src/app/App.tsx index afdb6ffc58..844c38ecce 100644 --- a/packages/core/src/api/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -17,19 +17,13 @@ import React, { ComponentType, FC } from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; import { AppContextProvider } from './AppContext'; -import { BackstageApp, AppOptions, AppComponents } from './types'; +import { BackstageApp, AppComponents } from './types'; import { BackstagePlugin } from '../plugin'; import { FeatureFlagsRegistryItem } from './FeatureFlags'; import { featureFlagsApiRef } from '../apis/definitions'; -import ErrorPage from '../../layout/ErrorPage'; import { AppThemeProvider } from './AppThemeProvider'; -import { - IconComponent, - SystemIcons, - SystemIconKey, - defaultSystemIcons, -} from '../../icons'; +import { IconComponent, SystemIcons, SystemIconKey } from '../icons'; import { ApiHolder, ApiProvider, @@ -38,7 +32,6 @@ import { AppThemeSelector, appThemeApiRef, } from '../apis'; -import { lightTheme, darkTheme } from '@backstage/theme'; import { ApiAggregator } from '../apis/ApiAggregator'; type FullAppOptions = { @@ -49,7 +42,7 @@ type FullAppOptions = { themes: AppTheme[]; }; -class AppImpl implements BackstageApp { +export class PrivateAppImpl implements BackstageApp { private readonly apis: ApiHolder; private readonly icons: SystemIcons; private readonly plugins: BackstagePlugin[]; @@ -175,40 +168,3 @@ class AppImpl implements BackstageApp { } } } - -/** - * Creates a new Backstage App. - */ -export function createApp(options?: AppOptions) { - const DefaultNotFoundPage = () => ( - - ); - - const apis = options?.apis ?? ApiRegistry.from([]); - const icons = { ...defaultSystemIcons, ...options?.icons }; - const plugins = options?.plugins ?? []; - const components = { - NotFoundErrorPage: DefaultNotFoundPage, - ...options?.components, - }; - const themes = options?.themes ?? [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - }, - { - id: 'dark', - title: 'Dark Theme', - variant: 'dark', - theme: darkTheme, - }, - ]; - - const app = new AppImpl({ apis, icons, plugins, components, themes }); - - app.verify(); - - return app; -} diff --git a/packages/core/src/api/app/AppContext.tsx b/packages/core-api/src/app/AppContext.tsx similarity index 100% rename from packages/core/src/api/app/AppContext.tsx rename to packages/core-api/src/app/AppContext.tsx diff --git a/packages/core/src/api/app/AppThemeProvider.tsx b/packages/core-api/src/app/AppThemeProvider.tsx similarity index 100% rename from packages/core/src/api/app/AppThemeProvider.tsx rename to packages/core-api/src/app/AppThemeProvider.tsx diff --git a/packages/core/src/api/app/FeatureFlags.test.tsx b/packages/core-api/src/app/FeatureFlags.test.tsx similarity index 100% rename from packages/core/src/api/app/FeatureFlags.test.tsx rename to packages/core-api/src/app/FeatureFlags.test.tsx diff --git a/packages/core/src/api/app/FeatureFlags.tsx b/packages/core-api/src/app/FeatureFlags.tsx similarity index 100% rename from packages/core/src/api/app/FeatureFlags.tsx rename to packages/core-api/src/app/FeatureFlags.tsx diff --git a/packages/core/src/api/app/index.ts b/packages/core-api/src/app/index.ts similarity index 95% rename from packages/core/src/api/app/index.ts rename to packages/core-api/src/app/index.ts index c40fa9e9f8..003b71e353 100644 --- a/packages/core/src/api/app/index.ts +++ b/packages/core-api/src/app/index.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -export { createApp } from './App'; export { FeatureFlags } from './FeatureFlags'; export { useApp } from './AppContext'; export * from './types'; diff --git a/packages/core/src/api/app/types.ts b/packages/core-api/src/app/types.ts similarity index 97% rename from packages/core/src/api/app/types.ts rename to packages/core-api/src/app/types.ts index 75a750ce6e..ea3a812557 100644 --- a/packages/core/src/api/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -15,7 +15,7 @@ */ import { ComponentType } from 'react'; -import { IconComponent, SystemIconKey, SystemIcons } from '../../icons'; +import { IconComponent, SystemIconKey, SystemIcons } from '../icons'; import { BackstagePlugin } from '../plugin'; import { ApiHolder } from '../apis'; import { AppTheme } from '../apis/definitions'; diff --git a/packages/core-api/src/icons/icons.tsx b/packages/core-api/src/icons/icons.tsx new file mode 100644 index 0000000000..488973b664 --- /dev/null +++ b/packages/core-api/src/icons/icons.tsx @@ -0,0 +1,39 @@ +/* + * 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 { SvgIconProps } from '@material-ui/core'; +import PeopleIcon from '@material-ui/icons/People'; +import PersonIcon from '@material-ui/icons/Person'; +import React, { FC } from 'react'; +import { useApp } from '../app/AppContext'; +import { IconComponent, SystemIconKey, SystemIcons } from './types'; + +export const defaultSystemIcons: SystemIcons = { + user: PersonIcon, + group: PeopleIcon, +}; + +const overridableSystemIcon = (key: SystemIconKey): IconComponent => { + const Component: FC = props => { + const app = useApp(); + const Icon = app.getSystemIcon(key); + return ; + }; + return Component; +}; + +export const UserIcon = overridableSystemIcon('user'); +export const GroupIcon = overridableSystemIcon('group'); diff --git a/packages/core-api/src/icons/index.ts b/packages/core-api/src/icons/index.ts new file mode 100644 index 0000000000..4c97d27176 --- /dev/null +++ b/packages/core-api/src/icons/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 * from './icons'; +export * from './types'; diff --git a/packages/core-api/src/icons/types.ts b/packages/core-api/src/icons/types.ts new file mode 100644 index 0000000000..30e0ba53b2 --- /dev/null +++ b/packages/core-api/src/icons/types.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 { ComponentType } from 'react'; +import { SvgIconProps } from '@material-ui/core'; + +export type IconComponent = ComponentType; +export type SystemIconKey = 'user' | 'group'; +export type SystemIcons = { [key in SystemIconKey]: IconComponent }; diff --git a/packages/core-api/src/index.ts b/packages/core-api/src/index.ts new file mode 100644 index 0000000000..f08e65809e --- /dev/null +++ b/packages/core-api/src/index.ts @@ -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 * from './public'; +import * as privateExports from './private'; +export default privateExports; diff --git a/packages/core/src/api/apis/implementations/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts similarity index 98% rename from packages/core/src/api/apis/implementations/lib/AuthConnector/DefaultAuthConnector.test.ts rename to packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts index d4f95f7907..9dde49aab8 100644 --- a/packages/core/src/api/apis/implementations/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -16,7 +16,7 @@ import ProviderIcon from '@material-ui/icons/AcUnit'; import { DefaultAuthConnector } from './DefaultAuthConnector'; -import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; +import MockOAuthApi from '../../apis/implementations/OAuthRequestApi/MockOAuthApi'; import * as loginPopup from '../loginPopup'; const anyFetch = fetch as any; diff --git a/packages/core/src/api/apis/implementations/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts similarity index 97% rename from packages/core/src/api/apis/implementations/lib/AuthConnector/DefaultAuthConnector.ts rename to packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts index ca8a9cd2ae..43bca21e90 100644 --- a/packages/core/src/api/apis/implementations/lib/AuthConnector/DefaultAuthConnector.ts +++ b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { AuthRequester } from '../../..'; -import { OAuthRequestApi, AuthProvider } from '../../../definitions'; +import { AuthRequester } from '../../apis'; +import { OAuthRequestApi, AuthProvider } from '../../apis/definitions'; import { showLoginPopup } from '../loginPopup'; import { AuthConnector } from './types'; diff --git a/packages/core/src/api/apis/implementations/lib/AuthConnector/MockAuthConnector.test.ts b/packages/core-api/src/lib/AuthConnector/MockAuthConnector.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthConnector/MockAuthConnector.test.ts rename to packages/core-api/src/lib/AuthConnector/MockAuthConnector.test.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthConnector/MockAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/MockAuthConnector.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthConnector/MockAuthConnector.ts rename to packages/core-api/src/lib/AuthConnector/MockAuthConnector.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthConnector/index.ts b/packages/core-api/src/lib/AuthConnector/index.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthConnector/index.ts rename to packages/core-api/src/lib/AuthConnector/index.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthConnector/types.ts b/packages/core-api/src/lib/AuthConnector/types.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthConnector/types.ts rename to packages/core-api/src/lib/AuthConnector/types.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/AuthSessionStore.test.ts b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthSessionManager/AuthSessionStore.test.ts rename to packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/AuthSessionStore.ts b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthSessionManager/AuthSessionStore.ts rename to packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts rename to packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/RefreshingAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthSessionManager/RefreshingAuthSessionManager.ts rename to packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/StaticAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthSessionManager/StaticAuthSessionManager.test.ts rename to packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/StaticAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthSessionManager/StaticAuthSessionManager.ts rename to packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/common.ts b/packages/core-api/src/lib/AuthSessionManager/common.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthSessionManager/common.ts rename to packages/core-api/src/lib/AuthSessionManager/common.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/index.ts b/packages/core-api/src/lib/AuthSessionManager/index.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthSessionManager/index.ts rename to packages/core-api/src/lib/AuthSessionManager/index.ts diff --git a/packages/core/src/api/apis/implementations/lib/AuthSessionManager/types.ts b/packages/core-api/src/lib/AuthSessionManager/types.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/AuthSessionManager/types.ts rename to packages/core-api/src/lib/AuthSessionManager/types.ts diff --git a/packages/core-api/src/lib/index.ts b/packages/core-api/src/lib/index.ts new file mode 100644 index 0000000000..10f213b50f --- /dev/null +++ b/packages/core-api/src/lib/index.ts @@ -0,0 +1,20 @@ +/* + * 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 './subjects'; +export * from './loginPopup'; +export * from './AuthConnector'; +export * from './AuthSessionManager'; diff --git a/packages/core/src/api/apis/implementations/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/loginPopup.test.ts rename to packages/core-api/src/lib/loginPopup.test.ts diff --git a/packages/core/src/api/apis/implementations/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/loginPopup.ts rename to packages/core-api/src/lib/loginPopup.ts diff --git a/packages/core/src/api/apis/implementations/lib/subjects.test.ts b/packages/core-api/src/lib/subjects.test.ts similarity index 100% rename from packages/core/src/api/apis/implementations/lib/subjects.test.ts rename to packages/core-api/src/lib/subjects.test.ts diff --git a/packages/core/src/api/apis/implementations/lib/subjects.ts b/packages/core-api/src/lib/subjects.ts similarity index 99% rename from packages/core/src/api/apis/implementations/lib/subjects.ts rename to packages/core-api/src/lib/subjects.ts index 5b424b7e55..9ebde6ebbc 100644 --- a/packages/core/src/api/apis/implementations/lib/subjects.ts +++ b/packages/core-api/src/lib/subjects.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Observable } from '../../../types'; +import { Observable } from '../types'; import ObservableImpl from 'zen-observable'; // TODO(Rugvip): These are stopgap and probably incomplete implementations of subjects. diff --git a/packages/core/src/api/plugin/Plugin.tsx b/packages/core-api/src/plugin/Plugin.tsx similarity index 100% rename from packages/core/src/api/plugin/Plugin.tsx rename to packages/core-api/src/plugin/Plugin.tsx diff --git a/packages/core/src/api/plugin/index.ts b/packages/core-api/src/plugin/index.ts similarity index 100% rename from packages/core/src/api/plugin/index.ts rename to packages/core-api/src/plugin/index.ts diff --git a/packages/core/src/api/plugin/types.ts b/packages/core-api/src/plugin/types.ts similarity index 100% rename from packages/core/src/api/plugin/types.ts rename to packages/core-api/src/plugin/types.ts diff --git a/packages/core/src/api/apis/implementations/lib/index.ts b/packages/core-api/src/private.ts similarity index 93% rename from packages/core/src/api/apis/implementations/lib/index.ts rename to packages/core-api/src/private.ts index aa2202858c..65462d8d51 100644 --- a/packages/core/src/api/apis/implementations/lib/index.ts +++ b/packages/core-api/src/private.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export * from './subjects'; +export { PrivateAppImpl } from './app/App'; diff --git a/packages/core-api/src/public.ts b/packages/core-api/src/public.ts new file mode 100644 index 0000000000..0a30936c52 --- /dev/null +++ b/packages/core-api/src/public.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. + */ + +export * from './apis'; +export * from './app'; +export * from './icons'; +export * from './plugin'; +export * from './routing'; +export * from './types'; diff --git a/packages/core/src/api/routing/RouteRef.ts b/packages/core-api/src/routing/RouteRef.ts similarity index 100% rename from packages/core/src/api/routing/RouteRef.ts rename to packages/core-api/src/routing/RouteRef.ts diff --git a/packages/core/src/api/routing/index.ts b/packages/core-api/src/routing/index.ts similarity index 100% rename from packages/core/src/api/routing/index.ts rename to packages/core-api/src/routing/index.ts diff --git a/packages/core/src/api/routing/types.ts b/packages/core-api/src/routing/types.ts similarity index 95% rename from packages/core/src/api/routing/types.ts rename to packages/core-api/src/routing/types.ts index 0b6d1c711f..491665bbcf 100644 --- a/packages/core/src/api/routing/types.ts +++ b/packages/core-api/src/routing/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { IconComponent } from '../../icons'; +import { IconComponent } from '../icons'; export type RouteRef = { path: string; diff --git a/packages/core-api/src/setupTests.ts b/packages/core-api/src/setupTests.ts new file mode 100644 index 0000000000..e34bc46f4b --- /dev/null +++ b/packages/core-api/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(); diff --git a/packages/core/src/api/types.ts b/packages/core-api/src/types.ts similarity index 100% rename from packages/core/src/api/types.ts rename to packages/core-api/src/types.ts diff --git a/packages/core/package.json b/packages/core/package.json index 00e80e8e0b..a14c1f9465 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,6 +28,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/core-api": "^0.1.1-alpha.6", "@backstage/theme": "^0.1.1-alpha.6", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", diff --git a/packages/core/src/api/createApp.tsx b/packages/core/src/api/createApp.tsx new file mode 100644 index 0000000000..d0deed667a --- /dev/null +++ b/packages/core/src/api/createApp.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import privateExports, { + AppOptions, + ApiRegistry, + defaultSystemIcons, +} from '@backstage/core-api'; + +import ErrorPage from '../layout/ErrorPage'; +import { lightTheme, darkTheme } from '@backstage/theme'; + +const { PrivateAppImpl } = privateExports; + +// createApp is defined in core, and not core-api, since we need access +// to the components inside core to provide defaults. +// The actual implementation of the app class still lives in core-api, +// as it needs to be used by dev- and test-utils. + +/** + * Creates a new Backstage App. + */ +export function createApp(options?: AppOptions) { + const DefaultNotFoundPage = () => ( + + ); + + const apis = options?.apis ?? ApiRegistry.from([]); + const icons = { ...defaultSystemIcons, ...options?.icons }; + const plugins = options?.plugins ?? []; + const components = { + NotFoundErrorPage: DefaultNotFoundPage, + ...options?.components, + }; + const themes = options?.themes ?? [ + { + id: 'light', + title: 'Light Theme', + variant: 'light', + theme: lightTheme, + }, + { + id: 'dark', + title: 'Dark Theme', + variant: 'dark', + theme: darkTheme, + }, + ]; + + const app = new PrivateAppImpl({ apis, icons, plugins, components, themes }); + + app.verify(); + + return app; +} diff --git a/packages/core/src/api/index.ts b/packages/core/src/api/index.ts index cde010293d..b8136305b0 100644 --- a/packages/core/src/api/index.ts +++ b/packages/core/src/api/index.ts @@ -14,8 +14,4 @@ * limitations under the License. */ -export * from './apis'; -export * from './app'; -export * from './routing'; -export * from './plugin'; -export * from './types'; +export { createApp } from './createApp'; diff --git a/packages/core/src/components/AlertDisplay/AlertDisplay.tsx b/packages/core/src/components/AlertDisplay/AlertDisplay.tsx index 85b41f2e0a..30940f68ac 100644 --- a/packages/core/src/components/AlertDisplay/AlertDisplay.tsx +++ b/packages/core/src/components/AlertDisplay/AlertDisplay.tsx @@ -18,7 +18,7 @@ import React, { FC, useEffect, useState } from 'react'; import { Snackbar, IconButton } from '@material-ui/core'; import CloseIcon from '@material-ui/icons/Close'; import { Alert } from '@material-ui/lab'; -import { AlertMessage, useApi, alertApiRef } from '../../api'; +import { AlertMessage, useApi, alertApiRef } from '@backstage/core-api'; type Props = {}; diff --git a/packages/core/src/components/CopyTextButton/CopyTextButton.stories.tsx b/packages/core/src/components/CopyTextButton/CopyTextButton.stories.tsx index 80c11f6b4a..3dc5854ab3 100644 --- a/packages/core/src/components/CopyTextButton/CopyTextButton.stories.tsx +++ b/packages/core/src/components/CopyTextButton/CopyTextButton.stories.tsx @@ -16,7 +16,12 @@ import React from 'react'; import CopyTextButton from '.'; -import { ApiProvider, errorApiRef, ApiRegistry, ErrorApi } from '../../api'; +import { + ApiProvider, + errorApiRef, + ApiRegistry, + ErrorApi, +} from '@backstage/core-api'; export default { title: 'CopyTextButton', diff --git a/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx b/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx index 7bc59d1f48..e0f7271014 100644 --- a/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx +++ b/packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx @@ -18,7 +18,12 @@ import React from 'react'; import { render } from '@testing-library/react'; import { wrapInThemedTestApp } from '@backstage/test-utils'; import CopyTextButton from './CopyTextButton'; -import { ApiRegistry, errorApiRef, ApiProvider, ErrorApi } from '../../api'; +import { + ApiRegistry, + errorApiRef, + ApiProvider, + ErrorApi, +} from '@backstage/core-api'; jest.mock('popper.js', () => { const PopperJS = jest.requireActual('popper.js'); diff --git a/packages/core/src/components/CopyTextButton/CopyTextButton.tsx b/packages/core/src/components/CopyTextButton/CopyTextButton.tsx index 5af62f4380..1c5028fa9c 100644 --- a/packages/core/src/components/CopyTextButton/CopyTextButton.tsx +++ b/packages/core/src/components/CopyTextButton/CopyTextButton.tsx @@ -19,9 +19,9 @@ import { IconButton, makeStyles, Tooltip } from '@material-ui/core'; import PropTypes from 'prop-types'; import CopyIcon from '@material-ui/icons/FileCopy'; import { BackstageTheme } from '@backstage/theme'; -import { errorApiRef, useApi } from '../../api'; +import { errorApiRef, useApi } from '@backstage/core-api'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ button: { '&:hover': { backgroundColor: theme.palette.highlight, @@ -56,7 +56,7 @@ const defaultProps = { tooltipText: 'Text copied to clipboard', }; -const CopyTextButton: FC = (props) => { +const CopyTextButton: FC = props => { const { text, tooltipDelay, tooltipText } = { ...defaultProps, ...props, @@ -66,7 +66,7 @@ const CopyTextButton: FC = (props) => { const inputRef = useRef(null); const [open, setOpen] = useState(false); - const handleCopyClick: MouseEventHandler = (e) => { + const handleCopyClick: MouseEventHandler = e => { e.stopPropagation(); setOpen(true); diff --git a/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx index d97956c1f0..cc7d99660b 100644 --- a/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx +++ b/packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx @@ -23,7 +23,7 @@ import { Theme, } from '@material-ui/core'; import React, { FC, useState } from 'react'; -import { PendingAuthRequest } from '../../api'; +import { PendingAuthRequest } from '@backstage/core-api'; const useItemStyles = makeStyles(theme => ({ root: { diff --git a/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx index bde12ccb04..07078c3fa2 100644 --- a/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx +++ b/packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx @@ -27,7 +27,7 @@ import { import React, { FC, useMemo, useState } from 'react'; import { useObservable } from 'react-use'; import LoginRequestListItem from './LoginRequestListItem'; -import { useApi, oauthRequestApiRef } from '../../api'; +import { useApi, oauthRequestApiRef } from '@backstage/core-api'; const useStyles = makeStyles(theme => ({ dialog: { diff --git a/packages/core/src/icons/icons.tsx b/packages/core/src/icons/icons.tsx index ca3ef32859..e8834a6162 100644 --- a/packages/core/src/icons/icons.tsx +++ b/packages/core/src/icons/icons.tsx @@ -18,7 +18,7 @@ import { SvgIconProps } from '@material-ui/core'; import PeopleIcon from '@material-ui/icons/People'; import PersonIcon from '@material-ui/icons/Person'; import React, { FC } from 'react'; -import { useApp } from '../api/app/AppContext'; +import { useApp } from '@backstage/core-api'; import { IconComponent, SystemIconKey, SystemIcons } from './types'; export const defaultSystemIcons: SystemIcons = { @@ -27,7 +27,7 @@ export const defaultSystemIcons: SystemIcons = { }; const overridableSystemIcon = (key: SystemIconKey): IconComponent => { - const Component: FC = (props) => { + const Component: FC = props => { const app = useApp(); const Icon = app.getSystemIcon(key); return ; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index fc6e2988bc..789c57430e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +export * from '@backstage/core-api'; + export * from './api'; export { default as Page } from './layout/Page'; export { gradients, pageTheme } from './layout/Page'; diff --git a/packages/core/src/layout/Sidebar/SidebarThemeToggle.tsx b/packages/core/src/layout/Sidebar/SidebarThemeToggle.tsx index 7f4c8890f9..32bc9d9632 100644 --- a/packages/core/src/layout/Sidebar/SidebarThemeToggle.tsx +++ b/packages/core/src/layout/Sidebar/SidebarThemeToggle.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 '../../api'; +import { appThemeApiRef, useApi } from '@backstage/core-api'; import { SidebarItem } from './Items'; export const SidebarThemeToggle: FC<{}> = () => { From 053fd7d384f7c31988ff8179974c9ae46c362fc1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 27 May 2020 02:07:41 +0200 Subject: [PATCH 11/23] packages/core-api: prune imports --- packages/core-api/package.json | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 88d593dcf8..d89d39d532 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -31,34 +31,17 @@ "@backstage/theme": "^0.1.1-alpha.6", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.45", - "@types/classnames": "^2.2.9", - "@types/google-protobuf": "^3.7.2", "@types/jest": "^25.2.2", "@types/node": "^12.0.0", - "@types/react-helmet": "^5.0.15", - "@types/react-sparklines": "^1.7.0", "@types/zen-observable": "^0.8.0", - "classnames": "^2.2.6", - "clsx": "^1.1.0", - "lodash": "^4.17.15", - "material-table": "^1.58.0", "prop-types": "^15.7.2", - "rc-progress": "^3.0.0", "react": "^16.12.0", - "react-addons-text-content": "0.0.4", - "react-dom": "^16.12.0", - "react-helmet": "5.2.1", - "react-router": "^5.2.0", "react-router-dom": "^5.2.0", - "react-sparklines": "^1.7.0", - "react-syntax-highlighter": "^12.2.1", "react-use": "^14.2.0", "zen-observable": "^0.8.15" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.6", - "@backstage/test-utils": "^0.1.1-alpha.6", "@backstage/test-utils-core": "^0.1.1-alpha.6", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", From 89d230d2e02861e9f0c847f1328c76addafdc36a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 27 May 2020 09:48:43 +0200 Subject: [PATCH 12/23] packages/core: prune imports --- packages/core/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index a14c1f9465..1625284702 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -47,20 +47,17 @@ "prop-types": "^15.7.2", "rc-progress": "^3.0.0", "react": "^16.12.0", - "react-addons-text-content": "0.0.4", "react-dom": "^16.12.0", "react-helmet": "6.0.0", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-sparklines": "^1.7.0", "react-syntax-highlighter": "^12.2.1", - "react-use": "^14.2.0", - "zen-observable": "^0.8.15" + "react-use": "^14.2.0" }, "devDependencies": { "@backstage/cli": "^0.1.1-alpha.6", "@backstage/test-utils": "^0.1.1-alpha.6", - "@backstage/test-utils-core": "^0.1.1-alpha.6", "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^10.2.4", From 9b13fa6091812067bc93590df55dabbadacff5b8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 27 May 2020 09:51:45 +0200 Subject: [PATCH 13/23] packages/core-api: update package json and pin core-api version --- packages/core-api/package.json | 4 ++-- packages/core/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core-api/package.json b/packages/core-api/package.json index d89d39d532..73e7a18d33 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-api", - "description": "Core API used by Backstage plugins and apps", + "description": "Internal Core API used by Backstage plugins and apps", "version": "0.1.1-alpha.6", "private": false, "publishConfig": { @@ -10,7 +10,7 @@ "repository": { "type": "git", "url": "https://github.com/spotify/backstage", - "directory": "packages/core" + "directory": "packages/core-api" }, "keywords": [ "backstage" diff --git a/packages/core/package.json b/packages/core/package.json index 1625284702..a89ffc0b92 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -28,7 +28,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-api": "^0.1.1-alpha.6", + "@backstage/core-api": "0.1.1-alpha.6", "@backstage/theme": "^0.1.1-alpha.6", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", From 11b6ad829e1ad7135fbc06d4fe1fe3876c9fed96 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 27 May 2020 10:00:32 +0200 Subject: [PATCH 14/23] packages/cli: add core-api as local package for e2e test --- packages/cli/src/lib/tasks.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index 6b752a36cc..2d592bc549 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -112,6 +112,7 @@ export async function templatingTask( const PATCH_PACKAGES = [ 'cli', 'core', + 'core-api', 'dev-utils', 'test-utils', 'test-utils-core', From 76dc2cc4a086dc11df4e44539cbd24438ed3820a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 27 May 2020 12:45:43 +0200 Subject: [PATCH 15/23] packages/core-api: properly forward auth options to respect the instantPopup option --- .../core-api/src/apis/definitions/auth.ts | 5 +++- .../implementations/auth/google/GoogleAuth.ts | 19 ++++++++----- .../DefaultAuthConnector.test.ts | 28 +++++++++++++++++-- .../lib/AuthConnector/DefaultAuthConnector.ts | 16 +++++++---- .../core-api/src/lib/AuthConnector/types.ts | 7 ++++- .../AuthSessionManager/AuthSessionStore.ts | 14 ++-------- .../RefreshingAuthSessionManager.test.ts | 17 +++++++++++ .../RefreshingAuthSessionManager.ts | 21 ++++---------- .../StaticAuthSessionManager.test.ts | 2 +- .../StaticAuthSessionManager.ts | 22 ++++----------- .../src/lib/AuthSessionManager/types.ts | 12 ++++---- 11 files changed, 97 insertions(+), 66 deletions(-) diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index 15c9e5d632..c066c83b73 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -85,7 +85,10 @@ export type OAuthApi = { * will be prompted to log in. The returned promise will not resolve until the user has * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. */ - getAccessToken(scope?: OAuthScope): Promise; + getAccessToken( + scope?: OAuthScope, + options?: AccessTokenOptions, + ): Promise; /** * Log out the user's session. This will reload the page. diff --git a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts index e52e8f0397..582afbcfea 100644 --- a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts +++ b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts @@ -21,6 +21,7 @@ import { OAuthApi, OpenIdConnectApi, IdTokenOptions, + AccessTokenOptions, } from '../../../definitions/auth'; import { OAuthRequestApi, AuthProvider } from '../../../definitions'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; @@ -95,19 +96,23 @@ class GoogleAuth implements OAuthApi, OpenIdConnectApi { constructor(private readonly sessionManager: SessionManager) {} - async getAccessToken(scope?: string | string[]) { + async getAccessToken( + scope?: string | string[], + options?: AccessTokenOptions, + ) { const normalizedScopes = GoogleAuth.normalizeScopes(scope); const session = await this.sessionManager.getSession({ - optional: false, + ...options, scopes: normalizedScopes, }); - return session.accessToken; + if (session) { + return session.accessToken; + } + return ''; } - async getIdToken({ optional }: IdTokenOptions = {}) { - const session = await this.sessionManager.getSession({ - optional: optional || false, - }); + async getIdToken(options: IdTokenOptions = {}) { + const session = await this.sessionManager.getSession(options); if (session) { return session.idToken; } diff --git a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts index 9dde49aab8..3d8f7abeb9 100644 --- a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -86,7 +86,7 @@ describe('DefaultAuthConnector', () => { ...defaultOptions, oauthRequestApi: mockOauth, }); - const promise = helper.createSession(new Set(['a', 'b'])); + const promise = helper.createSession({ scopes: new Set(['a', 'b']) }); await mockOauth.rejectAll(); await expect(promise).rejects.toMatchObject({ name: 'RejectedError' }); }); @@ -106,7 +106,9 @@ describe('DefaultAuthConnector', () => { oauthRequestApi: mockOauth, }); - const sessionPromise = helper.createSession(new Set(['a', 'b'])); + const sessionPromise = helper.createSession({ + scopes: new Set(['a', 'b']), + }); await mockOauth.triggerAll(); @@ -123,6 +125,26 @@ describe('DefaultAuthConnector', () => { }); }); + it('should instantly show popup if option is set', async () => { + const popupSpy = jest + .spyOn(loginPopup, 'showLoginPopup') + .mockResolvedValue('my-session'); + const helper = new DefaultAuthConnector({ + ...defaultOptions, + oauthRequestApi: new MockOAuthApi(), + sessionTransform: str => str, + }); + + const sessionPromise = helper.createSession({ + scopes: new Set(), + instantPopup: true, + }); + + expect(popupSpy).toBeCalledTimes(1); + + await expect(sessionPromise).resolves.toBe('my-session'); + }); + it('should use join func to join scopes', async () => { const mockOauth = new MockOAuthApi(); const popupSpy = jest @@ -134,7 +156,7 @@ describe('DefaultAuthConnector', () => { oauthRequestApi: mockOauth, }); - helper.createSession(new Set(['a', 'b'])); + helper.createSession({ scopes: new Set(['a', 'b']) }); await mockOauth.triggerAll(); diff --git a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts index 43bca21e90..21d2530c5a 100644 --- a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts @@ -17,7 +17,7 @@ import { AuthRequester } from '../../apis'; import { OAuthRequestApi, AuthProvider } from '../../apis/definitions'; import { showLoginPopup } from '../loginPopup'; -import { AuthConnector } from './types'; +import { AuthConnector, CreateSessionOptions } from './types'; const DEFAULT_BASE_PATH = '/api/auth/'; @@ -68,6 +68,7 @@ export class DefaultAuthConnector private readonly basePath: string; private readonly environment: string; private readonly provider: AuthProvider & { id: string }; + private readonly joinScopesFunc: (scopes: Set) => string; private readonly authRequester: AuthRequester; private readonly sessionTransform: (response: any) => Promise; @@ -84,18 +85,22 @@ export class DefaultAuthConnector this.authRequester = oauthRequestApi.createAuthRequester({ provider, - onAuthRequest: scopes => this.showPopup(joinScopes(scopes)), + onAuthRequest: scopes => this.showPopup(scopes), }); this.apiOrigin = apiOrigin; this.basePath = basePath; this.environment = environment; this.provider = provider; + this.joinScopesFunc = joinScopes; this.sessionTransform = sessionTransform; } - async createSession(scopes: Set): Promise { - return this.authRequester(scopes); + async createSession(options: CreateSessionOptions): Promise { + if (options.instantPopup) { + return this.showPopup(options.scopes); + } + return this.authRequester(options.scopes); } async refreshSession(): Promise { @@ -142,7 +147,8 @@ export class DefaultAuthConnector } } - private async showPopup(scope: string): Promise { + private async showPopup(scopes: Set): Promise { + const scope = this.joinScopesFunc(scopes); const popupUrl = this.buildUrl('/start', { scope }); const payload = await showLoginPopup({ diff --git a/packages/core-api/src/lib/AuthConnector/types.ts b/packages/core-api/src/lib/AuthConnector/types.ts index 146c31cbe1..46175a265f 100644 --- a/packages/core-api/src/lib/AuthConnector/types.ts +++ b/packages/core-api/src/lib/AuthConnector/types.ts @@ -14,12 +14,17 @@ * limitations under the License. */ +export type CreateSessionOptions = { + scopes: Set; + instantPopup?: boolean; +}; + /** * An AuthConnector is responsible for realizing auth session actions * by for example communicating with a backend or interacting with the user. */ export type AuthConnector = { - createSession(scopes: Set): Promise; + createSession(options: CreateSessionOptions): Promise; refreshSession(): Promise; removeSession(): Promise; }; diff --git a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts index 14796c6e11..8d318f5bff 100644 --- a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts +++ b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts @@ -18,6 +18,7 @@ import { SessionManager, SessionScopesFunc, SessionShouldRefreshFunc, + GetSessionOptions, } from './types'; import { SessionScopeHelper } from './common'; @@ -59,18 +60,7 @@ export class AuthSessionStore implements SessionManager { }); } - async getSession(options: { - optional: false; - scopes?: Set; - }): Promise; - async getSession(options: { - optional?: boolean; - scopes?: Set; - }): Promise; - async getSession(options: { - optional?: boolean; - scopes?: Set; - }): Promise { + async getSession(options: GetSessionOptions): Promise { const { scopes } = options; const session = this.loadSession(); diff --git a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts index 8684c932b2..ee7cbab2ae 100644 --- a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts +++ b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts @@ -113,6 +113,23 @@ describe('RefreshingAuthSessionManager', () => { expect(refreshSession).toBeCalledTimes(1); }); + it('should forward option to instantly show auth popup', async () => { + const createSession = jest.fn(); + const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); + const manager = new RefreshingAuthSessionManager({ + connector: { createSession, refreshSession }, + ...defaultOptions, + } as any); + + expect(await manager.getSession({ instantPopup: true })).toBe(undefined); + expect(createSession).toBeCalledTimes(1); + expect(createSession).toHaveBeenCalledWith({ + scopes: new Set(), + instantPopup: true, + }); + expect(refreshSession).toBeCalledTimes(1); + }); + it('should remove session and reload', async () => { // This is a workaround that is used by Facebook and the Jest core team // It is a limitation with the newest versions of JSDOM, and newer browser standards diff --git a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts index cbd6d5ca46..64df3deca4 100644 --- a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts +++ b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts @@ -18,6 +18,7 @@ import { SessionManager, SessionScopesFunc, SessionShouldRefreshFunc, + GetSessionOptions, } from './types'; import { AuthConnector } from '../AuthConnector'; import { SessionScopeHelper, hasScopes } from './common'; @@ -60,18 +61,7 @@ export class RefreshingAuthSessionManager implements SessionManager { this.helper = new SessionScopeHelper({ sessionScopes, defaultScopes }); } - async getSession(options: { - optional: false; - scopes?: Set; - }): Promise; - async getSession(options: { - optional?: boolean; - scopes?: Set; - }): Promise; - async getSession(options: { - optional?: boolean; - scopes?: Set; - }): Promise { + async getSession(options: GetSessionOptions): Promise { if ( this.helper.sessionExistsAndHasScope(this.currentSession, options.scopes) ) { @@ -115,9 +105,10 @@ export class RefreshingAuthSessionManager implements SessionManager { } // We can call authRequester multiple times, the returned session will contain all requested scopes. - this.currentSession = await this.connector.createSession( - this.helper.getExtendedScope(this.currentSession, options.scopes), - ); + this.currentSession = await this.connector.createSession({ + ...options, + scopes: this.helper.getExtendedScope(this.currentSession, options.scopes), + }); return this.currentSession; } diff --git a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts index dc13aec0c2..7d47fa91df 100644 --- a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts +++ b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts @@ -62,7 +62,7 @@ describe('StaticAuthSessionManager', () => { it('should only request auth once for same scopes', async () => { const createSession = jest .fn() - .mockImplementation(scopes => [...scopes].join(' ')); + .mockImplementation(({ scopes }) => [...scopes].join(' ')); const manager = new StaticAuthSessionManager({ connector: { createSession, ...baseConnector }, ...defaultOptions, diff --git a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts index 8d057ecc58..6e6db47a99 100644 --- a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts +++ b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { SessionManager } from './types'; +import { SessionManager, GetSessionOptions } from './types'; import { AuthConnector } from '../AuthConnector'; import { SessionScopeHelper } from './common'; @@ -43,18 +43,7 @@ export class StaticAuthSessionManager implements SessionManager { this.helper = new SessionScopeHelper({ sessionScopes, defaultScopes }); } - async getSession(options: { - optional: false; - scopes?: Set; - }): Promise; - async getSession(options: { - optional?: boolean; - scopes?: Set; - }): Promise; - async getSession(options: { - optional?: boolean; - scopes?: Set; - }): Promise { + async getSession(options: GetSessionOptions): Promise { if ( this.helper.sessionExistsAndHasScope(this.currentSession, options.scopes) ) { @@ -67,9 +56,10 @@ export class StaticAuthSessionManager implements SessionManager { } // We can call authRequester multiple times, the returned session will contain all requested scopes. - this.currentSession = await this.connector.createSession( - this.helper.getExtendedScope(this.currentSession, options.scopes), - ); + this.currentSession = await this.connector.createSession({ + ...options, + scopes: this.helper.getExtendedScope(this.currentSession, options.scopes), + }); return this.currentSession; } diff --git a/packages/core-api/src/lib/AuthSessionManager/types.ts b/packages/core-api/src/lib/AuthSessionManager/types.ts index 5ebc8643e1..d28a751fbf 100644 --- a/packages/core-api/src/lib/AuthSessionManager/types.ts +++ b/packages/core-api/src/lib/AuthSessionManager/types.ts @@ -14,17 +14,19 @@ * limitations under the License. */ +export type GetSessionOptions = { + optional?: boolean; + instantPopup?: boolean; + scopes?: Set; +}; + /** * A sessions manager keeps track of the current session and makes sure that * multiple simultaneous requests for sessions with different scope are handled * in a correct way. */ export type SessionManager = { - getSession(options: { optional: false; scopes?: Set }): Promise; - getSession(options: { - optional?: boolean; - scopes?: Set; - }): Promise; + getSession(options: GetSessionOptions): Promise; removeSession(): Promise; }; From 01025bbd01aa2da283b67dcd9be4afa03807d231 Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Wed, 27 May 2020 12:50:09 +0200 Subject: [PATCH 16/23] plugin(tech-radar): set private to false and publishConfig access to public --- plugins/tech-radar/package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 93285f5214..3a9c75a3f7 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -5,7 +5,10 @@ "main:src": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", - "private": true, + "private": false, + "publishConfig": { + "access": "public" + }, "scripts": { "build": "backstage-cli plugin:build", "lint": "backstage-cli lint", From 582f63677678205956dbb6b69f563dc2934cfd65 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 27 May 2020 18:20:51 +0200 Subject: [PATCH 17/23] docs: add adr004, module export structure --- .../adr004-module-export-structure.md | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/architecture-decisions/adr004-module-export-structure.md diff --git a/docs/architecture-decisions/adr004-module-export-structure.md b/docs/architecture-decisions/adr004-module-export-structure.md new file mode 100644 index 0000000000..56ea7568c6 --- /dev/null +++ b/docs/architecture-decisions/adr004-module-export-structure.md @@ -0,0 +1,59 @@ +# ADR004: Module Export Structure + +| Created | Status | +| ---------- | ------ | +| 2020-05-27 | Open | + +## Context + +With a growing number of exports of packages like `@backstage/core`, it is becoming more and more difficult to answer questions such as + +> Is the export in this module also exported by the package? + +or + +> What is exported from this directory? + +We currently do not use any pattern for how to structure exports. There is a mix of package-level re-exports deep into the directory tree, shallow re-exports for each directory, exports using `*` and explicit lists of each symbol, etc. +The mix and lack of predictability makes it difficult to reason about the boundaries of a module, and for example knowing whether is is safe to export a symbol in a given file. + +## Decision + +We will make each exported symbol traceable through index files all the way down to the root of the package, `src/index.ts`. Each index file will only re-export from its own immediate directory children, and only index files will have re-exports. This gives a file tree similar to this: + +```text +index.ts +components/index.ts + /ComponentX/index.ts + /ComponentX.tsx + /SubComponentY.tsx +lib/index.ts + /UtilityX/index.ts + /UtilityX.ts + /helper.ts +``` + +To check whether for example `SubComponentY` is exported from the package, it should be possible to traverse the index files towards the root, starting at the adjacent one. If there is any index file that doesn't export the previous one, the symbol is not publicly exported. For example, if `components/ComponentX/index.ts` exports `SubComponentY`, but `components/index.ts` does not re-export `./ComponentX`, one should be certain that `SubComponentY` is not exported outside the package. This rule would be broken if for example the root `index.ts` re-exports `./components/ComponentX` + +In addition, index files that are re-exporting other index files should always use wildcard form, that is: + +```ts +// in components/index.ts +export * from './ComponentX'; +``` + +Index files that are re-exporting symbols from non-index files should always enumerate all exports, that is: + +```ts +// in components/ComponentX/index.ts +export { ComponentX } from './ComponentX'; +export type { ComponentXProps } from './ComponentX'; +``` + +Internal cross-directory imports are allowed, but discouraged, and no module should re-export a symbol across directories. + +## Consequences + +We will actively work to rework the export structure in our codebase, prioritizing the library packages such as `@backstage/core` and `@backstage/backend-common`. + +If possible, we will add tools, such as lint rules, to help enforce the export structure. From d37d7b944b38f8f7e6b3c38b56c1dfe1e04855f2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 May 2020 00:42:23 +0200 Subject: [PATCH 18/23] docs/adr004: add clarification about internal imports --- .../adr004-module-export-structure.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/architecture-decisions/adr004-module-export-structure.md b/docs/architecture-decisions/adr004-module-export-structure.md index 56ea7568c6..56c675f069 100644 --- a/docs/architecture-decisions/adr004-module-export-structure.md +++ b/docs/architecture-decisions/adr004-module-export-structure.md @@ -50,7 +50,19 @@ export { ComponentX } from './ComponentX'; export type { ComponentXProps } from './ComponentX'; ``` -Internal cross-directory imports are allowed, but discouraged, and no module should re-export a symbol across directories. +Internal cross-directory imports are allowed from non-index modules to index modules, for example: + +```ts +// in components/ComponentX/ComponentX.tsx +import { UtilityX } from '../../lib/UtilityX'; +``` + +Imports that bypass an index file are discouraged, but may sometimes be necessary, for example: + +```ts +// in components/ComponentX/ComponentX.tsx +import { helperFunc } from '../../lib/UtilityX/helper'; +``` ## Consequences From eeb69c56177a63e40cb72a29d925c36d4842e699 Mon Sep 17 00:00:00 2001 From: Govind Goel <52847415+govindgoel@users.noreply.github.com> Date: Thu, 28 May 2020 12:17:19 +0530 Subject: [PATCH 19/23] Set the correct page title in /create (#1037) This commit will fix the page title error in /create --- plugins/scaffolder/src/components/ScaffolderPage/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder/src/components/ScaffolderPage/index.tsx b/plugins/scaffolder/src/components/ScaffolderPage/index.tsx index 922416972a..e6fed50cef 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/index.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/index.tsx @@ -40,6 +40,7 @@ const ScaffolderPage: React.FC<{}> = () => { return (
    Create a new component {' '} From 9daea8f16319aa0cc269aa897081e2cc6669c7b5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 May 2020 11:13:50 +0200 Subject: [PATCH 20/23] packages/cli: more strict rollup version --- packages/cli/package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 299fc5e8d3..a15765c731 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -62,7 +62,7 @@ "react-hot-loader": "^4.12.21", "recursive-readdir": "^2.2.2", "replace-in-file": "^6.0.0", - "rollup": "^2.3.2", + "rollup": "2.10.x", "rollup-plugin-dts": "^1.4.6", "rollup-plugin-esbuild": "^1.4.1", "rollup-plugin-image-files": "^1.4.2", diff --git a/yarn.lock b/yarn.lock index 55255c89d9..65f328a4bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18359,6 +18359,13 @@ rollup@0.64.1: "@types/estree" "0.0.39" "@types/node" "*" +rollup@2.10.x: + version "2.10.9" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.10.9.tgz#17dcc6753c619efcc1be2cf61d73a87827eebdf9" + integrity sha512-dY/EbjiWC17ZCUSyk14hkxATAMAShkMsD43XmZGWjLrgFj15M3Dw2kEkA9ns64BiLFm9PKN6vTQw8neHwK74eg== + optionalDependencies: + fsevents "~2.1.2" + rollup@^0.63.4: version "0.63.5" resolved "https://registry.npmjs.org/rollup/-/rollup-0.63.5.tgz#5543eecac9a1b83b7e1be598b5be84c9c0a089db" @@ -18367,13 +18374,6 @@ rollup@^0.63.4: "@types/estree" "0.0.39" "@types/node" "*" -rollup@^2.3.2: - version "2.10.9" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.10.9.tgz#17dcc6753c619efcc1be2cf61d73a87827eebdf9" - integrity sha512-dY/EbjiWC17ZCUSyk14hkxATAMAShkMsD43XmZGWjLrgFj15M3Dw2kEkA9ns64BiLFm9PKN6vTQw8neHwK74eg== - optionalDependencies: - fsevents "~2.1.2" - rsvp@^4.8.4: version "4.8.5" resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" From 3d4334fdb911a95589a9ffcf2550a501530a4b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 28 May 2020 12:46:23 +0200 Subject: [PATCH 21/23] New home: service catalog (#1018) * New home: service catalog * Add banner * Remove extra <> * Re-enable test * Review comments * Update DismissableBanner.stories.tsx --- packages/app/src/components/Root/Root.tsx | 3 - .../DismissableBanner.stories.tsx | 66 +++++++++++++ .../DismissableBanner.test.js | 46 +++++++++ .../DismissableBanner/DismissableBanner.tsx | 96 +++++++++++++++++++ .../src/components/DismissableBanner/index.ts | 17 ++++ packages/core/src/index.ts | 1 + .../components/CatalogPage/CatalogPage.tsx | 19 +++- plugins/catalog/src/plugin.ts | 2 +- plugins/welcome/src/plugin.ts | 2 +- 9 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 packages/core/src/components/DismissableBanner/DismissableBanner.stories.tsx create mode 100644 packages/core/src/components/DismissableBanner/DismissableBanner.test.js create mode 100644 packages/core/src/components/DismissableBanner/DismissableBanner.tsx create mode 100644 packages/core/src/components/DismissableBanner/index.ts diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 3c5ec4ae3a..20eabbcac5 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -20,7 +20,6 @@ import { Link, makeStyles } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ExploreIcon from '@material-ui/icons/Explore'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; -import AccountTreeIcon from '@material-ui/icons/AccountTree'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; import { @@ -82,8 +81,6 @@ const Root: FC<{}> = ({ children }) => ( {/* End global nav */} - - diff --git a/packages/core/src/components/DismissableBanner/DismissableBanner.stories.tsx b/packages/core/src/components/DismissableBanner/DismissableBanner.stories.tsx new file mode 100644 index 0000000000..e19495a099 --- /dev/null +++ b/packages/core/src/components/DismissableBanner/DismissableBanner.stories.tsx @@ -0,0 +1,66 @@ +/* + * 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 DismissableBanner from './DismissableBanner'; +import { Link, Typography } from '@material-ui/core'; + +export default { + title: 'DismissableBanner', + component: DismissableBanner, +}; + +const containerStyle = { width: '70%' }; + +export const Default = () => ( +
    + +
    +); + +export const Error = () => ( +
    + +
    +); + +export const EmojisIncluded = () => ( +
    + +
    +); + +export const WithLink = () => ( +
    + + This is a dismissable banner with a link:{' '} + + example.com + + + } + variant="info" + /> +
    +); diff --git a/packages/core/src/components/DismissableBanner/DismissableBanner.test.js b/packages/core/src/components/DismissableBanner/DismissableBanner.test.js new file mode 100644 index 0000000000..485b6226d2 --- /dev/null +++ b/packages/core/src/components/DismissableBanner/DismissableBanner.test.js @@ -0,0 +1,46 @@ +/* + * 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 { fireEvent, waitForElementToBeRemoved } from '@testing-library/react'; +import { renderWithEffects, wrapInThemedTestApp } from '@backstage/test-utils'; +// import { createSetting } from 'shared/apis/settings'; +import DismissableBanner from './DismissableBanner'; + +describe('', () => { + it('renders the message and the popover', async () => { + /* + const mockSetting = createSetting({ + id: 'mockSetting', + defaultValue: true, + }); + */ + + const rendered = await renderWithEffects( + wrapInThemedTestApp( + , + ), + ); + rendered.getByText('test message'); + + // fireEvent.click(rendered.getByTitle('Permanently dismiss this message')); + // await waitForElementToBeRemoved(rendered.queryByText('test message')); + }); +}); diff --git a/packages/core/src/components/DismissableBanner/DismissableBanner.tsx b/packages/core/src/components/DismissableBanner/DismissableBanner.tsx new file mode 100644 index 0000000000..7190ee8725 --- /dev/null +++ b/packages/core/src/components/DismissableBanner/DismissableBanner.tsx @@ -0,0 +1,96 @@ +/* + * 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, { FC, ReactNode, useState } from 'react'; +import classNames from 'classnames'; +import { makeStyles, Theme } from '@material-ui/core'; +import Snackbar from '@material-ui/core/Snackbar'; +import SnackbarContent from '@material-ui/core/SnackbarContent'; +import IconButton from '@material-ui/core/IconButton'; +import Close from '@material-ui/icons/Close'; +// import { useSetting, Setting } from 'shared/apis/settings'; + +const useStyles = makeStyles((theme: Theme) => ({ + root: { + position: 'relative', + padding: theme.spacing(0), + marginBottom: theme.spacing(6), + marginTop: -theme.spacing(3), + display: 'flex', + flexFlow: 'row nowrap', + }, + icon: { + fontSize: 20, + }, + content: { + width: '100%', + maxWidth: 'inherit', + }, + message: { + display: 'flex', + alignItems: 'center', + }, + info: { + backgroundColor: theme.palette.primary.main, + }, + error: { + backgroundColor: theme.palette.error.dark, + }, +})); + +type Props = { + variant: 'info' | 'error'; + // setting: Setting; + message: ReactNode; +}; + +const DismissableBanner: FC = ({ variant, /* setting, */ message }) => { + // const [show, setShown, loading] = useSetting(setting); + const [show, setShown] = useState(true); + const classes = useStyles(); + + const handleClick = () => { + setShown(false); + }; + + return ( + + + + , + ]} + /> + + ); +}; + +export default DismissableBanner; diff --git a/packages/core/src/components/DismissableBanner/index.ts b/packages/core/src/components/DismissableBanner/index.ts new file mode 100644 index 0000000000..28091e858b --- /dev/null +++ b/packages/core/src/components/DismissableBanner/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 { default } from './DismissableBanner'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 789c57430e..be9cdc432d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -23,6 +23,7 @@ export type { PageTheme } from './layout/Page'; export { default as CodeSnippet } from './components/CodeSnippet'; export { default as Content } from './layout/Content/Content'; export { default as ContentHeader } from './layout/ContentHeader/ContentHeader'; +export { default as DismissableBanner } from './components/DismissableBanner'; export { default as Header } from './layout/Header/Header'; export { default as HeaderLabel } from './layout/HeaderLabel'; export { default as HomepageTimer } from './layout/HomepageTimer'; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index f5094542a5..610ecc3bb8 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -18,6 +18,7 @@ import React, { FC } from 'react'; import { Content, ContentHeader, + DismissableBanner, Header, HomepageTimer, SupportButton, @@ -31,7 +32,7 @@ import { CatalogFilter, CatalogFilterItem, } from '../CatalogFilter/CatalogFilter'; -import { Button, makeStyles } from '@material-ui/core'; +import { Button, makeStyles, Typography, Link } from '@material-ui/core'; import { filterGroups, defaultFilter } from '../../data/filters'; const useStyles = makeStyles(theme => ({ @@ -65,6 +66,22 @@ const CatalogPage: FC = ({ componentFactory }) => {
    + + + 👋🏼 + {' '} + Welcome to Backstage, we are happy to have you. Start by checking + out our{' '} + + getting started + {' '} + page. + + } + />