UI updates to GCP-projects plugin

Adds the following to the project list page:
* pagination
* filtering
* sorting
* rows per page
* show/hide columns

Makes breadcrumb a link back to project list for the project details and new project views.

In project list page, updates New project button to use RouterLink instead of href to avoid login prompt.

In project details view, links to project details and logs now work, clicking on these will open the project or logs in GCP in new tab.

Signed-off-by: Jeremy Guarini <jguarini@paloaltonetworks.com>
This commit is contained in:
Jeremy Guarini
2021-10-29 10:20:14 -07:00
parent 3db0cb3683
commit 0287b18613
5 changed files with 186 additions and 100 deletions
+1
View File
@@ -35,6 +35,7 @@
"@backstage/core-plugin-api": "^0.1.12",
"@backstage/theme": "^0.2.12",
"@material-ui/core": "^4.12.2",
"@material-ui/data-grid": "^4.0.0-alpha.37",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"react": "^16.13.1",
@@ -28,6 +28,10 @@ import {
StructuredMetadataTable,
SupportButton,
} from '@backstage/core-components';
import { Link as RouterLink } from 'react-router-dom';
import { useRouteRef } from '@backstage/core-plugin-api';
import { rootRouteRef } from '../../routes';
export const Project = () => {
const [projectName, setProjectName] = useState('');
@@ -79,18 +83,20 @@ export const Project = () => {
</SimpleStepperStep>
</SimpleStepper>
<Button
component={RouterLink}
variant="text"
data-testid="cancel-button"
color="primary"
href="/gcp-projects"
to="/gcp-projects"
>
Cancel
</Button>
<Button
component={RouterLink}
variant="contained"
color="primary"
disabled={disabled}
href={`newProject?projectName=${encodeURIComponent(
to={`newProject?projectName=${encodeURIComponent(
projectName,
)},projectId=${encodeURIComponent(projectId)}`}
>
@@ -110,18 +116,21 @@ const labels = (
</>
);
export const NewProjectPage = () => (
<Page themeId="service">
<Header title="New GCP Project" type="tool">
{labels}
</Header>
<Content>
<ContentHeader title="">
<SupportButton>
This plugin allows you to view and interact with your gcp projects.
</SupportButton>
</ContentHeader>
<Project />
</Content>
</Page>
);
export const NewProjectPage = () => {
const docsRootLink = useRouteRef(rootRouteRef)();
return (
<Page themeId="tool">
<Header title="New GCP Project" type="GCP" typeLink={docsRootLink}>
{labels}
</Header>
<Content>
<ContentHeader title="">
<SupportButton>
This plugin allows you to view and interact with your gcp projects.
</SupportButton>
</ContentHeader>
<Project />
</Content>
</Page>
);
};
@@ -40,7 +40,8 @@ import {
WarningPanel,
} from '@backstage/core-components';
import { useApi } from '@backstage/core-plugin-api';
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
import { rootRouteRef } from '../../routes';
const useStyles = makeStyles<Theme>(theme => ({
root: {
@@ -81,6 +82,8 @@ const DetailsPage = () => {
);
}
const cloud_home_url = 'https://console.cloud.google.com';
return (
<Table component={Paper} className={classes.table}>
<Table>
@@ -127,12 +130,24 @@ const DetailsPage = () => {
>
{details?.name && (
<Button>
<a href={details.name}>GCP</a>
<a
href={`${cloud_home_url}/home/dashboard?project=${details.name}&supportedpurview=project`}
target="_blank"
rel="noreferrer noopener"
>
GCP
</a>
</Button>
)}
{details?.name && (
<Button>
<a href={details.name}>Logs</a>
<a
href={`${cloud_home_url}/logs/query?project=${details.name}&supportedpurview=project`}
target="_blank"
rel="noreferrer noopener"
>
Logs
</a>
</Button>
)}
</ButtonGroup>
@@ -151,16 +166,20 @@ const labels = (
</>
);
export const ProjectDetailsPage = () => (
<Page themeId="service">
<Header title="GCP Project Details" type="other">
{labels}
</Header>
<Content>
<ContentHeader title="">
<SupportButton>Support Button</SupportButton>
</ContentHeader>
<DetailsPage />
</Content>
</Page>
);
export const ProjectDetailsPage = () => {
const docsRootLink = useRouteRef(rootRouteRef)();
return (
<Page themeId="service">
<Header title="GCP Project Details" type="GCP" typeLink={docsRootLink}>
{labels}
</Header>
<Content>
<ContentHeader title="">
<SupportButton>Support Button</SupportButton>
</ContentHeader>
<DetailsPage />
</Content>
</Page>
);
};
@@ -15,18 +15,8 @@
*/
// NEEDS WORK
import {
Button,
LinearProgress,
Paper,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Tooltip,
Typography,
} from '@material-ui/core';
import { Button, LinearProgress, Tooltip, Typography } from '@material-ui/core';
import { DataGrid, GridColDef, GridCellParams } from '@material-ui/data-grid';
import React from 'react';
import { useAsync } from 'react-use';
import { gcpApiRef, Project } from '../../api';
@@ -44,6 +34,8 @@ import {
import { useApi } from '@backstage/core-plugin-api';
import { Link as RouterLink } from 'react-router-dom';
const LongText = ({ text, max }: { text: string; max: number }) => {
if (text.length < max) {
return <span>{text}</span>;
@@ -67,6 +59,18 @@ const PageContents = () => {
const { loading, error, value } = useAsync(() => api.listProjects());
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
const handleChangePage = (newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (pageSize: number) => {
setRowsPerPage(pageSize);
setPage(0);
};
if (loading) {
return <LinearProgress />;
} else if (error) {
@@ -77,60 +81,80 @@ const PageContents = () => {
);
}
function renderLink(params: GridCellParams) {
return (
<Link
to={`project?projectId=${encodeURIComponent(params.value!.toString())}`}
>
<Typography color="primary">
<LongText text={params.value!.toString()} max={60} />
</Typography>
</Link>
);
}
const columns: GridColDef[] = [
{
align: 'left',
field: 'name',
flex: 1,
headerAlign: 'left',
headerName: 'Name',
},
{
align: 'left',
field: 'projectNumber',
flex: 0.6,
headerAlign: 'left',
headerName: 'Project Number',
},
{
align: 'left',
field: 'projectID',
flex: 1,
headerAlign: 'left',
headerName: 'Project ID',
renderCell: renderLink,
},
{
align: 'left',
field: 'state',
flex: 0.6,
headerAlign: 'left',
headerName: 'State',
},
{
align: 'left',
field: 'creationTime',
flex: 0.7,
headerAlign: 'left',
headerName: 'Creation Time',
},
];
const rows =
value?.map((project: Project) => ({
id: project.projectId,
name: project.name,
projectNumber: project?.projectNumber || 'Error',
projectID: project.projectId,
state: project?.lifecycleState || 'Error',
creationTime: project?.createTime || 'Error',
})) || [];
return (
<Table 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 || 'Error'} 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 || 'Error'}
max={30}
/>
</Typography>
</TableCell>
<TableCell>
<Typography>
<LongText text={project?.createTime || 'Error'} max={30} />
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Table>
<div style={{ height: '95%', width: '100%' }}>
<DataGrid
rows={rows}
columns={columns}
page={page}
pageSize={rowsPerPage}
rowsPerPageOptions={[10, 25, 50, 100]}
onPageChange={handleChangePage}
onPageSizeChange={handleChangeRowsPerPage}
disableSelectionOnClick
/>
</div>
);
};
@@ -141,7 +165,12 @@ export const ProjectListPage = () => (
</Header>
<Content>
<ContentHeader title="">
<Button variant="contained" color="primary" href="/gcp-projects/new">
<Button
component={RouterLink}
variant="contained"
color="primary"
to="new"
>
New Project
</Button>
<SupportButton>All your software catalog entities</SupportButton>
+29 -1
View File
@@ -4416,6 +4416,16 @@
react-is "^16.8.0 || ^17.0.0"
react-transition-group "^4.4.0"
"@material-ui/data-grid@^4.0.0-alpha.37":
version "4.0.0-alpha.37"
resolved "https://registry.npmjs.org/@material-ui/data-grid/-/data-grid-4.0.0-alpha.37.tgz#89d907c4e94e6a0db4e89e4f59160f7811546ca2"
integrity sha512-3T2AG31aad/lWLMLwn1XUP4mUf3H9YZES17dGuYByzkRLCXbBZHBTPEnCctWukajzwm+v0KGg3QpwitGoiDAjA==
dependencies:
"@material-ui/utils" "^5.0.0-alpha.14"
clsx "^1.0.4"
prop-types "^15.7.2"
reselect "^4.0.0"
"@material-ui/icons@^4.11.2", "@material-ui/icons@^4.9.1":
version "4.11.2"
resolved "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz#b3a7353266519cd743b6461ae9fdfcb1b25eb4c5"
@@ -4503,6 +4513,17 @@
prop-types "^15.7.2"
react-is "^16.8.0 || ^17.0.0"
"@material-ui/utils@^5.0.0-alpha.14":
version "5.0.0-beta.5"
resolved "https://registry.npmjs.org/@material-ui/utils/-/utils-5.0.0-beta.5.tgz#de492037e1f1f0910fda32e6f11b66dfcde2a1c2"
integrity sha512-wtJ3ovXWZdTAz5eLBqvMpYH/IBJb3qMQbGCyL1i00+sf7AUlAuv4QLx+QtX/siA6L7IpxUQVfqpoCpQH1eYRpQ==
dependencies:
"@babel/runtime" "^7.14.8"
"@types/prop-types" "^15.7.4"
"@types/react-is" "^16.7.1 || ^17.0.0"
prop-types "^15.7.2"
react-is "^17.0.2"
"@mattiasbuelens/web-streams-polyfill@^0.2.0":
version "0.2.1"
resolved "https://registry.npmjs.org/@mattiasbuelens/web-streams-polyfill/-/web-streams-polyfill-0.2.1.tgz#d7c4aa94f98084ec0787be084d47167d62ea5f67"
@@ -7434,7 +7455,7 @@
resolved "https://registry.npmjs.org/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz#72a26101dc567b0d68fd956cf42314556e42d601"
integrity sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==
"@types/prop-types@*", "@types/prop-types@^15.7.3":
"@types/prop-types@*", "@types/prop-types@^15.7.3", "@types/prop-types@^15.7.4":
version "15.7.4"
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
@@ -7475,6 +7496,13 @@
dependencies:
"@types/react" "*"
"@types/react-is@^16.7.1 || ^17.0.0":
version "17.0.3"
resolved "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a"
integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==
dependencies:
"@types/react" "*"
"@types/react-lazylog@^4.5.0":
version "4.5.1"
resolved "https://registry.npmjs.org/@types/react-lazylog/-/react-lazylog-4.5.1.tgz#babb5d814f7035b5434518769975e12f299356a8"