fix(circleci): big refactor and cleanup
This commit is contained in:
@@ -4,8 +4,6 @@ Website: [https://circleci.com/](https://circleci.com/)
|
||||
|
||||
<img src="./src/assets/screenshot-1.png" />
|
||||
<img src="./src/assets/screenshot-2.png" />
|
||||
<img src="./src/assets/screenshot-3.png" />
|
||||
<img src="./src/assets/screenshot-4.png" />
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -35,8 +33,39 @@ export default builder.build() as ApiHolder;
|
||||
export { plugin as Circleci } from '@backstage/plugin-circleci';
|
||||
```
|
||||
|
||||
3. Run app with `yarn start` and navigate to `/circleci/settings`
|
||||
4. Enter project settings and **project** token, acquired according to [https://circleci.com/docs/2.0/managing-api-tokens/](https://circleci.com/docs/2.0/managing-api-tokens/)
|
||||
3. Register the plugin router:
|
||||
|
||||
```jsx
|
||||
// packages/app/src/components/catalog/EntityPage.tsx
|
||||
|
||||
import { Router as CircleCIRouter } from '@backstage/plugin-circleci';
|
||||
|
||||
// Then somewhere inside <EntityPageLayout>
|
||||
<EntityPageLayout.Content
|
||||
path="/ci-cd/*"
|
||||
title="CI/CD"
|
||||
element={<CircleCIRouter />}
|
||||
/>;
|
||||
```
|
||||
|
||||
4. Add proxy config:
|
||||
|
||||
```
|
||||
// app-config.yaml
|
||||
proxy:
|
||||
'/circleci/api':
|
||||
target: https://circleci.com/api/v1.1
|
||||
changeOrigin: true
|
||||
pathRewrite:
|
||||
'^/proxy/circleci/api/': '/'
|
||||
headers:
|
||||
Circle-Token:
|
||||
$secret:
|
||||
env: CIRCLECI_AUTH_TOKEN
|
||||
```
|
||||
|
||||
5. Get and provide `CIRCLECI_AUTH_TOKEN` as env variable (https://circleci.com/docs/api/#add-an-api-token)
|
||||
6. Add `circleci.com/project-slug` annotation to your component-info.yaml file in format <git-provider>/<owner>/<project> (https://backstage.io/docs/architecture-decisions/adrs-adr002#format)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -50,3 +79,4 @@ export { plugin as Circleci } from '@backstage/plugin-circleci';
|
||||
## Limitations
|
||||
|
||||
- CircleCI has pretty strict rate limits per token, be careful with opened tabs
|
||||
- CircelCI doesn't provide a way to auth by 3rd party (e.g. GitHub) token, nor by calling their OAuth endpoints, which currently stands in the way of better auth integration with Backstage (https://discuss.circleci.com/t/circleci-api-authorization-with-github-token/5356)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 972 KiB After Width: | Height: | Size: 235 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 807 KiB After Width: | Height: | Size: 212 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 856 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 943 KiB |
+43
-54
@@ -15,20 +15,22 @@
|
||||
*/
|
||||
import React, { FC, useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Content, InfoCard, Progress } from '@backstage/core';
|
||||
import { InfoCard, Progress, Link } from '@backstage/core';
|
||||
import { BuildWithSteps, BuildStepAction } from '../../api';
|
||||
import { Grid, Box, Link, IconButton } from '@material-ui/core';
|
||||
import {
|
||||
Grid,
|
||||
Box,
|
||||
IconButton,
|
||||
Breadcrumbs,
|
||||
Typography,
|
||||
Link as MaterialLink,
|
||||
} from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { PluginHeader } from '../../components/PluginHeader';
|
||||
import { ActionOutput } from './lib/ActionOutput/ActionOutput';
|
||||
import { Layout } from '../../components/Layout';
|
||||
import LaunchIcon from '@material-ui/icons/Launch';
|
||||
import { useSettings } from '../../state/useSettings';
|
||||
import { useBuildWithSteps } from '../../state/useBuildWithSteps';
|
||||
import { AppStateProvider } from '../../state';
|
||||
import { Settings } from '../../components/Settings';
|
||||
|
||||
const IconLink = IconButton as typeof Link;
|
||||
const IconLink = (IconButton as any) as typeof MaterialLink;
|
||||
const BuildName: FC<{ build?: BuildWithSteps }> = ({ build }) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
#{build?.build_num} - {build?.subject}
|
||||
@@ -94,56 +96,13 @@ const pickClassName = (
|
||||
return classes.neutral;
|
||||
};
|
||||
|
||||
const Page = () => (
|
||||
<AppStateProvider>
|
||||
<Layout>
|
||||
<Content>
|
||||
<BuildWithStepsView />
|
||||
<Settings />
|
||||
</Content>
|
||||
</Layout>
|
||||
</AppStateProvider>
|
||||
);
|
||||
|
||||
const BuildWithStepsView: FC<{}> = () => {
|
||||
const { buildId = '' } = useParams();
|
||||
const classes = useStyles();
|
||||
const [settings] = useSettings();
|
||||
const [{ loading, value }, { startPolling, stopPolling }] = useBuildWithSteps(
|
||||
parseInt(buildId, 10),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
startPolling();
|
||||
return () => stopPolling();
|
||||
}, [buildId, settings, startPolling, stopPolling]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PluginHeader title="Build info" />
|
||||
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item>
|
||||
<InfoCard
|
||||
className={pickClassName(classes, value)}
|
||||
title={<BuildName build={value} />}
|
||||
cardClassName={classes.cardContent}
|
||||
>
|
||||
{loading ? <Progress /> : <BuildsList build={value} />}
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const BuildsList: FC<{ build?: BuildWithSteps }> = ({ build }) => (
|
||||
<Box>
|
||||
{build &&
|
||||
build.steps &&
|
||||
build.steps.map(
|
||||
({ name, actions }: { name: string; actions: BuildStepAction[] }) => (
|
||||
<ActionsList name={name} actions={actions} />
|
||||
<ActionsList key={name} name={name} actions={actions} />
|
||||
),
|
||||
)}
|
||||
</Box>
|
||||
@@ -167,5 +126,35 @@ const ActionsList: FC<{ actions: BuildStepAction[]; name: string }> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
export { BuildWithStepsView as BuildWithSteps };
|
||||
export const BuildWithStepsPage = () => {
|
||||
const { buildId = '' } = useParams();
|
||||
const classes = useStyles();
|
||||
const [{ loading, value }, { startPolling, stopPolling }] = useBuildWithSteps(
|
||||
parseInt(buildId, 10),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
startPolling();
|
||||
return () => stopPolling();
|
||||
}, [buildId, startPolling, stopPolling]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Breadcrumbs aria-label="breadcrumb">
|
||||
<Link to="..">All builds</Link>
|
||||
<Typography>Build details</Typography>
|
||||
</Breadcrumbs>
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item>
|
||||
<InfoCard
|
||||
className={pickClassName(classes, value)}
|
||||
title={<BuildName build={value} />}
|
||||
cardClassName={classes.cardContent}
|
||||
>
|
||||
{loading ? <Progress /> : <BuildsList build={value} />}
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+1
-1
@@ -13,4 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './PluginHeader';
|
||||
export { BuildWithStepsPage } from './BuildWithStepsPage';
|
||||
+11
-1
@@ -13,4 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { default as Settings } from './Settings';
|
||||
import React from 'react';
|
||||
import { Builds } from './lib/Builds';
|
||||
import { Grid } from '@material-ui/core';
|
||||
|
||||
export const BuildsPage = () => (
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item>
|
||||
<Builds />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
+1
-1
@@ -13,4 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './Layout';
|
||||
export { BuildsPage } from './BuildsPage';
|
||||
+6
-2
@@ -17,7 +17,7 @@ import React, { FC } from 'react';
|
||||
import { Link, Typography, Box, IconButton } from '@material-ui/core';
|
||||
import RetryIcon from '@material-ui/icons/Replay';
|
||||
import GitHubIcon from '@material-ui/icons/GitHub';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { Link as RouterLink, generatePath } from 'react-router-dom';
|
||||
import {
|
||||
StatusError,
|
||||
StatusWarning,
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
Table,
|
||||
TableColumn,
|
||||
} from '@backstage/core';
|
||||
import { circleCIBuildRouteRef } from '../../../../route-refs';
|
||||
|
||||
export type CITableBuildInfo = {
|
||||
id: string;
|
||||
@@ -80,7 +81,10 @@ const generatedColumns: TableColumn[] = [
|
||||
field: 'buildName',
|
||||
highlight: true,
|
||||
render: (row: Partial<CITableBuildInfo>) => (
|
||||
<Link component={RouterLink} to={`/circleci/build/${row.id}`}>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`${generatePath(circleCIBuildRouteRef.path, { buildId: row.id! })}`}
|
||||
>
|
||||
{row.buildName}
|
||||
</Link>
|
||||
),
|
||||
@@ -1,38 +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 from 'react';
|
||||
import { Route, MemoryRouter, Routes } from 'react-router';
|
||||
import { Builds } from '../pages/BuildsPage';
|
||||
import { BuildWithSteps } from '../pages/BuildWithStepsPage';
|
||||
import { AppStateProvider } from '../state';
|
||||
import { Settings } from './Settings';
|
||||
|
||||
// TODO: allow pass in settings as props
|
||||
// When some shared settings workflow
|
||||
// will be established
|
||||
export const CircleCIWidget = () => (
|
||||
<MemoryRouter initialEntries={['/circleci']}>
|
||||
<AppStateProvider>
|
||||
<>
|
||||
<Routes>
|
||||
<Route path="/circleci" element={<Builds />} />
|
||||
<Route path="/circleci/build/:buildId" element={<BuildWithSteps />} />
|
||||
</Routes>
|
||||
<Settings />
|
||||
</>
|
||||
</AppStateProvider>
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -1,29 +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 from 'react';
|
||||
import { Header, Page, pageTheme, HeaderLabel } from '@backstage/core';
|
||||
|
||||
export const Layout: React.FC = ({ children }) => {
|
||||
return (
|
||||
<Page theme={pageTheme.tool}>
|
||||
<Header title="CircleCI" subtitle="See recent builds and their status">
|
||||
<HeaderLabel label="Owner" value="Spotify" />
|
||||
<HeaderLabel label="Lifecycle" value="Alpha" />
|
||||
</Header>
|
||||
{children}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
@@ -1,55 +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, { FC } from 'react';
|
||||
import { Link as RouterLink, useLocation } from 'react-router-dom';
|
||||
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<Props> = ({ title = 'CircleCI' }) => {
|
||||
const [, { showSettings }] = useSettings();
|
||||
const location = useLocation();
|
||||
const notRoot = !location.pathname.match(/\/circleci\/?$/);
|
||||
const isSettingsPage = location.pathname.match(/\/circleci\/settings\/?/);
|
||||
return (
|
||||
<ContentHeader
|
||||
title={title}
|
||||
titleComponent={() => (
|
||||
<Box alignItems="center" display="flex">
|
||||
{notRoot && (
|
||||
<IconButton component={RouterLink} to="/circleci">
|
||||
<ArrowBack />
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography variant="h4">{title}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
>
|
||||
{!isSettingsPage && (
|
||||
<Button onClick={showSettings} startIcon={<SettingsIcon />}>
|
||||
Settings
|
||||
</Button>
|
||||
)}
|
||||
<SupportButton>
|
||||
This plugin allows you to view and interact with your builds within the
|
||||
Circle CI environment.
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
);
|
||||
};
|
||||
@@ -1,129 +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, useEffect } 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] = useState(() => tokenFromStore);
|
||||
const [owner, setOwner] = useState(() => ownerFromStore);
|
||||
const [repo, setRepo] = useState(() => repoFromStore);
|
||||
|
||||
useEffect(() => {
|
||||
if (tokenFromStore !== token) {
|
||||
setToken(token);
|
||||
}
|
||||
if (ownerFromStore !== owner) {
|
||||
setOwner(owner);
|
||||
}
|
||||
if (repoFromStore !== repo) {
|
||||
setRepo(repo);
|
||||
}
|
||||
}, [ownerFromStore, repoFromStore, tokenFromStore, token, owner, repo]);
|
||||
|
||||
const [saved, setSaved] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Snackbar
|
||||
autoHideDuration={1000}
|
||||
open={saved}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||
onClose={() => setSaved(false)}
|
||||
>
|
||||
<Alert severity="success">Credentials saved.</Alert>
|
||||
</Snackbar>
|
||||
<Dialog open={showSettings} onClose={hideSettings}>
|
||||
<DialogTitle>
|
||||
Project Credentials
|
||||
{/* {authed ? <StatusOK /> : <StatusFailed />} */}
|
||||
</DialogTitle>
|
||||
<Box minWidth="400px">
|
||||
<List>
|
||||
<ListItem>
|
||||
<TextField
|
||||
name="circleci-token"
|
||||
label="Token"
|
||||
value={token}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onChange={e => setToken(e.target.value)}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<TextField
|
||||
name="circleci-owner"
|
||||
fullWidth
|
||||
label="Owner"
|
||||
variant="outlined"
|
||||
value={owner}
|
||||
onChange={e => setOwner(e.target.value)}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<TextField
|
||||
name="circleci-repo"
|
||||
label="Repo"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={repo}
|
||||
onChange={e => setRepo(e.target.value)}
|
||||
/>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Box mt={2} display="flex" width="100%" justifyContent="center">
|
||||
<Button
|
||||
data-testid="github-auth-button"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setSaved(true);
|
||||
saveSettings({ repo, owner, token });
|
||||
hideSettings();
|
||||
}}
|
||||
>
|
||||
Save credentials
|
||||
</Button>
|
||||
</Box>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Box>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
@@ -1,19 +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.
|
||||
*/
|
||||
export {
|
||||
default as DetailedViewPage,
|
||||
BuildWithSteps,
|
||||
} from './BuildWithStepsPage';
|
||||
@@ -1,48 +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, { FC } from 'react';
|
||||
import { Content } from '@backstage/core';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import { Builds as BuildsComp } from './lib/Builds';
|
||||
import { Layout } from '../../components/Layout';
|
||||
import { PluginHeader } from '../../components/PluginHeader';
|
||||
import { AppStateProvider } from '../../state/AppState';
|
||||
import { Settings } from '../../components/Settings';
|
||||
|
||||
const BuildsPage: FC<{}> = () => (
|
||||
<AppStateProvider>
|
||||
<Layout>
|
||||
<Content>
|
||||
<Builds />
|
||||
<Settings />
|
||||
</Content>
|
||||
</Layout>
|
||||
</AppStateProvider>
|
||||
);
|
||||
|
||||
const Builds = () => (
|
||||
<>
|
||||
<PluginHeader title="All builds" />
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item>
|
||||
<BuildsComp />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
|
||||
export default BuildsPage;
|
||||
export { Builds };
|
||||
@@ -1,16 +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.
|
||||
*/
|
||||
export { default as BuildsPage, Builds } from './BuildsPage';
|
||||
@@ -1,57 +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, { FC, useReducer, Dispatch, Reducer } from 'react';
|
||||
import { circleCIApiRef } from '../api';
|
||||
import type { State, Action, SettingsState } from './types';
|
||||
|
||||
export type { SettingsState };
|
||||
|
||||
export const AppContext = React.createContext<[State, Dispatch<Action>]>(
|
||||
[] as any,
|
||||
);
|
||||
export const STORAGE_KEY = `${circleCIApiRef.id}.settings`;
|
||||
|
||||
const initialState: State = {
|
||||
owner: '',
|
||||
repo: '',
|
||||
token: '',
|
||||
showSettings: false,
|
||||
};
|
||||
|
||||
const reducer: Reducer<State, Action> = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'setCredentials':
|
||||
return {
|
||||
...state,
|
||||
...action.payload,
|
||||
};
|
||||
case 'showSettings':
|
||||
return { ...state, showSettings: true };
|
||||
case 'hideSettings':
|
||||
return { ...state, showSettings: false };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const AppStateProvider: FC = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
return (
|
||||
<AppContext.Provider value={[state, dispatch]}>
|
||||
<>{children}</>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -13,7 +13,5 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from './AppState';
|
||||
export * from './useSettings';
|
||||
export * from './useBuilds';
|
||||
export * from './useBuildWithSteps';
|
||||
|
||||
@@ -1,36 +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.
|
||||
*/
|
||||
|
||||
export type Settings = { owner: string; repo: string; token: string };
|
||||
export type SettingsState = Settings & {
|
||||
showSettings: boolean;
|
||||
};
|
||||
|
||||
export type State = SettingsState;
|
||||
|
||||
type SettingsAction =
|
||||
| {
|
||||
type: 'setCredentials';
|
||||
payload: {
|
||||
repo: string;
|
||||
owner: string;
|
||||
token: string;
|
||||
};
|
||||
}
|
||||
| { type: 'showSettings' }
|
||||
| { type: 'hideSettings' };
|
||||
|
||||
export type Action = SettingsAction;
|
||||
@@ -1,68 +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 { errorApiRef, useApi } from '@backstage/core';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { AppContext, STORAGE_KEY } from './AppState';
|
||||
import { Settings } from './types';
|
||||
|
||||
export function useSettings() {
|
||||
const [settings, dispatch] = useContext(AppContext);
|
||||
|
||||
const errorApi = useApi(errorApiRef);
|
||||
|
||||
useEffect(() => {
|
||||
const rehydrate = () => {
|
||||
try {
|
||||
const stateFromStorage = JSON.parse(
|
||||
sessionStorage.getItem(STORAGE_KEY)!,
|
||||
);
|
||||
if (
|
||||
stateFromStorage &&
|
||||
Object.keys(stateFromStorage).some(
|
||||
k => (settings as any)[k] !== stateFromStorage[k],
|
||||
)
|
||||
)
|
||||
dispatch({
|
||||
type: 'setCredentials',
|
||||
payload: stateFromStorage,
|
||||
});
|
||||
} catch (error) {
|
||||
errorApi.post(error);
|
||||
}
|
||||
};
|
||||
|
||||
rehydrate();
|
||||
}, [dispatch, errorApi, settings]);
|
||||
|
||||
const persist = (state: Settings) => {
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||
};
|
||||
|
||||
return [
|
||||
settings,
|
||||
{
|
||||
saveSettings: (state: Settings) => {
|
||||
persist(state);
|
||||
dispatch({
|
||||
type: 'setCredentials',
|
||||
payload: state,
|
||||
});
|
||||
},
|
||||
showSettings: () => dispatch({ type: 'showSettings' }),
|
||||
hideSettings: () => dispatch({ type: 'hideSettings' }),
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
Reference in New Issue
Block a user