From 9c9250faf0c97aeb9fe99089639f655f705adc9c Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Mon, 18 May 2020 17:18:25 +0200 Subject: [PATCH 1/8] feat: remove polling, add pagination, refactor --- plugins/circleci/src/api/index.ts | 9 +- plugins/circleci/src/components/App.tsx | 32 ++-- .../circleci/src/components/Layout/Layout.tsx | 36 +++-- .../components/PluginHeader/PluginHeader.tsx | 8 +- .../src/components/Settings/Settings.tsx | 127 +++++++++++++++ .../Settings}/index.ts | 2 +- .../BuildWithStepsPage/BuildWithStepsPage.tsx | 14 +- .../pages/BuildsPage/lib/Builds/Builds.tsx | 79 ++-------- .../pages/BuildsPage/lib/CITable/CITable.tsx | 40 ++++- .../src/pages/SettingsPage/SettingsPage.tsx | 139 ----------------- plugins/circleci/src/state/AppState.tsx | 33 ++-- plugins/circleci/src/state/types.ts | 49 ++---- .../circleci/src/state/useBuildWithSteps.ts | 50 +++--- plugins/circleci/src/state/useBuilds.tsx | 145 +++++++++++++----- plugins/circleci/src/state/useSettings.ts | 13 +- 15 files changed, 401 insertions(+), 375 deletions(-) create mode 100644 plugins/circleci/src/components/Settings/Settings.tsx rename plugins/circleci/src/{pages/SettingsPage => components/Settings}/index.ts (91%) delete mode 100644 plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx diff --git a/plugins/circleci/src/api/index.ts b/plugins/circleci/src/api/index.ts index d98f511f35..f683f79ff9 100644 --- a/plugins/circleci/src/api/index.ts +++ b/plugins/circleci/src/api/index.ts @@ -48,8 +48,15 @@ export class CircleCIApi { }); } - async getBuilds(options: CircleCIOptions) { + async getBuilds( + { limit = 10, offset = 0 }: { limit: number; offset: number }, + options: CircleCIOptions, + ) { return getBuildSummaries(options.token, { + options: { + limit, + offset, + }, vcs: {}, circleHost: this.apiUrl, ...options, diff --git a/plugins/circleci/src/components/App.tsx b/plugins/circleci/src/components/App.tsx index 3324753b88..46b70cb500 100644 --- a/plugins/circleci/src/components/App.tsx +++ b/plugins/circleci/src/components/App.tsx @@ -16,20 +16,24 @@ import React from 'react'; import { Switch, Route } from 'react-router'; import { BuildsPage } from '../pages/BuildsPage'; -import { SettingsPage } from '../pages/SettingsPage'; import { DetailedViewPage } from '../pages/BuildWithStepsPage'; import { AppStateProvider } from '../state'; +import { Settings } from './Settings'; -export const App = () => ( - - - - - - - -); +export const App = () => { + return ( + + <> + + + + + + + + ); +}; diff --git a/plugins/circleci/src/components/Layout/Layout.tsx b/plugins/circleci/src/components/Layout/Layout.tsx index eab8ab0e88..2028e1533d 100644 --- a/plugins/circleci/src/components/Layout/Layout.tsx +++ b/plugins/circleci/src/components/Layout/Layout.tsx @@ -19,20 +19,22 @@ import { Header, Page, pageTheme, HeaderLabel } from '@backstage/core'; import logo from '../../assets/circle-logo-badge-white-15.png'; import { Box } from '@material-ui/core'; -export const Layout: React.FC = ({ children }) => ( - -
- Backstage Logo - Circle CI - - } - > - - -
- {children} -
-); +export const Layout: React.FC = ({ children }) => { + return ( + +
+ Backstage Logo + Circle CI + + } + > + + +
+ {children} +
+ ); +}; diff --git a/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx b/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx index 7bd35d0805..4bab4b5921 100644 --- a/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx +++ b/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx @@ -19,9 +19,11 @@ import { ContentHeader, SupportButton } from '@backstage/core'; import { Button, IconButton, Box, Typography } from '@material-ui/core'; import ArrowBack from '@material-ui/icons/ArrowBack'; import SettingsIcon from '@material-ui/icons/Settings'; +import { useSettings } from '../../state'; export type Props = { title?: string }; export const PluginHeader: FC = ({ title = 'Circle CI' }) => { + const [, { showSettings }] = useSettings(); const location = useLocation(); const notRoot = !location.pathname.match(/\/circleci\/?$/); const isSettingsPage = location.pathname.match(/\/circleci\/settings\/?/); @@ -40,11 +42,7 @@ export const PluginHeader: FC = ({ title = 'Circle CI' }) => { )} > {!isSettingsPage && ( - )} diff --git a/plugins/circleci/src/components/Settings/Settings.tsx b/plugins/circleci/src/components/Settings/Settings.tsx new file mode 100644 index 0000000000..b33bf59dd8 --- /dev/null +++ b/plugins/circleci/src/components/Settings/Settings.tsx @@ -0,0 +1,127 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useState } from 'react'; +import { + Button, + TextField, + List, + ListItem, + Snackbar, + Box, + Dialog, + DialogTitle, +} from '@material-ui/core'; +import { Alert } from '@material-ui/lab'; +import { useSettings } from '../../state'; + +const Settings = () => { + const [ + { + repo: repoFromStore, + owner: ownerFromStore, + token: tokenFromStore, + showSettings, + }, + { saveSettings, hideSettings }, + ] = useSettings(); + + const [token, setToken] = React.useState(() => tokenFromStore); + const [owner, setOwner] = React.useState(() => ownerFromStore); + const [repo, setRepo] = React.useState(() => repoFromStore); + + React.useEffect(() => { + if (tokenFromStore !== token) { + setToken(tokenFromStore); + } + if (ownerFromStore !== owner) { + setOwner(ownerFromStore); + } + if (repoFromStore !== repo) { + setRepo(repoFromStore); + } + }, [ownerFromStore, repoFromStore, tokenFromStore]); + + const [saved, setSaved] = useState(false); + + return ( + <> + setSaved(false)} + > + Credentials saved. + + + + Project Credentials + {/* {authed ? : } */} + + + + setToken(e.target.value)} + /> + + + setOwner(e.target.value)} + /> + + + setRepo(e.target.value)} + /> + + + + + + + + + + ); +}; + +export default Settings; diff --git a/plugins/circleci/src/pages/SettingsPage/index.ts b/plugins/circleci/src/components/Settings/index.ts similarity index 91% rename from plugins/circleci/src/pages/SettingsPage/index.ts rename to plugins/circleci/src/components/Settings/index.ts index e0ba8157b2..c04ded6dea 100644 --- a/plugins/circleci/src/pages/SettingsPage/index.ts +++ b/plugins/circleci/src/components/Settings/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { default as SettingsPage } from './SettingsPage'; +export { default as Settings } from './Settings'; diff --git a/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx index 7dbe5ef393..0a4dfd4050 100644 --- a/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -15,7 +15,7 @@ */ import React, { FC, useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { Content, InfoCard } from '@backstage/core'; +import { Content, InfoCard, Progress } from '@backstage/core'; import { BuildWithSteps, BuildStepAction } from '../../api'; import { Grid, Box, Link, IconButton } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; @@ -26,7 +26,7 @@ import { useBuildWithSteps, useSettings } from '../../state'; import LaunchIcon from '@material-ui/icons/Launch'; const IconLink = IconButton as typeof Link; -const BuildName: FC<{ build: BuildWithSteps | null }> = ({ build }) => ( +const BuildName: FC<{ build?: BuildWithSteps }> = ({ build }) => ( #{build?.build_num} - {build?.subject} @@ -95,7 +95,7 @@ const BuildWithStepsPage: FC<{}> = () => { const { buildId = '' } = useParams(); const classes = useStyles(); const [settings] = useSettings(); - const [build, { startPolling, stopPolling }] = useBuildWithSteps( + const [{ loading, value }, { startPolling, stopPolling }] = useBuildWithSteps( parseInt(buildId, 10), ); @@ -112,11 +112,11 @@ const BuildWithStepsPage: FC<{}> = () => { } + className={pickClassName(classes, value)} + title={} cardClassName={classes.cardContent} > - + {loading ? : } @@ -125,7 +125,7 @@ const BuildWithStepsPage: FC<{}> = () => { ); }; -const BuildsList: FC<{ build: BuildWithSteps | null }> = ({ build }) => ( +const BuildsList: FC<{ build?: BuildWithSteps }> = ({ build }) => ( {build && build.steps && diff --git a/plugins/circleci/src/pages/BuildsPage/lib/Builds/Builds.tsx b/plugins/circleci/src/pages/BuildsPage/lib/Builds/Builds.tsx index 41001c91dd..bed96bb24c 100644 --- a/plugins/circleci/src/pages/BuildsPage/lib/Builds/Builds.tsx +++ b/plugins/circleci/src/pages/BuildsPage/lib/Builds/Builds.tsx @@ -13,73 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FC, useEffect } from 'react'; -import { CITableBuildInfo, CITable } from '../CITable'; -import { BuildSummary } from '../../../../api'; -import { useSettings, useBuilds } from '../../../../state'; - -const makeReadableStatus = (status: string | undefined) => { - if (!status) return ''; - return ({ - retried: 'Retried', - canceled: 'Canceled', - infrastructure_fail: 'Infra fail', - timedout: 'Timedout', - not_run: 'Not run', - running: 'Running', - failed: 'Failed', - queued: 'Queued', - scheduled: 'Scheduled', - not_running: 'Not running', - no_tests: 'No tests', - fixed: 'Fixed', - success: 'Success', - } as Record)[status]; -}; - -const transform = ( - buildsData: BuildSummary[], - restartBuild: { (buildId: number): Promise }, -): CITableBuildInfo[] => { - return buildsData.map((buildData) => { - const tableBuildInfo: CITableBuildInfo = { - id: String(buildData.build_num), - buildName: buildData.subject - ? buildData.subject + - (buildData.retry_of ? ` (retry of #${buildData.retry_of})` : '') - : '', - onRestartClick: () => - typeof buildData.build_num !== 'undefined' && - restartBuild(buildData.build_num), - source: { - branchName: String(buildData.branch), - commit: { - hash: String(buildData.vcs_revision), - url: 'todo', - }, - }, - status: makeReadableStatus(buildData.status), - buildUrl: buildData.build_url, - }; - return tableBuildInfo; - }); -}; +import React, { FC } from 'react'; +import { CITable } from '../CITable'; +import { useBuilds } from '../../../../state'; export const Builds: FC<{}> = () => { const [ - builds, - { restartBuild: handleRestartBuild, startPolling, stopPolling }, + { total, loading, value, projectName, page, pageSize }, + { setPage, retry, setPageSize }, ] = useBuilds(); - const [{ repo, owner }] = useSettings(); - - useEffect(() => { - startPolling(); - return () => stopPolling(); - }, [repo, owner]); - - const transformedBuilds = transform(builds, handleRestartBuild); - return ( - + ); }; diff --git a/plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx b/plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx index f8e0026ff5..b349dcfcbe 100644 --- a/plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx +++ b/plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx @@ -114,14 +114,46 @@ const generatedColumns: TableColumn[] = [ width: '10%', }, ]; -export const CITable: FC<{ + +type Props = { + loading: boolean; + retry: () => void; builds: CITableBuildInfo[]; projectName: string; -}> = React.memo(({ builds = [], projectName }) => { + page: number; + onChangePage: (page: number) => void; + total: number; + pageSize: number; + onChangePageSize: (pageSize: number) => void; +}; +export const CITable: FC = ({ + projectName, + loading, + pageSize, + page, + retry, + builds, + onChangePage, + onChangePageSize, + total, +}) => { return ( , + tooltip: 'Refresh Data', + isFreeAction: true, + onClick: () => retry(), + }, + ]} data={builds} + onChangePage={onChangePage} + onChangeRowsPerPage={onChangePageSize} title={ @@ -132,4 +164,4 @@ export const CITable: FC<{ columns={generatedColumns} /> ); -}); +}; diff --git a/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx b/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx deleted file mode 100644 index 62b874d70e..0000000000 --- a/plugins/circleci/src/pages/SettingsPage/SettingsPage.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React, { useState } from 'react'; -import { - Button, - TextField, - List, - Grid, - ListItem, - Snackbar, - Box, -} from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; -import { InfoCard, Content } from '@backstage/core'; -import { Layout } from '../../components/Layout'; -import { PluginHeader } from '../../components/PluginHeader'; -import { useSettings } from '../../state'; - -const SettingsPage = () => { - const [ - { repo: repoFromStore, owner: ownerFromStore, token: tokenFromStore }, - { saveSettings }, - ] = useSettings(); - - const [token, setToken] = React.useState(() => tokenFromStore); - const [owner, setOwner] = React.useState(() => ownerFromStore); - const [repo, setRepo] = React.useState(() => repoFromStore); - - React.useEffect(() => { - if (tokenFromStore !== token) { - setToken(tokenFromStore); - } - if (ownerFromStore !== owner) { - setOwner(ownerFromStore); - } - if (repoFromStore !== repo) { - setRepo(repoFromStore); - } - }, [ownerFromStore, repoFromStore, tokenFromStore]); - - const [saved, setSaved] = useState(false); - - return ( - - - - - - - - Project Credentials - {/* {authed ? : } */} - setSaved(false)} - > - Credentials saved. - - - } - > - - - setToken(e.target.value)} - /> - - - setOwner(e.target.value)} - /> - - - setRepo(e.target.value)} - /> - - - - - - - - - - - - - ); -}; - -export default SettingsPage; diff --git a/plugins/circleci/src/state/AppState.tsx b/plugins/circleci/src/state/AppState.tsx index ecea9e5221..2e98f86661 100644 --- a/plugins/circleci/src/state/AppState.tsx +++ b/plugins/circleci/src/state/AppState.tsx @@ -17,7 +17,6 @@ import React, { FC, useReducer, Dispatch, Reducer } from 'react'; import { circleCIApiRef } from '../api'; import { State, Action, SettingsState } from './types'; export { SettingsState }; -import equal from 'fast-deep-equal'; export const AppContext = React.createContext<[State, Dispatch]>( [] as any, @@ -25,13 +24,10 @@ export const AppContext = React.createContext<[State, Dispatch]>( export const STORAGE_KEY = `${circleCIApiRef.id}.settings`; const initialState: State = { - settings: { - owner: '', - repo: '', - token: '', - }, - builds: [], - buildsWithSteps: {}, + owner: '', + repo: '', + token: '', + showSettings: false, }; const reducer: Reducer = (state, action) => { @@ -39,23 +35,12 @@ const reducer: Reducer = (state, action) => { case 'setCredentials': return { ...state, - settings: { ...state.settings, ...action.payload }, + ...action.payload, }; - case 'setBuilds': - if (equal(action.payload, state.builds)) return state; - return { - ...state, - builds: action.payload, - }; - case 'setBuildWithSteps': { - return { - ...state, - buildsWithSteps: { - ...state.buildsWithSteps, - [action.payload.build_num!]: action.payload, - }, - }; - } + case 'showSettings': + return { ...state, showSettings: true }; + case 'hideSettings': + return { ...state, showSettings: false }; default: return state; } diff --git a/plugins/circleci/src/state/types.ts b/plugins/circleci/src/state/types.ts index de801ad213..41b3577082 100644 --- a/plugins/circleci/src/state/types.ts +++ b/plugins/circleci/src/state/types.ts @@ -13,41 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { BuildSummary, BuildWithSteps } from '../api'; -export type SettingsState = { - owner: string; - repo: string; - token: string; +export type Settings = { owner: string; repo: string; token: string }; +export type SettingsState = Settings & { + showSettings: boolean; }; -export type BuildsState = BuildSummary[]; +export type State = SettingsState; -export type State = { - settings: SettingsState; - builds: BuildsState; - buildsWithSteps: BuildsWithStepsState; -}; +type SettingsAction = + | { + type: 'setCredentials'; + payload: { + repo: string; + owner: string; + token: string; + }; + } + | { type: 'showSettings' } + | { type: 'hideSettings' }; -type SettingsAction = { - type: 'setCredentials'; - payload: { - repo: string; - owner: string; - token: string; - }; -}; - -type BuildsAction = { - type: 'setBuilds'; - payload: BuildSummary[]; -}; - -type BuildsWithStepsAction = { - type: 'setBuildWithSteps'; - payload: BuildWithSteps; -}; - -export type BuildsWithStepsState = Record; - -export type Action = SettingsAction | BuildsAction | BuildsWithStepsAction; +export type Action = SettingsAction; diff --git a/plugins/circleci/src/state/useBuildWithSteps.ts b/plugins/circleci/src/state/useBuildWithSteps.ts index 96d3a3f159..d77203cce0 100644 --- a/plugins/circleci/src/state/useBuildWithSteps.ts +++ b/plugins/circleci/src/state/useBuildWithSteps.ts @@ -14,50 +14,43 @@ * limitations under the License. */ import { errorApiRef, useApi } from '@backstage/core'; -import { useContext } from 'react'; +import { useCallback } from 'react'; import { circleCIApiRef, GitType } from '../api/index'; -import { AppContext } from '.'; import { useSettings } from './useSettings'; - import { useAsyncPolling } from './useAsyncPolling'; +import { useAsyncRetry } from 'react-use'; -const INTERVAL_AMOUNT = 3000; - +const INTERVAL_AMOUNT = 1500; export function useBuildWithSteps(buildId: number) { - const [settings] = useSettings(); - const [{ buildsWithSteps }, dispatch] = useContext(AppContext); + const [{ token, repo, owner }] = useSettings(); const api = useApi(circleCIApiRef); const errorApi = useApi(errorApiRef); - const { isPolling, startPolling, stopPolling } = useAsyncPolling( - () => getBuildWithSteps(), - INTERVAL_AMOUNT, - ); - - const getBuildWithSteps = async () => { + const getBuildWithSteps = useCallback(async () => { try { const options = { - token: settings.token, + token: token, vcs: { - owner: settings.owner, - repo: settings.repo, + owner: owner, + repo: repo, type: GitType.GITHUB, }, }; - const build = await api.getBuild(buildId, options); - if (isPolling) dispatch({ type: 'setBuildWithSteps', payload: build }); + const b = await api.getBuild(buildId, options); + return Promise.resolve(b); } catch (e) { errorApi.post(e); + return Promise.reject(e); } - }; + }, [token, owner, repo, buildId]); const restartBuild = async () => { try { await api.retry(buildId, { - token: settings.token, + token: token, vcs: { - owner: settings.owner, - repo: settings.repo, + owner: owner, + repo: repo, type: GitType.GITHUB, }, }); @@ -66,15 +59,22 @@ export function useBuildWithSteps(buildId: number) { } }; - const build = buildsWithSteps[buildId]; + const { loading, value, retry } = useAsyncRetry(() => getBuildWithSteps(), [ + getBuildWithSteps, + ]); + + const { startPolling, stopPolling } = useAsyncPolling( + getBuildWithSteps, + INTERVAL_AMOUNT, + ); return [ - build, + { loading, value, retry }, { restartBuild, + getBuildWithSteps, startPolling, stopPolling, - getBuildWithSteps, }, ] as const; } diff --git a/plugins/circleci/src/state/useBuilds.tsx b/plugins/circleci/src/state/useBuilds.tsx index d46ac642a5..4435c2b8bd 100644 --- a/plugins/circleci/src/state/useBuilds.tsx +++ b/plugins/circleci/src/state/useBuilds.tsx @@ -14,52 +14,102 @@ * limitations under the License. */ import { errorApiRef, useApi } from '@backstage/core'; -import { GitType } from 'circleci-api'; -import { useContext } from 'react'; +import { GitType, BuildSummary } from 'circleci-api'; +import { useState, useEffect, useCallback } from 'react'; import { circleCIApiRef } from '../api/index'; -import { AppContext } from '.'; -import { useAsyncPolling } from './useAsyncPolling'; +import { useAsyncRetry } from 'react-use'; +import { CITableBuildInfo } from '../pages/BuildsPage/lib/CITable'; +import { useSettings } from './useSettings'; -const INTERVAL_AMOUNT = 3000; +const makeReadableStatus = (status: string | undefined) => { + if (!status) return ''; + return ({ + retried: 'Retried', + canceled: 'Canceled', + infrastructure_fail: 'Infra fail', + timedout: 'Timedout', + not_run: 'Not run', + running: 'Running', + failed: 'Failed', + queued: 'Queued', + scheduled: 'Scheduled', + not_running: 'Not running', + no_tests: 'No tests', + fixed: 'Fixed', + success: 'Success', + } as Record)[status]; +}; + +export const transform = ( + buildsData: BuildSummary[], + restartBuild: { (buildId: number): Promise }, +): CITableBuildInfo[] => { + return buildsData.map((buildData) => { + const tableBuildInfo: CITableBuildInfo = { + id: String(buildData.build_num), + buildName: buildData.subject + ? buildData.subject + + (buildData.retry_of ? ` (retry of #${buildData.retry_of})` : '') + : '', + onRestartClick: () => + typeof buildData.build_num !== 'undefined' && + restartBuild(buildData.build_num), + source: { + branchName: String(buildData.branch), + commit: { + hash: String(buildData.vcs_revision), + url: 'todo', + }, + }, + status: makeReadableStatus(buildData.status), + buildUrl: buildData.build_url, + }; + return tableBuildInfo; + }); +}; export function useBuilds() { - const [{ builds, settings }, dispatch] = useContext(AppContext); + const [{ repo, owner, token }] = useSettings(); const api = useApi(circleCIApiRef); const errorApi = useApi(errorApiRef); - const { isPolling, startPolling, stopPolling } = useAsyncPolling( - () => getBuilds(), - INTERVAL_AMOUNT, - ); - const getBuilds = async () => { - if (settings.owner === '' || settings.repo === '') return; - try { - const newBuilds = await api.getBuilds({ - token: settings.token, - vcs: { - owner: settings.owner, - repo: settings.repo, - type: GitType.GITHUB, - }, - }); - if (isPolling) - dispatch({ - type: 'setBuilds', - payload: newBuilds, - }); - } catch (e) { - errorApi.post(e); - } - }; + const [total, setTotal] = useState(0); + const [page, setPage] = useState(0); + const [pageSize, setPageSize] = useState(5); + + const getBuilds = useCallback( + async ({ limit, offset }: { limit: number; offset: number }) => { + if (owner === '' || repo === '') { + return; + } + try { + return await api.getBuilds( + { limit, offset }, + { + token: token, + vcs: { + owner: owner, + repo: repo, + type: GitType.GITHUB, + }, + }, + ); + } catch (e) { + errorApi.post(e); + return Promise.reject(e); + } + }, + [repo, token, owner], + ); const restartBuild = async (buildId: number) => { try { await api.retry(buildId, { - token: settings.token, + token: token, vcs: { - owner: settings.owner, - repo: settings.repo, + owner: owner, + repo: repo, type: GitType.GITHUB, }, }); @@ -68,12 +118,35 @@ export function useBuilds() { } }; + useEffect(() => { + getBuilds({ limit: 1, offset: 0 }).then((b) => setTotal(b?.[0].build_num!)); + }, [repo]); + + const { loading, value, retry } = useAsyncRetry( + () => + getBuilds({ + offset: page * pageSize, + limit: pageSize, + }).then((builds) => transform(builds ?? [], restartBuild)), + [page, pageSize, getBuilds], + ); + + const projectName = `${owner}/${repo}`; return [ - builds, { + page, + pageSize, + loading, + value, + projectName, + total, + }, + { + getBuilds, + setPage, + setPageSize, restartBuild, - startPolling, - stopPolling, + retry, }, ] as const; } diff --git a/plugins/circleci/src/state/useSettings.ts b/plugins/circleci/src/state/useSettings.ts index 34b2f5e402..04ce83a0a9 100644 --- a/plugins/circleci/src/state/useSettings.ts +++ b/plugins/circleci/src/state/useSettings.ts @@ -14,12 +14,11 @@ * limitations under the License. */ import { useContext, useEffect } from 'react'; -import { AppContext, STORAGE_KEY, SettingsState } from '.'; +import { AppContext, STORAGE_KEY } from '.'; import { useApi, errorApiRef } from '@backstage/core'; - +import { Settings } from './types'; export function useSettings() { - const [{ settings }, dispatch] = useContext(AppContext); - + const [settings, dispatch] = useContext(AppContext); const errorApi = useApi(errorApiRef); @@ -45,20 +44,22 @@ export function useSettings() { rehydrate(); }, []); - const persist = (state: SettingsState) => { + const persist = (state: Settings) => { sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state)); }; return [ settings, { - saveSettings: (state: SettingsState) => { + saveSettings: (state: Settings) => { persist(state); dispatch({ type: 'setCredentials', payload: state, }); }, + showSettings: () => dispatch({ type: 'showSettings' }), + hideSettings: () => dispatch({ type: 'hideSettings' }), }, ] as const; } From d15789ea4a2e5824027c050a90e805e7a0889a99 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 18 May 2020 17:27:22 +0200 Subject: [PATCH 2/8] build(deps-dev): bump ts-node from 8.8.1 to 8.10.1 (#896) Bumps [ts-node](https://github.com/TypeStrong/ts-node) from 8.8.1 to 8.10.1. - [Release notes](https://github.com/TypeStrong/ts-node/releases) - [Commits](https://github.com/TypeStrong/ts-node/compare/v8.8.1...v8.10.1) Signed-off-by: dependabot-preview[bot] Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index c844afcb5a..103fb2ab9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18182,10 +18182,10 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.6, source-map-support@~0.5.12: - version "0.5.16" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== +source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.12: + version "0.5.19" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -19460,14 +19460,14 @@ ts-loader@^7.0.4: semver "^6.0.0" ts-node@^8.6.2: - version "8.8.1" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-8.8.1.tgz#7c4d3e9ed33aa703b64b28d7f9d194768be5064d" - integrity sha512-10DE9ONho06QORKAaCBpPiFCdW+tZJuY/84tyypGtl6r+/C7Asq0dhqbRZURuUlLQtZxxDvT8eoj8cGW0ha6Bg== + version "8.10.1" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-8.10.1.tgz#77da0366ff8afbe733596361d2df9a60fc9c9bd3" + integrity sha512-bdNz1L4ekHiJul6SHtZWs1ujEKERJnHs4HxN7rjTyyVOFf3HaJ6sLqe6aPG62XTzAB/63pKRh5jTSWL0D7bsvw== dependencies: arg "^4.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.6" + source-map-support "^0.5.17" yn "3.1.1" ts-pnp@^1.1.2: From dd93bc9ced36525599b12247a56ead66d51701b8 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Mon, 18 May 2020 17:56:36 +0200 Subject: [PATCH 3/8] feat: proxy Allowing to put a proxy field into a package.json for both app and plugin to use it with a wds build-in proxy --- packages/cli/src/commands/app/serve.ts | 8 +++++++- packages/cli/src/commands/plugin/serve.ts | 6 ++++++ packages/cli/src/lib/bundler/types.ts | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index 182e338287..58e9e5fc16 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -14,13 +14,19 @@ * limitations under the License. */ -import { serveBundle } from '../../lib/bundler'; +import fs from 'fs-extra'; import { Command } from 'commander'; +import { serveBundle } from '../../lib/bundler'; +import { paths } from '../../lib/paths'; export default async (cmd: Command) => { + const pkgPath = paths.resolveTarget('package.json'); + const pkg = await fs.readJson(pkgPath); + const waitForExit = await serveBundle({ entry: 'src/index', checksEnabled: cmd.check, + proxy: pkg.proxy, }); await waitForExit(); diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index 5700992644..f9768ec497 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -14,13 +14,19 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { serveBundle } from '../../lib/bundler'; import { Command } from 'commander'; +import { paths } from '../../lib/paths'; export default async (cmd: Command) => { + const pkgPath = paths.resolveTarget('package.json'); + const pkg = await fs.readJson(pkgPath); + const waitForExit = await serveBundle({ entry: 'dev/index', checksEnabled: cmd.check, + proxy: pkg.proxy, }); await waitForExit(); diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index 4182226d69..5e14760574 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -15,6 +15,7 @@ */ import { BundlingPathsOptions } from './paths'; +import { ProxyConfigMap } from 'webpack-dev-server'; export type BundlingOptions = { checksEnabled: boolean; @@ -23,6 +24,7 @@ export type BundlingOptions = { export type ServeOptions = BundlingPathsOptions & { checksEnabled: boolean; + proxy?: ProxyConfigMap; }; export type BuildOptions = BundlingPathsOptions & { From 0a592af4dedbb8a9660960a7080aa7c77daf0fe2 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Mon, 18 May 2020 17:59:12 +0200 Subject: [PATCH 4/8] feat: settings -> dialog --- packages/cli/src/commands/plugin/serve.ts | 6 ++ plugins/circleci/dev/index.tsx | 28 ++++++ plugins/circleci/package.json | 42 +++++--- .../src/components/Settings/Settings.tsx | 97 ++++++++++--------- plugins/circleci/src/state/AppState.tsx | 1 - plugins/circleci/src/state/index.ts | 3 + .../circleci/src/state/useBuildWithSteps.ts | 8 +- plugins/circleci/src/state/useBuilds.tsx | 5 - 8 files changed, 118 insertions(+), 72 deletions(-) create mode 100644 plugins/circleci/dev/index.tsx diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index 5700992644..f9768ec497 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -14,13 +14,19 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { serveBundle } from '../../lib/bundler'; import { Command } from 'commander'; +import { paths } from '../../lib/paths'; export default async (cmd: Command) => { + const pkgPath = paths.resolveTarget('package.json'); + const pkg = await fs.readJson(pkgPath); + const waitForExit = await serveBundle({ entry: 'dev/index', checksEnabled: cmd.check, + proxy: pkg.proxy, }); await waitForExit(); diff --git a/plugins/circleci/dev/index.tsx b/plugins/circleci/dev/index.tsx new file mode 100644 index 0000000000..ed7dd5de9c --- /dev/null +++ b/plugins/circleci/dev/index.tsx @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; +import { circleCIApiRef, CircleCIApi } from '../src/api'; + +createDevApp() + .registerPlugin(plugin) + .registerApiFactory({ + deps: {}, + factory: () => new CircleCIApi(), + implements: circleCIApiRef, + }) + .render(); diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index cd00135004..e3e0372d88 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -3,18 +3,31 @@ "version": "0.1.1-alpha.4", "main": "dist/index.esm.js", "module": "dist/index.esm.js", - "types": "dist/index.d.ts", + "types": "src/index.ts", "license": "Apache-2.0", "private": true, + "proxy": { + "/circleci/api": { + "target": "https://circleci.com/api/v1.1", + "changeOrigin": true, + "pathRewrite": { + "^/circleci/api/": "/" + } + } + }, "scripts": { "build": "backstage-cli plugin:build", "lint": "backstage-cli lint", "test": "backstage-cli test", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "diff": "backstage-cli plugin:diff", + "start": "backstage-cli plugin:serve", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.4", - "@backstage/theme": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.5", + "@backstage/theme": "^0.1.1-alpha.5", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -22,24 +35,25 @@ "circleci-api": "^4.0.0", "fast-deep-equal": "^3.1.1", "moment": "^2.25.3", - "react": "16.13.1", - "react-dom": "16.13.1", + "react": "^16.13.1", + "react-dom": "^16.13.1", "react-lazylog": "^4.5.2", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", - "react-use": "^13.0.0" + "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.4", - "@testing-library/jest-dom": "^4.2.4", + "@backstage/cli": "^0.1.1-alpha.5", + "@testing-library/jest-dom": "^5.7.0", "@testing-library/react": "^9.3.2", - "@testing-library/user-event": "^7.1.2", - "@types/jest": "^24.0.0", + "@testing-library/user-event": "^10.2.4", + "@types/jest": "^25.2.1", "@types/node": "^12.0.0", - "@types/testing-library__jest-dom": "5.0.2", - "jest-fetch-mock": "^3.0.3" + "@types/testing-library__jest-dom": "^5.0.4", + "jest-fetch-mock": "^3.0.3", + "@backstage/dev-utils": "^0.1.1-alpha.5" }, "files": [ - "dist" + "dist/**/*.{js,d.ts}" ] } diff --git a/plugins/circleci/src/components/Settings/Settings.tsx b/plugins/circleci/src/components/Settings/Settings.tsx index b33bf59dd8..510af583d2 100644 --- a/plugins/circleci/src/components/Settings/Settings.tsx +++ b/plugins/circleci/src/components/Settings/Settings.tsx @@ -23,6 +23,7 @@ import { Box, Dialog, DialogTitle, + DialogContent, } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import { useSettings } from '../../state'; @@ -71,54 +72,56 @@ const Settings = () => { Project Credentials {/* {authed ? : } */} - - - setToken(e.target.value)} - /> - - - setOwner(e.target.value)} - /> - - - setRepo(e.target.value)} - /> - - - - - - - + onChange={(e) => setToken(e.target.value)} + /> + + + setOwner(e.target.value)} + /> + + + setRepo(e.target.value)} + /> + + + + + + + + ); diff --git a/plugins/circleci/src/state/AppState.tsx b/plugins/circleci/src/state/AppState.tsx index 2e98f86661..720b660ccd 100644 --- a/plugins/circleci/src/state/AppState.tsx +++ b/plugins/circleci/src/state/AppState.tsx @@ -48,7 +48,6 @@ const reducer: Reducer = (state, action) => { export const AppStateProvider: FC = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); - return ( <>{children} diff --git a/plugins/circleci/src/state/index.ts b/plugins/circleci/src/state/index.ts index 4ef67f62ea..2321103eb4 100644 --- a/plugins/circleci/src/state/index.ts +++ b/plugins/circleci/src/state/index.ts @@ -14,3 +14,6 @@ * limitations under the License. */ export * from './AppState'; +export * from './useSettings'; +export * from './useBuilds'; +export * from './useBuildWithSteps'; diff --git a/plugins/circleci/src/state/useBuildWithSteps.ts b/plugins/circleci/src/state/useBuildWithSteps.ts index 4c9f6a0c42..8abc08f580 100644 --- a/plugins/circleci/src/state/useBuildWithSteps.ts +++ b/plugins/circleci/src/state/useBuildWithSteps.ts @@ -27,6 +27,9 @@ export function useBuildWithSteps(buildId: number) { const errorApi = useApi(errorApiRef); const getBuildWithSteps = useCallback(async () => { + if (owner === '' || repo === '' || token === '') { + return; + } try { const options = { token: token, @@ -44,11 +47,6 @@ export function useBuildWithSteps(buildId: number) { } }, [token, owner, repo, buildId]); - const { startPolling, stopPolling } = useAsyncPolling( - getBuildWithSteps, - INTERVAL_AMOUNT, - ); - const restartBuild = async () => { try { await api.retry(buildId, { diff --git a/plugins/circleci/src/state/useBuilds.tsx b/plugins/circleci/src/state/useBuilds.tsx index c29b05890c..4435c2b8bd 100644 --- a/plugins/circleci/src/state/useBuilds.tsx +++ b/plugins/circleci/src/state/useBuilds.tsx @@ -103,11 +103,6 @@ export function useBuilds() { [repo, token, owner], ); - const { startPolling, stopPolling } = useAsyncPolling( - getBuilds, - INTERVAL_AMOUNT, - ); - const restartBuild = async (buildId: number) => { try { await api.retry(buildId, { From 3d019401910f7da3f8ea9613716bf60bca9c2078 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Mon, 18 May 2020 18:06:19 +0200 Subject: [PATCH 5/8] fix: tsc --- plugins/circleci/src/components/Settings/Settings.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/circleci/src/components/Settings/Settings.tsx b/plugins/circleci/src/components/Settings/Settings.tsx index 510af583d2..5159c156cc 100644 --- a/plugins/circleci/src/components/Settings/Settings.tsx +++ b/plugins/circleci/src/components/Settings/Settings.tsx @@ -23,7 +23,6 @@ import { Box, Dialog, DialogTitle, - DialogContent, } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import { useSettings } from '../../state'; From aef288b6cbd27c3c70287dce2ea6bbc032017f4b Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Mon, 18 May 2020 18:19:56 +0200 Subject: [PATCH 6/8] refactor: move to serveBundle --- packages/cli/src/commands/app/serve.ts | 8 +------- packages/cli/src/commands/plugin/serve.ts | 6 ------ packages/cli/src/lib/bundler/server.ts | 4 ++++ packages/cli/src/lib/bundler/types.ts | 2 -- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index 58e9e5fc16..182e338287 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -14,19 +14,13 @@ * limitations under the License. */ -import fs from 'fs-extra'; -import { Command } from 'commander'; import { serveBundle } from '../../lib/bundler'; -import { paths } from '../../lib/paths'; +import { Command } from 'commander'; export default async (cmd: Command) => { - const pkgPath = paths.resolveTarget('package.json'); - const pkg = await fs.readJson(pkgPath); - const waitForExit = await serveBundle({ entry: 'src/index', checksEnabled: cmd.check, - proxy: pkg.proxy, }); await waitForExit(); diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index f9768ec497..5700992644 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -14,19 +14,13 @@ * limitations under the License. */ -import fs from 'fs-extra'; import { serveBundle } from '../../lib/bundler'; import { Command } from 'commander'; -import { paths } from '../../lib/paths'; export default async (cmd: Command) => { - const pkgPath = paths.resolveTarget('package.json'); - const pkg = await fs.readJson(pkgPath); - const waitForExit = await serveBundle({ entry: 'dev/index', checksEnabled: cmd.check, - proxy: pkg.proxy, }); await waitForExit(); diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 3aaf5968f5..ca224fdd1c 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import fs from 'fs-extra'; import yn from 'yn'; import webpack from 'webpack'; import WebpackDevServer from 'webpack-dev-server'; @@ -36,6 +37,8 @@ export async function serveBundle(options: ServeOptions) { const urls = prepareUrls(protocol, host, port); const paths = resolveBundlingPaths(options); + const pkgPath = paths.targetPackageJson; + const pkg = await fs.readJson(pkgPath); const config = createConfig(paths, { ...options, isDev: true }); const compiler = webpack(config); @@ -48,6 +51,7 @@ export async function serveBundle(options: ServeOptions) { https: protocol === 'https', host, port, + proxy: pkg.proxy, }); await new Promise((resolve, reject) => { diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index 5e14760574..4182226d69 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -15,7 +15,6 @@ */ import { BundlingPathsOptions } from './paths'; -import { ProxyConfigMap } from 'webpack-dev-server'; export type BundlingOptions = { checksEnabled: boolean; @@ -24,7 +23,6 @@ export type BundlingOptions = { export type ServeOptions = BundlingPathsOptions & { checksEnabled: boolean; - proxy?: ProxyConfigMap; }; export type BuildOptions = BundlingPathsOptions & { From e78eaa9bb630bb05d42ffda7ab910fc082208ecd Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Mon, 18 May 2020 18:44:19 +0200 Subject: [PATCH 7/8] fix: lint --- plugins/circleci/src/state/useBuildWithSteps.ts | 13 +++++++------ .../src/state/{useBuilds.tsx => useBuilds.ts} | 11 ++++++----- plugins/circleci/src/state/useSettings.ts | 5 +++-- 3 files changed, 16 insertions(+), 13 deletions(-) rename plugins/circleci/src/state/{useBuilds.tsx => useBuilds.ts} (94%) diff --git a/plugins/circleci/src/state/useBuildWithSteps.ts b/plugins/circleci/src/state/useBuildWithSteps.ts index 8abc08f580..7aba770851 100644 --- a/plugins/circleci/src/state/useBuildWithSteps.ts +++ b/plugins/circleci/src/state/useBuildWithSteps.ts @@ -15,10 +15,10 @@ */ import { errorApiRef, useApi } from '@backstage/core'; import { useCallback } from 'react'; -import { circleCIApiRef, GitType } from '../api/index'; -import { useSettings } from './useSettings'; -import { useAsyncPolling } from './useAsyncPolling'; import { useAsyncRetry } from 'react-use'; +import { circleCIApiRef, GitType } from '../api/index'; +import { useAsyncPolling } from './useAsyncPolling'; +import { useSettings } from './useSettings'; const INTERVAL_AMOUNT = 1500; export function useBuildWithSteps(buildId: number) { @@ -28,8 +28,9 @@ export function useBuildWithSteps(buildId: number) { const getBuildWithSteps = useCallback(async () => { if (owner === '' || repo === '' || token === '') { - return; + return Promise.reject('No credentials provided'); } + try { const options = { token: token, @@ -39,8 +40,8 @@ export function useBuildWithSteps(buildId: number) { type: GitType.GITHUB, }, }; - const b = await api.getBuild(buildId, options); - return Promise.resolve(b); + const build = await api.getBuild(buildId, options); + return Promise.resolve(build); } catch (e) { errorApi.post(e); return Promise.reject(e); diff --git a/plugins/circleci/src/state/useBuilds.tsx b/plugins/circleci/src/state/useBuilds.ts similarity index 94% rename from plugins/circleci/src/state/useBuilds.tsx rename to plugins/circleci/src/state/useBuilds.ts index 4435c2b8bd..248cc677f6 100644 --- a/plugins/circleci/src/state/useBuilds.tsx +++ b/plugins/circleci/src/state/useBuilds.ts @@ -14,10 +14,10 @@ * limitations under the License. */ import { errorApiRef, useApi } from '@backstage/core'; -import { GitType, BuildSummary } from 'circleci-api'; -import { useState, useEffect, useCallback } from 'react'; -import { circleCIApiRef } from '../api/index'; +import { BuildSummary, GitType } from 'circleci-api'; +import { useCallback, useEffect, useState } from 'react'; import { useAsyncRetry } from 'react-use'; +import { circleCIApiRef } from '../api/index'; import { CITableBuildInfo } from '../pages/BuildsPage/lib/CITable'; import { useSettings } from './useSettings'; @@ -80,9 +80,10 @@ export function useBuilds() { const getBuilds = useCallback( async ({ limit, offset }: { limit: number; offset: number }) => { - if (owner === '' || repo === '') { - return; + if (owner === '' || repo === '' || token === '') { + return Promise.reject('No credentials provided'); } + try { return await api.getBuilds( { limit, offset }, diff --git a/plugins/circleci/src/state/useSettings.ts b/plugins/circleci/src/state/useSettings.ts index 04ce83a0a9..5482abeb8b 100644 --- a/plugins/circleci/src/state/useSettings.ts +++ b/plugins/circleci/src/state/useSettings.ts @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { errorApiRef, useApi } from '@backstage/core'; import { useContext, useEffect } from 'react'; -import { AppContext, STORAGE_KEY } from '.'; -import { useApi, errorApiRef } from '@backstage/core'; +import { AppContext, STORAGE_KEY } from './AppState'; import { Settings } from './types'; + export function useSettings() { const [settings, dispatch] = useContext(AppContext); From f3da9cd7eff33d3f1b6810606773e52c838d8ffe Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Mon, 18 May 2020 18:52:41 +0200 Subject: [PATCH 8/8] fix: type imports --- plugins/circleci/src/state/AppState.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/circleci/src/state/AppState.tsx b/plugins/circleci/src/state/AppState.tsx index 720b660ccd..f229175111 100644 --- a/plugins/circleci/src/state/AppState.tsx +++ b/plugins/circleci/src/state/AppState.tsx @@ -15,7 +15,7 @@ */ import React, { FC, useReducer, Dispatch, Reducer } from 'react'; import { circleCIApiRef } from '../api'; -import { State, Action, SettingsState } from './types'; +import type { State, Action, SettingsState } from './types'; export { SettingsState }; export const AppContext = React.createContext<[State, Dispatch]>(