second draft of the Bazaar plugin
signed-off-by: lykkeaxlin <lykkeaxlin@hotmail.com> Co-authored-by: klaraab <klarabroman@live.se> Signed-off-by: Lykke Axlin <lykkeaxlin@hotmail.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { Grid, Typography, Link, makeStyles } from '@material-ui/core';
|
||||
import { InfoCard } from '@backstage/core';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
subheader: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
export const About = () => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={5}>
|
||||
<InfoCard title="About Bazaar">
|
||||
<Typography className={classes.subheader} variant="body1">
|
||||
What is the Bazaar?
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
The Bazaar is a place where teams can propose projects for
|
||||
cross-functional team development. Essentially a marketplace for
|
||||
internal projects suitable for{' '}
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://en.wikipedia.org/wiki/Inner_source"
|
||||
>
|
||||
Inner Sourcing
|
||||
</Link>
|
||||
. With "Inner Sourcing", we mean projects that are developed
|
||||
internally within a company, but with Open Source best practices.
|
||||
</Typography>
|
||||
<Typography className={classes.subheader} variant="body1">
|
||||
Why?
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
Many companies today are of high need to increase the ease of
|
||||
cross-team cooperation. In large organizations, engineers often have
|
||||
limited ways of discovering or announcing the projects which could
|
||||
benefit from a wider development effort in terms of different
|
||||
expertise, experiences, and teams spread across the organization.
|
||||
With no good way to find these existing internal projects to join,
|
||||
the possibility of working with Inner Sourcing practices suffers.
|
||||
</Typography>
|
||||
<Typography className={classes.subheader} variant="body1">
|
||||
How?
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
The Bazaar allows engineers and teams to open up and announce their
|
||||
new and exciting projects for transparent cooperation in other parts
|
||||
of larger organizations. The Bazaar ensures that new Inner Sourcing
|
||||
friendly projects gain visibility through Backstage and a way for
|
||||
interested engineers to show their interest and in the future
|
||||
contribute with their specific skill set. The Bazaar also provides
|
||||
an easy way to manage, catalog, and browse these Inner Sourcing
|
||||
friendly projects and components.
|
||||
</Typography>
|
||||
</InfoCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { About } from './About';
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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, Dispatch, SetStateAction } from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { SubmitHandler } from 'react-hook-form';
|
||||
import { updateMetadata } from '../../util/dbRequests';
|
||||
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { ProjectDialog } from '../ProjectDialog';
|
||||
import { ProjectSelector } from '../ProjectSelector';
|
||||
import { BazaarProject, FormValues, Status } from '../../util/types';
|
||||
|
||||
type Props = {
|
||||
catalogEntities: Entity[];
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
setBazaarProjects: Dispatch<SetStateAction<BazaarProject[]>>;
|
||||
setCatalogEntities: Dispatch<SetStateAction<Entity[]>>;
|
||||
};
|
||||
|
||||
export const AddProjectDialog = ({
|
||||
catalogEntities,
|
||||
open,
|
||||
handleClose,
|
||||
setBazaarProjects,
|
||||
setCatalogEntities,
|
||||
}: Props) => {
|
||||
const baseUrl = useApi(configApiRef)
|
||||
.getConfig('backend')
|
||||
.getString('baseUrl');
|
||||
const [selectedEntity, setSelectedEntity] = useState(
|
||||
catalogEntities ? catalogEntities[0] : null,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedEntity(catalogEntities ? catalogEntities[0] : null);
|
||||
}, [catalogEntities]);
|
||||
|
||||
const defaultValues = {
|
||||
title: 'Add project',
|
||||
announcement: '',
|
||||
status: 'proposed' as Status,
|
||||
};
|
||||
|
||||
const handleListItemClick = (entity: Entity) => {
|
||||
setSelectedEntity(entity);
|
||||
};
|
||||
|
||||
const handleSave: SubmitHandler<FormValues> = async (
|
||||
getValues: any,
|
||||
reset: any,
|
||||
) => {
|
||||
const formValues = getValues();
|
||||
|
||||
const bazaarProject: BazaarProject = {
|
||||
entityRef: `${selectedEntity!.metadata.namespace}/${
|
||||
selectedEntity!.kind
|
||||
}/${selectedEntity!.metadata.name}`,
|
||||
name: selectedEntity!.metadata.name,
|
||||
announcement: formValues.announcement,
|
||||
status: formValues.status,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
setBazaarProjects((oldProjects: BazaarProject[]) => {
|
||||
return [...oldProjects, bazaarProject];
|
||||
});
|
||||
|
||||
setCatalogEntities((oldEntities: Entity[]) => {
|
||||
return oldEntities.filter(entity => entity !== selectedEntity);
|
||||
});
|
||||
|
||||
await updateMetadata(
|
||||
selectedEntity!,
|
||||
selectedEntity!.metadata.name,
|
||||
formValues.announcement,
|
||||
formValues.status,
|
||||
baseUrl,
|
||||
);
|
||||
|
||||
handleClose();
|
||||
reset(defaultValues);
|
||||
};
|
||||
|
||||
return (
|
||||
<ProjectDialog
|
||||
handleSave={handleSave}
|
||||
title="Add project"
|
||||
isAddForm
|
||||
defaultValues={defaultValues}
|
||||
open={open}
|
||||
projectSelector={
|
||||
<ProjectSelector
|
||||
value={selectedEntity?.metadata.name || ''}
|
||||
onChange={handleListItemClick}
|
||||
isFormInvalid={false}
|
||||
entities={catalogEntities || []}
|
||||
/>
|
||||
}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { AddProjectDialog } from './AddProjectDialog';
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { Snackbar, IconButton } from '@material-ui/core';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
message: JSX.Element;
|
||||
handleClose: () => void;
|
||||
};
|
||||
|
||||
export const AlertBanner = ({ open, message, handleClose }: Props) => {
|
||||
return (
|
||||
<Snackbar
|
||||
open={open}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||
>
|
||||
<Alert
|
||||
severity="error"
|
||||
action={
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={handleClose}
|
||||
data-testid="error-button-close"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
{message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { AlertBanner } from './AlertBanner';
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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, { Dispatch, SetStateAction } from 'react';
|
||||
import {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
WithStyles,
|
||||
} from '@material-ui/core/styles';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import MuiDialogTitle from '@material-ui/core/DialogTitle';
|
||||
import MuiDialogContent from '@material-ui/core/DialogContent';
|
||||
import MuiDialogActions from '@material-ui/core/DialogActions';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { deleteEntity } from '../../util/dbRequests';
|
||||
import {
|
||||
useApi,
|
||||
configApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
margin: 0,
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
closeButton: {
|
||||
position: 'absolute',
|
||||
right: theme.spacing(1),
|
||||
top: theme.spacing(1),
|
||||
color: theme.palette.grey[500],
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
DialogTitleProps, DialogTitle, DialogContent and DialogActions
|
||||
are copied from the git-release plugin
|
||||
*/
|
||||
export interface DialogTitleProps extends WithStyles<typeof styles> {
|
||||
id: string;
|
||||
children: React.ReactNode;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const DialogTitle = withStyles(styles)((props: DialogTitleProps) => {
|
||||
const { children, classes, onClose, ...other } = props;
|
||||
return (
|
||||
<MuiDialogTitle disableTypography className={classes.root} {...other}>
|
||||
<Typography variant="h6">{children}</Typography>
|
||||
{onClose ? (
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={onClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</MuiDialogTitle>
|
||||
);
|
||||
});
|
||||
|
||||
const DialogContent = withStyles((theme: Theme) => ({
|
||||
root: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
}))(MuiDialogContent);
|
||||
|
||||
const DialogActions = withStyles((theme: Theme) => ({
|
||||
root: {
|
||||
margin: 0,
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
}))(MuiDialogActions);
|
||||
|
||||
type Props = {
|
||||
entity: Entity;
|
||||
openDelete: boolean;
|
||||
handleClose: () => void;
|
||||
setIsBazaar: Dispatch<SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export const DeleteProjectDialog = ({
|
||||
entity,
|
||||
openDelete,
|
||||
handleClose,
|
||||
setIsBazaar,
|
||||
}: Props) => {
|
||||
const baseUrl = useApi(configApiRef)
|
||||
.getConfig('backend')
|
||||
.getString('baseUrl');
|
||||
|
||||
const handleCloseAndClear = () => {
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const identity = useApi(identityApiRef);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await deleteEntity(baseUrl, entity, identity);
|
||||
setIsBazaar(false);
|
||||
handleCloseAndClear();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth="xs"
|
||||
onClose={handleCloseAndClear}
|
||||
aria-labelledby="customized-dialog-title"
|
||||
open={openDelete}
|
||||
>
|
||||
<DialogTitle id="customized-dialog-title" onClose={handleCloseAndClear}>
|
||||
Delete project
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent dividers>
|
||||
Are you sure you want to delete this project from the Bazaar?
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={handleSubmit} color="primary" type="submit">
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { DeleteProjectDialog } from './DeleteProjectDialog';
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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, Dispatch, SetStateAction } from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { updateMetadata } from '../../util/dbRequests';
|
||||
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
import { ProjectDialog } from '../ProjectDialog';
|
||||
import { BazaarProject, FormValues } from '../../util/types';
|
||||
|
||||
type Props = {
|
||||
entity: Entity;
|
||||
bazaarProject: BazaarProject;
|
||||
setBazaarProject: Dispatch<SetStateAction<BazaarProject>>;
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
isAddForm: boolean;
|
||||
};
|
||||
|
||||
export const EditProjectDialog = ({
|
||||
entity,
|
||||
bazaarProject,
|
||||
setBazaarProject,
|
||||
open,
|
||||
handleClose,
|
||||
}: Props) => {
|
||||
const baseUrl = useApi(configApiRef)
|
||||
.getConfig('backend')
|
||||
.getString('baseUrl');
|
||||
|
||||
const [defaultValues, setDefaultValues] = useState<FormValues>({
|
||||
announcement: bazaarProject.announcement,
|
||||
status: bazaarProject.status,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setDefaultValues({
|
||||
announcement: bazaarProject.announcement,
|
||||
status: bazaarProject.status,
|
||||
});
|
||||
}, [bazaarProject]);
|
||||
|
||||
const handleSave: any = async (getValues: any, _: any) => {
|
||||
const formValues = getValues();
|
||||
|
||||
const updateResponse = await updateMetadata(
|
||||
entity!,
|
||||
entity.metadata.name,
|
||||
formValues.announcement,
|
||||
formValues.status,
|
||||
baseUrl,
|
||||
);
|
||||
|
||||
if (updateResponse.status === 'ok')
|
||||
setBazaarProject((oldProject: BazaarProject) => {
|
||||
return {
|
||||
...oldProject,
|
||||
announcement: formValues.announcement,
|
||||
status: formValues.status,
|
||||
};
|
||||
});
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProjectDialog
|
||||
title="Edit project"
|
||||
handleSave={handleSave}
|
||||
isAddForm={false}
|
||||
defaultValues={defaultValues}
|
||||
open={open}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { EditProjectDialog } from './EditProjectDialog';
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* Copyright 2021 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 {
|
||||
Grid,
|
||||
makeStyles,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Typography,
|
||||
Divider,
|
||||
IconButton,
|
||||
Popover,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
ListItemText,
|
||||
} from '@material-ui/core';
|
||||
import {
|
||||
Progress,
|
||||
HeaderIconLinkRow,
|
||||
IconLinkVerticalProps,
|
||||
Avatar,
|
||||
} from '@backstage/core-components';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { AboutField } from '@backstage/plugin-catalog';
|
||||
import { StatusTag } from '../StatusTag';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import ChatIcon from '@material-ui/icons/Chat';
|
||||
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import DeleteIcon from '@material-ui/icons/Delete';
|
||||
import { EditProjectDialog } from '../EditProjectDialog';
|
||||
import { DeleteProjectDialog } from '../DeleteProjectDialog';
|
||||
import ExitToAppIcon from '@material-ui/icons/ExitToApp';
|
||||
import {
|
||||
useApi,
|
||||
identityApiRef,
|
||||
configApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { getEntityRef } from '../../util/dbRequests';
|
||||
import { useAsync } from 'react-use';
|
||||
import { Member, BazaarProject } from '../../util/types';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
description: {
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
icon: {
|
||||
marginRight: '1.75rem',
|
||||
},
|
||||
link: {
|
||||
color: '#9cc9ff',
|
||||
'&:hover': {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
},
|
||||
memberLink: {
|
||||
display: 'block',
|
||||
marginBottom: '0.3rem',
|
||||
},
|
||||
});
|
||||
|
||||
const sortMembers = (m1: Member, m2: Member) => {
|
||||
return new Date(m2.joinDate).getTime() - new Date(m1.joinDate).getTime();
|
||||
};
|
||||
|
||||
export const EntityBazaarInfoCard = () => {
|
||||
const { entity } = useEntity();
|
||||
const classes = useStyles();
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const [openDelete, setOpenDelete] = useState(false);
|
||||
const identity = useApi(identityApiRef);
|
||||
const [isMember, setIsMember] = useState(false);
|
||||
const [members, setMembers] = useState<Member[]>([]);
|
||||
const [bazaarProject, setBazaarProject] = useState<BazaarProject>({
|
||||
entityRef: '',
|
||||
name: '',
|
||||
announcement: '',
|
||||
status: 'proposed',
|
||||
updatedAt: '',
|
||||
});
|
||||
const [isBazaar, setIsBazaar] = useState(false);
|
||||
|
||||
const baseUrl = useApi(configApiRef)
|
||||
.getConfig('backend')
|
||||
.getString('baseUrl');
|
||||
|
||||
const getInitMemberStatus = async () => {
|
||||
const response = await fetch(`${baseUrl}/api/bazaar/members`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
entity_ref: getEntityRef(entity),
|
||||
},
|
||||
}).then(resp => resp.json());
|
||||
|
||||
const dbMembers = response.data.map((obj: any) => {
|
||||
const member: Member = {
|
||||
userId: obj.user_id,
|
||||
entityRef: obj.entity_ref,
|
||||
joinDate: obj.join_date,
|
||||
};
|
||||
|
||||
return member;
|
||||
});
|
||||
|
||||
dbMembers.sort(sortMembers);
|
||||
|
||||
setMembers(dbMembers);
|
||||
setIsMember(
|
||||
dbMembers
|
||||
.map((member: Member) => member.userId)
|
||||
.indexOf(identity.getUserId()) >= 0,
|
||||
);
|
||||
};
|
||||
|
||||
const getMetadata = async () => {
|
||||
const response = await fetch(`${baseUrl}/api/bazaar/metadata`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
entity_ref: getEntityRef(entity),
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status !== 404) {
|
||||
setIsBazaar(true);
|
||||
const data = await response.json().then(resp => resp.data);
|
||||
|
||||
setBazaarProject({
|
||||
entityRef: data[0].entityRef,
|
||||
name: data[0].name,
|
||||
announcement: data[0].announcement,
|
||||
status: data[0].status,
|
||||
updatedAt: data[0].updatedAt,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const { loading } = useAsync(async () => {
|
||||
await getInitMemberStatus();
|
||||
await getMetadata();
|
||||
});
|
||||
|
||||
const onOpen = (event: React.SyntheticEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
setPopoverOpen(true);
|
||||
};
|
||||
|
||||
const closeEdit = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const closeDelete = () => {
|
||||
setOpenDelete(false);
|
||||
};
|
||||
|
||||
const popoverCloseHandler = () => {
|
||||
setPopoverOpen(false);
|
||||
};
|
||||
|
||||
const handleMembersClick = async () => {
|
||||
setIsMember(!isMember);
|
||||
|
||||
const newMember: Member = {
|
||||
userId: identity.getUserId(),
|
||||
entityRef: getEntityRef(entity),
|
||||
joinDate: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const headers = {
|
||||
user_id: newMember.userId,
|
||||
entity_ref: newMember.entityRef,
|
||||
join_date: newMember.joinDate,
|
||||
};
|
||||
|
||||
if (!isMember) {
|
||||
setMembers((prevMembers: Member[]) => {
|
||||
const newMembers: Member[] = [...prevMembers, newMember];
|
||||
newMembers.sort(sortMembers);
|
||||
return newMembers;
|
||||
});
|
||||
await fetch(`${baseUrl}/api/bazaar/members/add`, {
|
||||
method: 'PUT',
|
||||
headers: headers,
|
||||
});
|
||||
} else {
|
||||
setMembers(
|
||||
members.filter(
|
||||
(member: Member) => member.userId !== identity.getUserId(),
|
||||
),
|
||||
);
|
||||
await fetch(`${baseUrl}/api/bazaar/members/remove`, {
|
||||
method: 'DELETE',
|
||||
headers: headers,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const links: IconLinkVerticalProps[] = [
|
||||
{
|
||||
label: 'Community',
|
||||
icon: <ChatIcon />,
|
||||
},
|
||||
{
|
||||
label: isMember ? 'Leave' : 'Join',
|
||||
icon: isMember ? <ExitToAppIcon /> : <PersonAddIcon />,
|
||||
href: '',
|
||||
onClick: async () => {
|
||||
handleMembersClick();
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
} else if (!isBazaar) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader title="Bazaar" style={{ paddingBottom: '1rem' }} />
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<Typography variant="body1">
|
||||
This project is not in the Bazaar. Go to the{' '}
|
||||
<Link className={classes.link} to="/bazaar">
|
||||
Bazaar
|
||||
</Link>{' '}
|
||||
to add the project or to{' '}
|
||||
<Link className={classes.link} to="/bazaar/about">
|
||||
read more
|
||||
</Link>
|
||||
.
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Card>
|
||||
<EditProjectDialog
|
||||
open={open}
|
||||
entity={entity}
|
||||
bazaarProject={bazaarProject}
|
||||
setBazaarProject={setBazaarProject}
|
||||
handleClose={closeEdit}
|
||||
isAddForm={false}
|
||||
/>
|
||||
|
||||
<DeleteProjectDialog
|
||||
entity={entity}
|
||||
openDelete={openDelete}
|
||||
handleClose={closeDelete}
|
||||
setIsBazaar={setIsBazaar}
|
||||
/>
|
||||
<CardHeader
|
||||
title="Bazaar"
|
||||
action={
|
||||
<IconButton onClick={onOpen}>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
}
|
||||
subheader={<HeaderIconLinkRow links={links} />}
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<Popover
|
||||
open={popoverOpen}
|
||||
onClose={popoverCloseHandler}
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
>
|
||||
<MenuList>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<EditIcon className={classes.icon} />
|
||||
<ListItemText primary="Suggest changes" />
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setOpenDelete(true);
|
||||
setPopoverOpen(false);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon className={classes.icon} />
|
||||
<ListItemText primary="Remove from Bazaar" />
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Popover>
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<AboutField label="Announcement">
|
||||
{bazaarProject.announcement
|
||||
? bazaarProject.announcement.split('\n').map((str: string) => (
|
||||
<Typography
|
||||
key={Math.floor(Math.random() * 1000)}
|
||||
variant="body2"
|
||||
paragraph
|
||||
className={classes.description}
|
||||
>
|
||||
{str}
|
||||
</Typography>
|
||||
))
|
||||
: 'No announcement'}
|
||||
</AboutField>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6}>
|
||||
<AboutField label="Status">
|
||||
<StatusTag status={bazaarProject.status} />
|
||||
</AboutField>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={6}>
|
||||
{' '}
|
||||
<AboutField label="Latest members">
|
||||
{members.length ? (
|
||||
members.slice(0, 3).map(member => {
|
||||
return (
|
||||
<div key={member.userId}>
|
||||
<Avatar
|
||||
displayName={member.userId}
|
||||
customStyles={{
|
||||
width: '19px',
|
||||
height: '19px',
|
||||
fontSize: '8px',
|
||||
float: 'left',
|
||||
marginRight: '0.3rem',
|
||||
}}
|
||||
/>
|
||||
<Link
|
||||
className={classes.memberLink}
|
||||
to={`http://github.com/${member.userId}`}
|
||||
>
|
||||
{member?.userId}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</AboutField>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 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 { EntityBazaarInfoCard } from './EntityBazaarInfoCard';
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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, RoutedTabs } from '@backstage/core-components';
|
||||
import { Route } from 'react-router-dom';
|
||||
import { SortView } from '../SortView';
|
||||
import { About } from '../About';
|
||||
|
||||
export const HomePage = () => {
|
||||
const tabContent = [
|
||||
{
|
||||
path: '/',
|
||||
title: 'Home',
|
||||
children: <Route path="/" element={<SortView />} />,
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
title: 'About',
|
||||
children: <Route path="/about" element={<About />} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Header
|
||||
data-testid="bazaar-header"
|
||||
title="Bazaar"
|
||||
subtitle="Marketplace for inner source projects"
|
||||
/>
|
||||
<RoutedTabs routes={tabContent} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { HomePage } from './HomePage';
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { Controller, Control, FieldError } from 'react-hook-form';
|
||||
import { TextField } from '@material-ui/core';
|
||||
import { FormValues } from '../../util/types';
|
||||
|
||||
type Props = {
|
||||
inputType: string;
|
||||
error: FieldError | undefined;
|
||||
control: Control<FormValues, object>;
|
||||
name: 'announcement' | 'status';
|
||||
helperText: string;
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export const InputField = ({
|
||||
inputType,
|
||||
error,
|
||||
control,
|
||||
name,
|
||||
helperText,
|
||||
placeholder,
|
||||
}: Props) => {
|
||||
const label = inputType.charAt(0).toUpperCase() + inputType.slice(1);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
rules={{
|
||||
required: true,
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
margin="dense"
|
||||
multiline
|
||||
id="title"
|
||||
type="text"
|
||||
fullWidth
|
||||
label={label}
|
||||
placeholder={placeholder}
|
||||
error={!!error}
|
||||
helperText={error && helperText}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { InputField } from './InputField';
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 InputLabel from '@material-ui/core/InputLabel';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import { Controller, Control, FieldError } from 'react-hook-form';
|
||||
import { FormValues } from '../../util/types';
|
||||
|
||||
type Props = {
|
||||
options: string[];
|
||||
control: Control<FormValues, object>;
|
||||
name: 'announcement' | 'status';
|
||||
error?: FieldError | undefined;
|
||||
};
|
||||
|
||||
export const InputSelector = ({ name, options, control, error }: Props) => {
|
||||
const label = name.charAt(0).toUpperCase() + name.slice(1);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
rules={{
|
||||
required: true,
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel
|
||||
htmlFor="demo-simple-select-outlined"
|
||||
id="demo-simple-select-outlined-label"
|
||||
>
|
||||
{label}
|
||||
</InputLabel>
|
||||
<Select
|
||||
{...field}
|
||||
required
|
||||
labelId="demo-simple-select-outlined-label"
|
||||
id="demo-simple-select-outlined"
|
||||
label={label}
|
||||
error={!!error}
|
||||
>
|
||||
{options.map(option => {
|
||||
return (
|
||||
<MenuItem
|
||||
data-testid="menu-item"
|
||||
button
|
||||
key={option}
|
||||
value={option}
|
||||
>
|
||||
{option}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { InputSelector } from './InputSelector';
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { ItemCardHeader } from '@backstage/core-components';
|
||||
import {
|
||||
Card,
|
||||
CardActionArea,
|
||||
CardContent,
|
||||
makeStyles,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { StatusTag } from '../StatusTag/StatusTag';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import moment from 'moment';
|
||||
import { catalogRouteRef } from '@backstage/plugin-catalog-react';
|
||||
import { useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { BazaarProject } from '../../util/types';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
statusTag: {
|
||||
display: 'inline-block',
|
||||
whiteSpace: 'nowrap',
|
||||
marginBottom: '0.5rem',
|
||||
},
|
||||
announcement: {
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 5,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
marginBottom: '0.8rem',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
memberCount: {
|
||||
float: 'right',
|
||||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
bazaarProject: BazaarProject;
|
||||
memberCount: number;
|
||||
};
|
||||
|
||||
export const ProjectCard = ({ bazaarProject, memberCount }: Props) => {
|
||||
const classes = useStyles();
|
||||
const { entityRef, name, status, updatedAt, announcement } = bazaarProject;
|
||||
const catalogLink = useRouteRef(catalogRouteRef);
|
||||
|
||||
return (
|
||||
<Card key={entityRef}>
|
||||
<CardActionArea
|
||||
style={{
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
}}
|
||||
component={RouterLink}
|
||||
to={`${catalogLink()}/${entityRef}`}
|
||||
>
|
||||
<ItemCardHeader
|
||||
title={name}
|
||||
subtitle={`updated ${moment(updatedAt).fromNow()}`}
|
||||
/>
|
||||
<CardContent style={{ height: '12rem' }}>
|
||||
<StatusTag styles={classes.statusTag} status={status} />
|
||||
<Typography variant="body2" className={classes.memberCount}>
|
||||
{memberCount === 1
|
||||
? `${memberCount} member`
|
||||
: `${memberCount} members`}
|
||||
</Typography>
|
||||
<div style={{ minHeight: '6.5rem', maxHeight: '6.5rem' }}>
|
||||
<Typography variant="body2" className={classes.announcement}>
|
||||
{announcement}
|
||||
</Typography>
|
||||
</div>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { ProjectCard } from './ProjectCard';
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
createStyles,
|
||||
Theme,
|
||||
withStyles,
|
||||
WithStyles,
|
||||
} from '@material-ui/core/styles';
|
||||
import MuiDialogTitle from '@material-ui/core/DialogTitle';
|
||||
import MuiDialogContent from '@material-ui/core/DialogContent';
|
||||
import MuiDialogActions from '@material-ui/core/DialogActions';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import { Button, Dialog, Typography, IconButton } from '@material-ui/core';
|
||||
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||
import { InputField } from '../InputField/InputField';
|
||||
import { InputSelector } from '../InputSelector/InputSelector';
|
||||
import { FormValues } from '../../util/types';
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
margin: 0,
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
closeButton: {
|
||||
position: 'absolute',
|
||||
right: theme.spacing(1),
|
||||
top: theme.spacing(1),
|
||||
color: theme.palette.grey[500],
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
DialogTitleProps, DialogTitle, DialogContent and DialogActions
|
||||
are copied from the git-release plugin
|
||||
*/
|
||||
export interface DialogTitleProps extends WithStyles<typeof styles> {
|
||||
id: string;
|
||||
children: React.ReactNode;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const DialogTitle = withStyles(styles)((props: DialogTitleProps) => {
|
||||
const { children, classes, onClose, ...other } = props;
|
||||
return (
|
||||
<MuiDialogTitle disableTypography className={classes.root} {...other}>
|
||||
<Typography variant="h6">{children}</Typography>
|
||||
{onClose ? (
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
className={classes.closeButton}
|
||||
onClick={onClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
) : null}
|
||||
</MuiDialogTitle>
|
||||
);
|
||||
});
|
||||
|
||||
const DialogContent = withStyles((theme: Theme) => ({
|
||||
root: {
|
||||
padding: theme.spacing(2),
|
||||
},
|
||||
}))(MuiDialogContent);
|
||||
|
||||
const DialogActions = withStyles((theme: Theme) => ({
|
||||
root: {
|
||||
margin: 0,
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
}))(MuiDialogActions);
|
||||
|
||||
type Props = {
|
||||
handleSave: (getValues: any, reset: any) => SubmitHandler<FormValues>;
|
||||
isAddForm: boolean;
|
||||
title: string;
|
||||
defaultValues: FormValues;
|
||||
open: boolean;
|
||||
projectSelector?: JSX.Element;
|
||||
handleClose: () => void;
|
||||
};
|
||||
|
||||
export const ProjectDialog = ({
|
||||
handleSave,
|
||||
isAddForm,
|
||||
title,
|
||||
defaultValues,
|
||||
open,
|
||||
projectSelector,
|
||||
handleClose,
|
||||
}: Props) => {
|
||||
const {
|
||||
handleSubmit,
|
||||
reset,
|
||||
control,
|
||||
getValues,
|
||||
formState: { errors },
|
||||
} = useForm<FormValues>({
|
||||
mode: 'onChange',
|
||||
defaultValues: defaultValues,
|
||||
});
|
||||
|
||||
const handleCloseAndClear = () => {
|
||||
handleClose();
|
||||
reset(defaultValues);
|
||||
};
|
||||
|
||||
const handleSaveProject = () => {
|
||||
handleSave(getValues, reset);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth="xs"
|
||||
onClose={handleCloseAndClear}
|
||||
aria-labelledby="customized-dialog-title"
|
||||
open={open}
|
||||
>
|
||||
<DialogTitle id="customized-dialog-title" onClose={handleCloseAndClear}>
|
||||
{title}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent dividers>
|
||||
{isAddForm && projectSelector}
|
||||
|
||||
<InputField
|
||||
error={errors.announcement}
|
||||
control={control}
|
||||
inputType="announcement"
|
||||
helperText="please enter an announcement"
|
||||
placeholder="Describe who you are and what skills you are looking for"
|
||||
name="announcement"
|
||||
/>
|
||||
|
||||
<InputSelector
|
||||
control={control}
|
||||
name="status"
|
||||
options={['proposed', 'ongoing']}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={handleSubmit(handleSaveProject)}
|
||||
color="primary"
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { ProjectDialog } from './ProjectDialog';
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { Content } from '@backstage/core-components';
|
||||
import { ProjectCard } from '../ProjectCard/ProjectCard';
|
||||
import { makeStyles, Grid } from '@material-ui/core';
|
||||
import Pagination from '@material-ui/lab/Pagination';
|
||||
import { BazaarProject } from '../../util/types';
|
||||
|
||||
type Props = {
|
||||
bazaarProjects: BazaarProject[];
|
||||
sortingMethod: (arg0: BazaarProject, arg1: BazaarProject) => number;
|
||||
bazaarMembers: Map<string, number>;
|
||||
};
|
||||
|
||||
const useStyles = makeStyles({
|
||||
content: {
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center',
|
||||
},
|
||||
item: {
|
||||
minWidth: '20.5rem',
|
||||
maxWidth: '20.5rem',
|
||||
width: '20%',
|
||||
},
|
||||
});
|
||||
|
||||
export const ProjectPreview = ({
|
||||
bazaarProjects,
|
||||
sortingMethod,
|
||||
bazaarMembers,
|
||||
}: Props) => {
|
||||
const classes = useStyles();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const pageSize = 10;
|
||||
const pageCount = Math.ceil(bazaarProjects.length / pageSize);
|
||||
|
||||
const handlePageClick = (_: any, pageIndex: number) => {
|
||||
setCurrentPage(pageIndex);
|
||||
};
|
||||
|
||||
if (!bazaarProjects.length) {
|
||||
return (
|
||||
<div
|
||||
data-testid="empty-bazaar"
|
||||
style={{
|
||||
height: '10rem',
|
||||
textAlign: 'center',
|
||||
verticalAlign: 'middle',
|
||||
lineHeight: '10rem',
|
||||
}}
|
||||
>
|
||||
Please add projects to the Bazaar.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
bazaarProjects.sort(sortingMethod);
|
||||
|
||||
return (
|
||||
<Content className={classes.content} noPadding>
|
||||
<Grid wrap="wrap" container spacing={3}>
|
||||
{bazaarProjects
|
||||
.slice(pageSize * (currentPage - 1), pageSize * currentPage)
|
||||
.map((bazaarProject: BazaarProject) => {
|
||||
const entityRef = bazaarProject.entityRef;
|
||||
|
||||
return (
|
||||
<Grid key={entityRef || ''} className={classes.item} item xs={3}>
|
||||
<ProjectCard
|
||||
bazaarProject={bazaarProject}
|
||||
key={Math.random()}
|
||||
memberCount={bazaarMembers.get(entityRef) || 0}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
<Pagination
|
||||
showFirstButton
|
||||
showLastButton
|
||||
siblingCount={2}
|
||||
style={{
|
||||
marginTop: '1rem',
|
||||
marginLeft: 'auto',
|
||||
marginRight: '0',
|
||||
}}
|
||||
count={pageCount}
|
||||
page={currentPage}
|
||||
onChange={handlePageClick}
|
||||
/>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { ProjectPreview } from './ProjectPreview';
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 InputLabel from '@material-ui/core/InputLabel';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
type Props = {
|
||||
entities: Entity[];
|
||||
value: string;
|
||||
onChange: (entity: Entity) => void;
|
||||
isFormInvalid: boolean;
|
||||
};
|
||||
|
||||
export const ProjectSelector = ({
|
||||
entities,
|
||||
value,
|
||||
onChange,
|
||||
isFormInvalid,
|
||||
}: Props) => {
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id="demo-simple-select-outlined-label">
|
||||
Select a project
|
||||
</InputLabel>
|
||||
<Select
|
||||
required
|
||||
labelId="demo-simple-select-outlined-label"
|
||||
id="demo-simple-select-outlined"
|
||||
value={value}
|
||||
error={isFormInvalid && value === ''}
|
||||
label="Project"
|
||||
>
|
||||
{entities?.map(entity => {
|
||||
const projectName = entity.metadata.name;
|
||||
return (
|
||||
<MenuItem
|
||||
button
|
||||
onClick={() => onChange(entity)}
|
||||
key={projectName}
|
||||
value={projectName}
|
||||
>
|
||||
{projectName}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { ProjectSelector } from './ProjectSelector';
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
Content,
|
||||
ContentHeader,
|
||||
SupportButton,
|
||||
Progress,
|
||||
} from '@backstage/core-components';
|
||||
import { AddProjectDialog } from '../AddProjectDialog';
|
||||
import { AlertBanner } from '../AlertBanner';
|
||||
import { ProjectPreview } from '../ProjectPreview/ProjectPreview';
|
||||
import { Button, makeStyles, Link } from '@material-ui/core';
|
||||
import { useAsync } from 'react-use';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { getBazaarMembers } from '../../util/dbRequests';
|
||||
import { useApi, configApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
CATALOG_FILTER_EXISTS,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { BazaarProject } from '../../util/types';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
marginTop: '2rem',
|
||||
},
|
||||
});
|
||||
|
||||
export const SortView = () => {
|
||||
const classes = useStyles();
|
||||
const [openAdd, setOpenAdd] = useState(false);
|
||||
const [openNoProjects, setOpenNoProjects] = useState(false);
|
||||
const [catalogEntities, setCatalogEntities] = useState<Entity[]>([]);
|
||||
const [bazaarMembers, setBazaarMembers] = useState<Map<string, number>>(
|
||||
new Map(),
|
||||
);
|
||||
const [bazaarProjects, setBazaarProjects] = useState<BazaarProject[]>([]);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const baseUrl = useApi(configApiRef)
|
||||
.getConfig('backend')
|
||||
.getString('baseUrl');
|
||||
|
||||
const compareProjectsByDate = (
|
||||
a: BazaarProject,
|
||||
b: BazaarProject,
|
||||
): number => {
|
||||
const dateA = new Date(a.updatedAt).getTime();
|
||||
const dateB = new Date(b.updatedAt).getTime();
|
||||
return dateB - dateA;
|
||||
};
|
||||
|
||||
const handleCloseNoProjects = () => {
|
||||
setOpenNoProjects(false);
|
||||
};
|
||||
|
||||
const { loading } = useAsync(async () => {
|
||||
const entities = await catalogApi.getEntities({
|
||||
filter: {
|
||||
kind: 'Component',
|
||||
'metadata.annotations.backstage.io/edit-url': CATALOG_FILTER_EXISTS,
|
||||
},
|
||||
fields: ['apiVersion', 'kind', 'metadata', 'spec'],
|
||||
});
|
||||
|
||||
const response = await fetch(`${baseUrl}/api/bazaar/entities`, {
|
||||
method: 'GET',
|
||||
}).then(resp => resp.json());
|
||||
|
||||
const dbProjects: BazaarProject[] = [];
|
||||
const bazaarProjectRefs: string[] = [];
|
||||
|
||||
response.data.forEach((project: any) => {
|
||||
dbProjects.push({
|
||||
entityRef: project.entity_ref,
|
||||
name: project.name,
|
||||
status: project.status,
|
||||
announcement: project.announcement,
|
||||
updatedAt: project.updated_at,
|
||||
});
|
||||
|
||||
bazaarProjectRefs.push(project.entity_ref);
|
||||
});
|
||||
|
||||
setBazaarMembers(await getBazaarMembers(dbProjects, baseUrl));
|
||||
setBazaarProjects(dbProjects);
|
||||
setCatalogEntities(
|
||||
entities.items.filter((entity: Entity) => {
|
||||
const catalogEntityRef = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
|
||||
|
||||
return !bazaarProjectRefs.includes(catalogEntityRef);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
if (loading) {
|
||||
return <Progress />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Content noPadding>
|
||||
<AlertBanner
|
||||
open={openNoProjects}
|
||||
message={
|
||||
<div>
|
||||
No project available. Please{' '}
|
||||
<Link
|
||||
style={{ color: 'inherit', fontWeight: 'bold' }}
|
||||
href="/create"
|
||||
>
|
||||
create a project
|
||||
</Link>{' '}
|
||||
from a template first.
|
||||
</div>
|
||||
}
|
||||
handleClose={handleCloseNoProjects}
|
||||
/>
|
||||
<ContentHeader title="Latest updated">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
if (catalogEntities.length !== 0) {
|
||||
setOpenAdd(true);
|
||||
} else {
|
||||
setOpenNoProjects(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Add project
|
||||
</Button>
|
||||
<AddProjectDialog
|
||||
catalogEntities={catalogEntities}
|
||||
handleClose={() => {
|
||||
setOpenAdd(false);
|
||||
}}
|
||||
open={openAdd}
|
||||
setBazaarProjects={setBazaarProjects}
|
||||
setCatalogEntities={setCatalogEntities}
|
||||
/>
|
||||
<SupportButton />
|
||||
</ContentHeader>
|
||||
<ProjectPreview
|
||||
bazaarProjects={bazaarProjects || []}
|
||||
sortingMethod={compareProjectsByDate}
|
||||
bazaarMembers={bazaarMembers}
|
||||
/>
|
||||
<Content noPadding className={classes.container} />
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { SortView } from './SortView';
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { StatusOK, StatusWarning } from '@backstage/core-components';
|
||||
import { Status } from '../../util/types';
|
||||
|
||||
interface StatusComponent {
|
||||
[key: string]: JSX.Element | undefined;
|
||||
}
|
||||
|
||||
const statuses: StatusComponent = {
|
||||
proposed: <StatusWarning>proposed</StatusWarning>,
|
||||
ongoing: <StatusOK>ongoing</StatusOK>,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
status: Status;
|
||||
styles?: string;
|
||||
};
|
||||
|
||||
export const StatusTag = ({ status, styles }: Props) => {
|
||||
return <div className={styles}>{statuses[status]}</div>;
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { StatusTag } from './StatusTag';
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { bazaarPlugin, BazaarPage } from './plugin';
|
||||
export { EntityBazaarInfoCard } from './components/EntityBazaarInfoCard';
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { bazaarPlugin } from './plugin';
|
||||
|
||||
describe('bazaar', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(bazaarPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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,
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
export const bazaarPlugin = createPlugin({
|
||||
id: 'bazaar',
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const BazaarPage = bazaarPlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () => import('./components/HomePage').then(m => m.HomePage),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
title: 'bazaar',
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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';
|
||||
import 'cross-fetch/polyfill';
|
||||
@@ -0,0 +1,11 @@
|
||||
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
||||
// It should be published with your NPM package. It should not be tracked by Git.
|
||||
{
|
||||
"tsdocVersion": "0.12",
|
||||
"toolPackages": [
|
||||
{
|
||||
"packageName": "@microsoft/api-extractor",
|
||||
"packageVersion": "7.18.9"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 { Entity } from '@backstage/catalog-model';
|
||||
import { IdentityApi } from '@backstage/core-api';
|
||||
import { BazaarProject, Status } from './types';
|
||||
|
||||
export const getEntityRef = (entity: Entity) => {
|
||||
return `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
|
||||
};
|
||||
|
||||
export const updateMetadata = async (
|
||||
entity: Entity,
|
||||
name: string,
|
||||
announcement: string,
|
||||
status: Status,
|
||||
baseUrl: string,
|
||||
) => {
|
||||
return await fetch(`${baseUrl}/api/bazaar/metadata`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
entity_ref: getEntityRef(entity),
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
announcement: announcement,
|
||||
status: status,
|
||||
}),
|
||||
}).then(resp => resp.json());
|
||||
};
|
||||
|
||||
export const getBazaarMembers = async (
|
||||
bazaarProjects: BazaarProject[],
|
||||
baseUrl: string,
|
||||
) => {
|
||||
const bazaarMembers = new Map<string, number>();
|
||||
for (const project of bazaarProjects) {
|
||||
const response = await fetch(`${baseUrl}/api/bazaar/members`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
entity_ref: project.entityRef,
|
||||
},
|
||||
});
|
||||
|
||||
const json = await response.json();
|
||||
const nbrOfMembers = await json.data.length;
|
||||
bazaarMembers.set(project.entityRef, nbrOfMembers);
|
||||
}
|
||||
return bazaarMembers;
|
||||
};
|
||||
|
||||
export const deleteEntity = async (
|
||||
baseUrl: string,
|
||||
entity: Entity,
|
||||
identity: IdentityApi,
|
||||
) => {
|
||||
await fetch(`${baseUrl}/api/bazaar/metadata`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
entity_ref: getEntityRef(entity),
|
||||
},
|
||||
});
|
||||
|
||||
await fetch(`${baseUrl}/api/bazaar/members/remove`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
user_id: identity.getUserId(),
|
||||
entity_ref: getEntityRef(entity),
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* 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 Member = {
|
||||
entityRef: string;
|
||||
userId: string;
|
||||
joinDate: string;
|
||||
};
|
||||
|
||||
export type Status = 'ongoing' | 'proposed';
|
||||
|
||||
export type BazaarProject = {
|
||||
name: string;
|
||||
entityRef: string;
|
||||
status: Status;
|
||||
announcement: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type FormValues = {
|
||||
announcement: string;
|
||||
status: string;
|
||||
};
|
||||
Reference in New Issue
Block a user