diff --git a/plugins/circleci/src/api/index.ts b/plugins/circleci/src/api/index.ts index 849ac4ce1a..b3de6758cc 100644 --- a/plugins/circleci/src/api/index.ts +++ b/plugins/circleci/src/api/index.ts @@ -17,45 +17,69 @@ import { CircleCI, GitType, CircleCIOptions } from 'circleci-api'; import { ApiRef } from '@backstage/core'; -const options: Partial = { - // Required for all requests - // token: CIRCLECI_TOKEN, // Set your CircleCi API token - - // Optional - // Anything set here can be overriden when making the request - - // Git information is required for project/build/etc endpoints +const defaultOptions: Partial = { vcs: { - type: GitType.GITHUB, // default: github + type: GitType.GITHUB, owner: 'CircleCITest3', repo: 'circleci-test', }, }; - -export class CircleCIApi { - api: null | CircleCI = null; - constuctor() {} - async authenticate(token: string) { - try { - if (token === '') return Promise.reject(); - this.api = new CircleCI({ ...options, token }); - // await this.api.me(); - return Promise.resolve(); - } catch (e) { - this.api = null; - return this.cantAuth(); - } - } - async cantAuth() { - return Promise.reject("Can't auth"); - } - async getBuilds() { - if (!this.api) return this.cantAuth(); - return this.api.builds(); - } -} - export const circleCIApiRef = new ApiRef({ id: 'plugin.circleci.service', description: 'Used by the CircleCI plugin to make requests', }); + +export class CircleCIApi { + token: string = ''; + private options: Partial; + + authed: boolean = false; + constructor(options?: Partial) { + this.options = Object.assign(Object.create(null), defaultOptions, options); + } + + setToken(token: string) { + this.token = token; + this.persistToken(); + } + + async restorePersistedToken() { + if (this.authed) return Promise.resolve(); + const key = circleCIApiRef.id; + const persistedToken = sessionStorage.getItem(key); + if (persistedToken) { + this.token = persistedToken; + return Promise.resolve(); + } + return Promise.reject(); + } + + async persistToken() { + if (this.authed) return; + const key = circleCIApiRef.id; + sessionStorage.setItem(key, this.token); + } + + async validateToken() { + if (!this.token || this.token === '') { + return Promise.reject('Wrong token'); + } + + // TODO: switch towards using personal token + await this.api.builds(); + this.authed = true; + return Promise.resolve(); + } + + private get api() { + return new CircleCI({ ...this.options, token: this.token }); + } + + async getBuilds() { + return this.api.builds(); + } + + async getUser() { + return this.api.me(); + } +} diff --git a/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx b/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx index 50ca39b3a6..6e27d3e51e 100644 --- a/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx +++ b/plugins/circleci/src/components/CircleCIFetch/CirleCIFetch.tsx @@ -74,18 +74,27 @@ const transform = (buildsData: BuildSummary[]): CITableBuildInfo[] => { }; export const CircleCIFetch: FC<{}> = () => { + const [authed, setAuthed] = React.useState(false); const [builds, setBuilds] = React.useState([]); const api = useApi(circleCIApiRef); React.useEffect(() => { - const intervalId = setInterval(() => { - if (!api.api) return; + const intervalId = setInterval(async () => { + if (!authed) { + await api.restorePersistedToken(); + await api + .validateToken() + .then(() => { + setAuthed(true); + }) + .catch(() => setAuthed(false)); + } api.getBuilds().then(setBuilds); }, 1500); return () => clearInterval(intervalId); - }, []); + }, [authed]); - if (!api.api) return
Not authenticated
; + if (!authed) return
Not authenticated
; const transformedBuilds = transform(builds || []); return ; }; diff --git a/plugins/circleci/src/components/CircleCIPage/CircleCIPage.tsx b/plugins/circleci/src/components/CircleCIPage/CircleCIPage.tsx index 23ac5c0d7d..a37d5e633d 100644 --- a/plugins/circleci/src/components/CircleCIPage/CircleCIPage.tsx +++ b/plugins/circleci/src/components/CircleCIPage/CircleCIPage.tsx @@ -15,7 +15,7 @@ */ import React, { FC } from 'react'; -import { Grid } from '@material-ui/core'; +import { Grid, Button } from '@material-ui/core'; import { InfoCard, Header, @@ -27,7 +27,6 @@ import { SupportButton, } from '@backstage/core'; import { CircleCIFetch } from '../CircleCIFetch'; -import { LoginCard } from '../LoginCard'; export const CircleCIPage: FC<{}> = () => { return ( @@ -37,15 +36,13 @@ export const CircleCIPage: FC<{}> = () => { - + + A description of your plugin goes here. - - - - + diff --git a/plugins/circleci/src/components/LoginCard/LoginCard.tsx b/plugins/circleci/src/components/LoginCard/LoginCard.tsx deleted file mode 100644 index 806f218cf9..0000000000 --- a/plugins/circleci/src/components/LoginCard/LoginCard.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; -import { - Typography, - Button, - TextField, - List, - ListItem, -} from '@material-ui/core'; -import { Person as PersonIcon } from '@material-ui/icons'; -import { InfoCard, useApi } from '@backstage/core'; -import { circleCIApiRef } from 'api'; - -const useSessionStorage = (key: string): [string, (value: string) => void] => { - const [value, setter] = React.useState(sessionStorage.getItem(key) ?? ''); - const setValue = (newValue: string) => { - sessionStorage.setItem(key, newValue); - setter(sessionStorage.getItem(key) ?? ''); - }; - - React.useEffect(() => { - const storageChangeHandle = (e: StorageEvent) => { - if (e.storageArea !== sessionStorage) return; - if (e.key !== key) return; - if (e.newValue !== e.oldValue) { - setter(e.newValue ?? ''); - } - }; - window.addEventListener('storage', storageChangeHandle); - return () => window.removeEventListener('storage', storageChangeHandle); - }, [key, setter]); - - return [value, setValue]; -}; -export const LoginCard = () => { - const [token, setToken] = useSessionStorage(circleCIApiRef.id); - const api = useApi(circleCIApiRef); - - React.useEffect(() => { - if (token && token !== '') { - api.authenticate(token); - } - }, []); - return ( - - - CircleCI Auth - - - - setToken(e.target.value)} - /> - - - - - - - - ); -}; diff --git a/plugins/circleci/src/components/LoginCard/index.ts b/plugins/circleci/src/components/LoginCard/index.ts deleted file mode 100644 index 1d3c3bcf3e..0000000000 --- a/plugins/circleci/src/components/LoginCard/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './LoginCard'; diff --git a/plugins/circleci/src/components/SettingsPage/SettingsPage.tsx b/plugins/circleci/src/components/SettingsPage/SettingsPage.tsx new file mode 100644 index 0000000000..5b4487bb32 --- /dev/null +++ b/plugins/circleci/src/components/SettingsPage/SettingsPage.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { Button, TextField, List, Grid, ListItem } from '@material-ui/core'; +import { circleCIApiRef } from 'api'; +import { + InfoCard, + useApi, + Header, + Page, + pageTheme, + Content, + ContentHeader, + HeaderLabel, + SupportButton, +} from '@backstage/core'; + +export const SettingsPage = () => { + const [authed, setAuthed] = React.useState(false); + const [token, setToken] = React.useState(''); + const api = useApi(circleCIApiRef); + + React.useEffect(() => { + api + .restorePersistedToken() + .then(() => api.validateToken()) + .then(() => setAuthed(true)) + .catch(() => setAuthed(false)); + }, []); + + return ( + +
+ + +
+ + + + A description of your plugin goes here. + + + + + + {authed ? ( + <>Already authed + ) : ( + <> + + setToken(e.target.value)} + /> + + + + + + + )} + + + + + +
+ ); +}; diff --git a/plugins/circleci/src/components/SettingsPage/index.ts b/plugins/circleci/src/components/SettingsPage/index.ts new file mode 100644 index 0000000000..f533f5abe0 --- /dev/null +++ b/plugins/circleci/src/components/SettingsPage/index.ts @@ -0,0 +1 @@ +export * from './SettingsPage'; diff --git a/plugins/circleci/src/plugin.ts b/plugins/circleci/src/plugin.ts index 5c1eca9be3..458a7f27bc 100644 --- a/plugins/circleci/src/plugin.ts +++ b/plugins/circleci/src/plugin.ts @@ -15,10 +15,12 @@ */ import { createPlugin } from '@backstage/core'; import { CircleCIPage } from './components/CircleCIPage'; +import { SettingsPage } from './components/SettingsPage'; export const plugin = createPlugin({ id: 'circleci', register({ router }) { router.registerRoute('/circleci', CircleCIPage); + router.registerRoute('/circleci/settings', SettingsPage); }, });