Created gcp-projects plugin

This commit is contained in:
ebarrios
2020-07-14 07:16:20 +02:00
parent 240b183a01
commit 24c509b9e2
24 changed files with 970 additions and 8 deletions
+2 -2
View File
@@ -4,6 +4,6 @@
# The last matching pattern takes precedence.
# https://help.github.com/articles/about-codeowners/
* @spotify/backstage-core
/plugins/techdocs @spotify/techdocs-core
* @spotify/backstage-core
/packages/techdocs-cli @spotify/techdocs-core
/plugins/techdocs @spotify/techdocs-core
+3
View File
@@ -53,5 +53,8 @@
"*.{json,md}": [
"prettier --write"
]
},
"dependencies": {
"ts-node": "^8.10.2"
}
}
+1
View File
@@ -8,6 +8,7 @@
"@backstage/plugin-catalog": "^0.1.1-alpha.13",
"@backstage/plugin-circleci": "^0.1.1-alpha.13",
"@backstage/plugin-explore": "^0.1.1-alpha.13",
"@backstage/plugin-gcp-projects": "^0.1.1-alpha.13",
"@backstage/plugin-github-actions": "^0.1.1-alpha.13",
"@backstage/plugin-gitops-profiles": "^0.1.1-alpha.13",
"@backstage/plugin-graphiql": "^0.1.1-alpha.13",
+2
View File
@@ -58,6 +58,7 @@ import {
import { scaffolderApiRef, ScaffolderApi } from '@backstage/plugin-scaffolder';
import { rollbarApiRef, RollbarClient } from '@backstage/plugin-rollbar';
import { GCPClient, GCPApiRef } from '@backstage/plugin-gcp-projects';
export const apis = (config: ConfigApi) => {
// eslint-disable-next-line no-console
@@ -75,6 +76,7 @@ export const apis = (config: ConfigApi) => {
builder.add(storageApiRef, WebStorage.create({ errorApi }));
builder.add(circleCIApiRef, new CircleCIApi());
builder.add(GCPApiRef, new GCPClient());
builder.add(featureFlagsApiRef, new FeatureFlags());
builder.add(lighthouseApiRef, new LighthouseRestApi('http://localhost:3003'));
+1
View File
@@ -27,3 +27,4 @@ export { plugin as TechDocs } from '@backstage/plugin-techdocs';
export { plugin as GraphiQL } from '@backstage/plugin-graphiql';
export { plugin as GithubActions } from '@backstage/plugin-github-actions';
export { plugin as Rollbar } from '@backstage/plugin-rollbar';
export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects';
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+13
View File
@@ -0,0 +1,13 @@
# gcp-projects
Welcome to the gcp-projects plugin!
_This plugin was created through the Backstage CLI_
## Getting started
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/gcp-projects](http://localhost:3000/gcp-projects).
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory.
+22
View File
@@ -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 { createDevApp } from '@backstage/dev-utils';
import { plugin } from '../src/plugin';
createDevApp()
.registerPlugin(plugin)
.render();
+47
View File
@@ -0,0 +1,47 @@
{
"name": "@backstage/plugin-gcp-projects",
"version": "0.1.1-alpha.13",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"scripts": {
"build": "backstage-cli plugin:build",
"start": "backstage-cli plugin:serve",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"diff": "backstage-cli plugin:diff",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.13",
"@backstage/theme": "^0.1.1-alpha.13",
"@material-ui/core": "^4.9.1",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-use": "^14.2.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.13",
"@backstage/dev-utils": "^0.1.1-alpha.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^25.2.2",
"@types/node": "^12.0.0",
"jest-fetch-mock": "^3.0.3"
},
"files": [
"dist"
]
}
+34
View File
@@ -0,0 +1,34 @@
/*
* 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 { createApiRef } from '@backstage/core';
import { Project, Operation } from './types';
export const GCPApiRef = createApiRef<GCPApi>({
id: 'plugin.gcpprojects.service',
description: 'Used by the GCP Projects plugin to make requests',
});
export type GCPApi = {
listProjects: ({ token }: { token: string }) => Promise<Project[]>;
getProject: (projectId: string, token: Promise<string>) => Promise<Project>;
createProject: (
projectName: string,
projectId: string,
owner: string,
token: string,
) => Promise<Operation>;
};
+125
View File
@@ -0,0 +1,125 @@
/*
* 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 { GCPApi } from './GCPApi';
import { Project, Operation, Status } from './types';
export class GCPClient implements GCPApi {
async listProjects({ token }: { token: string }): Promise<Project[]> {
const url = `https://content-cloudresourcemanager.googleapis.com/v1/projects`;
const response = await fetch(url, {
headers: new Headers({
Accept: '*/*',
Authorization: `Bearer ${token}`,
}),
});
if (!response.ok) {
return [
{
name: 'Error',
projectNumber: 'Response status is not OK',
projectId: 'Error',
lifecycleState: 'error',
createTime: 'Error',
},
];
}
const data = await response.json();
return data.projects;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getProject(
projectId: string,
token: Promise<string>,
): Promise<Project> {
const url = `https://content-cloudresourcemanager.googleapis.com/v1/projects/${projectId}`;
const response = await fetch(url, {
headers: new Headers({
Authorization: `Bearer ${await token}`,
}),
});
const dataBlank: Project = {
name: 'Error',
projectNumber: `Response status is${response.status}`,
projectId: 'Error',
lifecycleState: 'error',
createTime: 'Error',
};
if (!response.ok) {
return dataBlank;
}
const data = await response.json();
const newData: Project = data;
return newData;
}
async createProject(
projectName: string,
projectId: string,
token: string,
): Promise<Operation> {
const status: Status = {
code: 0,
message: '',
details: [],
};
const op: Operation = {
name: '',
metadata: '',
done: true,
error: status,
response: '',
};
const newProject: Project = {
name: projectName,
projectId: projectId,
};
const body = JSON.stringify(newProject);
const url = `https://content-cloudresourcemanager.googleapis.com/v1/projects`;
const response = await fetch(url, {
headers: new Headers({
Accept: '*/*',
Authorization: `Bearer ${token}`,
}),
body: body,
method: 'POST',
});
if (!response.ok) {
status.code = response.status;
return op;
}
const data = await response.json();
return data;
}
}
+19
View File
@@ -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 './GCPApi';
export * from './GCPClient';
export * from './types';
+41
View File
@@ -0,0 +1,41 @@
/*
* 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 Project = {
name: string;
projectNumber?: string;
projectId: string;
lifecycleState?: string;
createTime?: string;
};
export type ProjectDetails = {
details: string;
};
export type Operation = {
name: string;
metadata: string;
done: boolean;
error: Status;
response: string;
};
export type Status = {
code: number;
message: string;
details: string[];
};
@@ -0,0 +1,119 @@
/*
* 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, useState } from 'react';
import { Grid, Button, TextField } from '@material-ui/core';
import {
InfoCard,
Header,
Page,
pageTheme,
Content,
ContentHeader,
HeaderLabel,
SupportButton,
SimpleStepper,
SimpleStepperStep,
StructuredMetadataTable,
} from '@backstage/core';
export const NewProjectPage: FC<{}> = () => {
const [projectName, setProjectName] = useState('');
const [projectId, setProjectId] = useState('');
const [done, setDone] = useState(false);
const metadata = {
ProjectName: projectName,
ProjectId: projectId,
};
return (
<Page theme={pageTheme.tool}>
<Header title="GCP - Projects" subtitle="Create new GCP Project">
<HeaderLabel label="Owner" value="Infrastructure Operations" />
<HeaderLabel label="Lifecycle" value="Alpha" />
</Header>
<Content>
<ContentHeader title="Create new GCP Project at trivago">
<SupportButton>
This plugin will help you create a project in gcp.
</SupportButton>
</ContentHeader>
<Grid container spacing={3}>
<Grid item xs={12} md={4}>
<InfoCard>
<SimpleStepper>
<SimpleStepperStep title="Project Name">
<TextField
variant="outlined"
name="projectName"
label="Project Name"
helperText="The name of the new project."
inputProps={{ 'aria-label': 'Project Name' }}
onChange={e => setProjectName(e.target.value)}
value={projectName}
fullWidth
/>
</SimpleStepperStep>
<SimpleStepperStep title="Project ID">
<TextField
variant="outlined"
name="projectId"
label="projectId"
onChange={e => setProjectId(e.target.value)}
value={projectId}
fullWidth
/>
</SimpleStepperStep>
<SimpleStepperStep title="" end>
<Button
variant="contained"
color="primary"
onClick={() => setDone(true)}
>
Finish
</Button>
</SimpleStepperStep>
</SimpleStepper>
</InfoCard>
</Grid>
{done === true ? (
<Grid item xs={12} md={8}>
<InfoCard title="Project Info:">
<StructuredMetadataTable metadata={metadata} />
<br />
<br />
<br />
<Button
variant="contained"
color="primary"
href={`newProject?projectName=${encodeURIComponent(
projectName,
)},projectId=${encodeURIComponent(projectId)}`}
>
Confirm
</Button>
</InfoCard>
</Grid>
) : (
<br />
)}
</Grid>
</Content>
</Page>
);
};
@@ -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 { NewProjectPage } from './NewProjectPage';
@@ -0,0 +1,153 @@
/*
* 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 {
Button,
ButtonGroup,
LinearProgress,
makeStyles,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
Theme,
Typography,
} from '@material-ui/core';
import React from 'react';
import { useLocation } from 'react-router-dom';
import { useAsync } from 'react-use';
import { Link, useApi, googleAuthApiRef } from '@backstage/core';
import { GCPApiRef } from '../../api';
const useStyles = makeStyles<Theme>(theme => ({
root: {
maxWidth: 720,
margin: theme.spacing(2),
},
title: {
padding: theme.spacing(1, 0, 2, 0),
},
table: {
padding: theme.spacing(1),
},
}));
export const ProjectDetailsPage = () => {
const api = useApi(GCPApiRef);
const googleApi = useApi(googleAuthApiRef);
const token = googleApi.getAccessToken(
'https://www.googleapis.com/auth/cloud-platform.read-only',
);
const classes = useStyles();
const location = useLocation();
const status = useAsync(
() =>
api.getProject(
decodeURIComponent(location.search.split('projectId=')[1]),
token,
),
[location.search],
);
if (status.loading) {
return <LinearProgress />;
} else if (status.error) {
return (
<Typography variant="h6" color="error">
Failed to load build, {status.error.message}
</Typography>
);
}
const details = status.value;
return (
<div className={classes.root}>
<Typography className={classes.title} variant="h3">
<Link to="/gcp-projects">
<Typography component="span" variant="h3" color="primary">
&lt;
</Typography>
</Link>
Build Details
</Typography>
<TableContainer component={Paper} className={classes.table}>
<Table>
<TableBody>
<TableRow>
<TableCell>
<Typography noWrap>Name</Typography>
</TableCell>
<TableCell>{details?.name}</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Project Number</Typography>
</TableCell>
<TableCell>{details?.projectNumber}</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Project ID</Typography>
</TableCell>
<TableCell>{details?.projectId}</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>State</Typography>
</TableCell>
<TableCell>
<TableCell>{details?.lifecycleState}</TableCell>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Creation Time</Typography>
</TableCell>
<TableCell>{details?.createTime}</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography noWrap>Links</Typography>
</TableCell>
<TableCell>
<ButtonGroup
variant="text"
color="primary"
aria-label="text primary button group"
>
{details?.name && (
<Button>
<a href={details.name}>GCP</a>
</Button>
)}
{details?.name && (
<Button>
<a href={details.name}>Logs</a>
</Button>
)}
</ButtonGroup>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</div>
);
};
@@ -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 { ProjectDetailsPage } from './ProjectDetailsPage';
@@ -0,0 +1,155 @@
/*
* 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.
*/
// NEEDS WORK
import { Link, useApi, googleAuthApiRef, InfoCard } from '@backstage/core';
import {
LinearProgress,
makeStyles,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Theme,
Tooltip,
Typography,
Button,
} from '@material-ui/core';
import React from 'react';
import { useAsync } from 'react-use';
import { GCPApiRef, Project } from '../../api';
const LongText = ({ text, max }: { text: string; max: number }) => {
if (text.length < max) {
return <span>{text}</span>;
}
return (
<Tooltip title={text}>
<span>{text.slice(0, max)}...</span>
</Tooltip>
);
};
const useStyles = makeStyles<Theme>(theme => ({
root: {
padding: theme.spacing(2),
},
title: {
padding: theme.spacing(1, 0, 2, 0),
},
}));
const PageContents = () => {
const api = useApi(GCPApiRef);
const googleApi = useApi(googleAuthApiRef);
const { loading, error, value } = useAsync(async () => {
const token = await googleApi.getAccessToken(
'https://www.googleapis.com/auth/cloud-platform.read-only',
);
const projects = api.listProjects({ token });
return projects;
});
if (loading) {
return <LinearProgress />;
}
if (error) {
return (
<Typography variant="h2" color="error">
Failed to load projects, {error.message}{' '}
</Typography>
);
}
return (
<TableContainer component={Paper}>
<Table aria-label="GCP Projects table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Project Number</TableCell>
<TableCell>Project ID</TableCell>
<TableCell>State</TableCell>
<TableCell>Creation Time</TableCell>
</TableRow>
</TableHead>
<TableBody>
{value?.map((project: Project) => (
<TableRow key={project.projectId}>
<TableCell>
<Typography>
<LongText text={project.name} max={30} />
</Typography>
</TableCell>
<TableCell>
<Typography>
<LongText text={project.projectNumber} max={30} />
</Typography>
</TableCell>
<TableCell>
<Link
to={`project?projectId=${encodeURIComponent(
project.projectId,
)}`}
>
<Typography color="primary">
<LongText text={project.projectId} max={60} />
</Typography>
</Link>
</TableCell>
<TableCell>
<Typography>
<LongText text={project.lifecycleState} max={30} />
</Typography>
</TableCell>
<TableCell>
<Typography>
<LongText text={project.createTime} max={30} />
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
};
export const ProjectListPage = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<Typography variant="h3" className={classes.title}>
GCP Projects
</Typography>
<InfoCard>
<Button variant="contained" color="primary" href="/gcp-projects/new">
Create new GCP Project
</Button>
</InfoCard>
<PageContents />
</div>
);
};
@@ -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 { ProjectListPage } from './ProjectListPage';
+18
View File
@@ -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 { plugin } from './plugin';
export * from './api';
+23
View File
@@ -0,0 +1,23 @@
/*
* 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 { plugin } from './plugin';
describe('gcp-projects', () => {
it('should export plugin', () => {
expect(plugin).toBeDefined();
});
});
+42
View File
@@ -0,0 +1,42 @@
/*
* 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 { createPlugin, createRouteRef } from '@backstage/core';
import { ProjectListPage } from './components/ProjectListPage';
import { ProjectDetailsPage } from './components/ProjectDetailsPage';
import { NewProjectPage } from './components/NewProjectPage';
export const rootRouteRef = createRouteRef({
path: '/gcp-projects',
title: 'GCP Projects',
});
export const ProjectRouteRef = createRouteRef({
path: '/gcp-projects/project',
title: 'GCP Project Page',
});
export const NewProjectRouteRef = createRouteRef({
path: '/gcp-projects/new',
title: 'GCP Project Page',
});
export const plugin = createPlugin({
id: 'gcp-projects',
register({ router }) {
router.addRoute(rootRouteRef, ProjectListPage);
router.addRoute(ProjectRouteRef, ProjectDetailsPage);
router.addRoute(NewProjectRouteRef, NewProjectPage);
},
});
+19
View File
@@ -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.
*/
import '@testing-library/jest-dom';
require('jest-fetch-mock').enableMocks();
+77 -6
View File
@@ -9942,6 +9942,18 @@ highlight.js@~9.15.0, highlight.js@~9.15.1:
resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2"
integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==
history@^4.9.0:
version "4.10.1"
resolved "https://registry.npmjs.org/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3"
integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==
dependencies:
"@babel/runtime" "^7.1.2"
loose-envify "^1.2.0"
resolve-pathname "^3.0.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
value-equal "^1.0.1"
history@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/history/-/history-5.0.0.tgz#0cabbb6c4bbf835addb874f8259f6d25101efd08"
@@ -9958,7 +9970,7 @@ hmac-drbg@^1.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -11090,6 +11102,11 @@ is-yarn-global@^0.3.0:
resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232"
integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -12430,7 +12447,7 @@ loglevel@^1.6.8:
resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"
integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@@ -12889,6 +12906,14 @@ min-indent@^1.0.0:
resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256"
integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=
mini-create-react-context@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040"
integrity sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==
dependencies:
"@babel/runtime" "^7.5.5"
tiny-warning "^1.0.3"
mini-css-extract-plugin@^0.7.0:
version "0.7.0"
resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.7.0.tgz#5ba8290fbb4179a43dd27cca444ba150bee743a0"
@@ -14369,6 +14394,13 @@ path-to-regexp@2.2.1:
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
dependencies:
isarray "0.0.1"
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@@ -15646,7 +15678,7 @@ react-inspector@^4.0.0:
is-dom "^1.0.9"
prop-types "^15.6.1"
react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0:
react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0:
version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -15725,6 +15757,35 @@ react-router-dom@6.0.0-beta.0:
prop-types "^15.7.2"
react-router "6.0.0-beta.0"
react-router-dom@^5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662"
integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
loose-envify "^1.3.1"
prop-types "^15.6.2"
react-router "5.2.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router@5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293"
integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==
dependencies:
"@babel/runtime" "^7.1.2"
history "^4.9.0"
hoist-non-react-statics "^3.1.0"
loose-envify "^1.3.1"
mini-create-react-context "^0.4.0"
path-to-regexp "^1.7.0"
prop-types "^15.6.2"
react-is "^16.6.0"
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
react-router@6.0.0-beta.0:
version "6.0.0-beta.0"
resolved "https://registry.npmjs.org/react-router/-/react-router-6.0.0-beta.0.tgz#3e11f39b6ded4412c2fed9e4f989dd4c8156724d"
@@ -16342,6 +16403,11 @@ resolve-from@^5.0.0:
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
resolve-pathname@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -18185,12 +18251,12 @@ tiny-emitter@^2.0.0:
resolved "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tiny-invariant@^1.0.6:
tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
version "1.1.0"
resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
tiny-warning@^1.0.2:
tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
@@ -18406,7 +18472,7 @@ ts-loader@^7.0.4:
micromatch "^4.0.0"
semver "^6.0.0"
ts-node@^8.6.2:
ts-node@^8.10.2, ts-node@^8.6.2:
version "8.10.2"
resolved "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d"
integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==
@@ -18987,6 +19053,11 @@ validate.io-number@^1.0.3:
resolved "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz#f63ffeda248bf28a67a8d48e0e3b461a1665baf8"
integrity sha1-9j/+2iSL8opnqNSODjtGGhZluvg=
value-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"