Replace name with title, and make card height adjustable

Signed-off-by: Frida Jacobsson <fridahelenajacobsson@gmail.com>
This commit is contained in:
Frida Jacobsson
2022-11-27 16:40:39 +01:00
parent 50b941cf6b
commit 9b1891061c
24 changed files with 133 additions and 60 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-bazaar': minor
---
`HomePageBazaarInfoCard` is now displaying `title` instead of `name`. Title is a string that doesn't have to be URL friendly.
The BazaarOverviewCard have the new property `fullHeight`. Link in `BazaarOverviewCard`is moved to header in card.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-bazaar-backend': patch
---
Column `title` has replaced column `name` for `BazaarProject` in database
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright 2022 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.
*/
// Update with your config settings.
// This file makes it possible to run "yarn knex migrate:make some_file_name"
// to assist in making new migrations
module.exports = {
client: 'better-sqlite3',
connection: ':memory:',
useNullAsDefault: true,
migrations: {
directory: './migrations',
},
};
@@ -0,0 +1,35 @@
/*
* Copyright 2022 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.
*/
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = async function up(knex) {
await knex.schema.alterTable('metadata', table => {
table.renameColumn('name', 'title');
});
};
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = async function down(knex) {
await knex.schema.alterTable('metadata', table => {
table.renameColumn('title', 'name');
});
};
@@ -19,7 +19,7 @@ import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
import { Knex as KnexType } from 'knex';
const bazaarProject: any = {
name: 'n1',
title: 'n1',
entityRef: 'ref1',
community: '',
status: 'proposed',
@@ -64,7 +64,7 @@ describe('DatabaseHandler', () => {
await knex('metadata').insert({
entity_ref: bazaarProject.entityRef,
name: bazaarProject.name,
title: bazaarProject.title,
description: bazaarProject.description,
community: bazaarProject.community,
status: bazaarProject.status,
@@ -52,7 +52,7 @@ export class DatabaseHandler {
private columns = [
'metadata.id',
'metadata.entity_ref',
'metadata.name',
'metadata.title',
'metadata.description',
'metadata.status',
'metadata.updated_at',
@@ -116,7 +116,7 @@ export class DatabaseHandler {
async insertMetadata(bazaarProject: any) {
const {
name,
title,
entityRef,
community,
description,
@@ -129,7 +129,7 @@ export class DatabaseHandler {
await this.client
.insert({
name,
title,
entity_ref: entityRef,
community,
description,
@@ -145,7 +145,7 @@ export class DatabaseHandler {
async updateMetadata(bazaarProject: any) {
const {
name,
title,
id,
entityRef,
community,
@@ -158,7 +158,7 @@ export class DatabaseHandler {
} = bazaarProject;
return await this.client('metadata').where({ id: id }).update({
name,
title,
entity_ref: entityRef,
description,
community,
+3 -3
View File
@@ -80,13 +80,13 @@ export const homePage = (
+ </Grid>
+ <Grid item xs={12} >
+ <BazaarOverviewCard order='random' fullWidth />
+ <BazaarOverviewCard order='random' fullWidth fullHeight />
+ </Grid>
{/* ...other homepage items */}
```
The property `fullWidth` is optional and can be used to adjust the card to fit a grid with column width 12.
The properties `fullHeight` and `fullWidth` are optional and can be used to adjust the cards styling.
## How does the Bazaar work?
@@ -106,7 +106,7 @@ To add a project to the bazaar, simply click on the `add-project` button and fil
The following fields are mandatory:
- name - name of the project on URL safe format
- title - title of the project
- description - present your idea and what skills you are looking for
- status - whether or not the project has started
- size - small, medium or large
+1
View File
@@ -17,6 +17,7 @@ export const BazaarOverviewCard: (
export type BazaarOverviewCardProps = {
order: 'latest' | 'random';
fullWidth?: boolean;
fullHeight?: boolean;
};
// @public (undocumented)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

@@ -42,8 +42,7 @@ export const AddProjectDialog = ({
const [selectedEntity, setSelectedEntity] = useState<Entity | null>(null);
const defaultValues = {
name: '',
title: 'Add project',
title: '',
community: '',
description: '',
status: 'proposed' as Status,
@@ -65,6 +64,7 @@ export const AddProjectDialog = ({
reset: UseFormReset<FormValues>,
) => {
const formValues = getValues();
const response = await bazaarApi.addProject({
...formValues,
entityRef: selectedEntity ? stringifyEntityRef(selectedEntity) : null,
@@ -24,13 +24,16 @@ import type { BazaarProject } from '../../types';
import { bazaarApiRef } from '../../api';
import { fetchCatalogItems } from '../../util/fetchMethods';
import { parseBazaarProject } from '../../util/parseMethods';
import { ErrorPanel, InfoCard } from '@backstage/core-components';
import { ErrorPanel, InfoCard, Link } from '@backstage/core-components';
import { bazaarPlugin } from '../../plugin';
import { IconButton } from '@material-ui/core';
import StorefrontIcon from '@material-ui/icons/Storefront';
/** @public */
export type BazaarOverviewCardProps = {
order: 'latest' | 'random';
fullWidth?: boolean;
fullHeight?: boolean;
};
const getUnlinkedCatalogEntities = (
@@ -48,7 +51,7 @@ const getUnlinkedCatalogEntities = (
/** @public */
export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => {
const { order, fullWidth = false } = props;
const { order, fullWidth = false, fullHeight = false } = props;
const bazaarApi = useApi(bazaarApiRef);
const catalogApi = useApi(catalogApiRef);
const root = useRouteRef(bazaarPlugin.routes.root);
@@ -127,7 +130,13 @@ export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => {
title={
order === 'latest' ? 'Bazaar Latest Projects' : 'Bazaar Random Projects'
}
deepLink={bazaarLink}
action={
<IconButton>
<Link to={bazaarLink.link} title={bazaarLink.title}>
<StorefrontIcon />
</Link>
</IconButton>
}
>
<ProjectPreview
bazaarProjects={bazaarProjects.value || []}
@@ -135,6 +144,7 @@ export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => {
catalogEntities={unlinkedCatalogEntities || []}
useTablePagination={false}
gridSize={fullWidth ? 2 : 4}
height={fullHeight ? '13rem' : '7rem'}
/>
</InfoCard>
);
@@ -17,7 +17,6 @@
import React from 'react';
import {
Grid,
makeStyles,
Card,
CardContent,
Typography,
@@ -31,13 +30,6 @@ import { entityRouteRef } from '@backstage/plugin-catalog-react';
import { StatusTag } from '../StatusTag';
import { Member, BazaarProject } from '../../types';
const useStyles = makeStyles({
break: {
wordBreak: 'break-word',
textAlign: 'justify',
},
});
type Props = {
bazaarProject: BazaarProject;
members: Member[];
@@ -51,7 +43,6 @@ export const CardContentFields = ({
descriptionSize,
membersSize,
}: Props) => {
const classes = useStyles();
const catalogEntityRoute = useRouteRef(entityRouteRef);
return (
@@ -64,12 +55,7 @@ export const CardContentFields = ({
{bazaarProject.description
.split('\n')
.map((str: string, i: number) => (
<Typography
key={i}
variant="body2"
paragraph
className={classes.break}
>
<Typography key={i} variant="body2" paragraph>
{str}
</Typography>
))}
@@ -114,7 +100,6 @@ export const CardContentFields = ({
picture={member.picture}
/>
<Link
className={classes.break}
target="_blank"
to={
member.userRef
@@ -106,8 +106,8 @@ export const EditProjectDialog = ({
handleClose={handleDeleteClose}
message={[
'Are you sure you want to delete ',
<b key={bazaarProject.name} className={classes.wordBreak}>
{bazaarProject.name}
<b key={bazaarProject.id} className={classes.wordBreak}>
{bazaarProject.title}
</b>,
' from the Bazaar?',
]}
@@ -173,7 +173,7 @@ export const EntityBazaarInfoContent = ({
{parseEntityRef(bazaarProject.entityRef!).name}
</b>,
' from ',
<b className={classes.wordBreak}>{bazaarProject.name}</b>,
<b className={classes.wordBreak}>{bazaarProject.title}</b>,
' ?',
]}
type="unlink"
@@ -182,7 +182,7 @@ export const EntityBazaarInfoContent = ({
)}
<CardHeader
title={<p className={classes.wordBreak}>{bazaarProject?.name!}</p>}
title={<p className={classes.wordBreak}>{bazaarProject?.title!}</p>}
action={
<div>
<IconButton
@@ -237,7 +237,7 @@ export const HomePageBazaarInfoCard = ({
{parseEntityRef(bazaarProject.value?.entityRef!).name}
</b>,
' from ',
<b className={classes.wordBreak}>{bazaarProject.value?.name}</b>,
<b className={classes.wordBreak}>{bazaarProject.value?.title}</b>,
' ?',
]}
type="unlink"
@@ -257,7 +257,7 @@ export const HomePageBazaarInfoCard = ({
<CardHeader
title={
<p className={classes.wordBreak}>
{bazaarProject.value?.name || initProject.name}
{bazaarProject.value?.title || initProject.title}
</p>
}
action={
@@ -30,7 +30,7 @@ type Rules = {
};
type Props = {
inputType: 'description' | 'community' | 'responsible' | 'name';
inputType: 'description' | 'community' | 'responsible' | 'title';
error?: FieldError | undefined;
control: Control<FormValues, object>;
helperText?: string;
@@ -34,7 +34,7 @@ type Props = {
project: BazaarProject;
fetchBazaarProjects: () => Promise<BazaarProject[]>;
catalogEntities: Entity[];
fullHeight?: boolean;
height?: string;
};
const useStyles = makeStyles({
@@ -48,7 +48,6 @@ const useStyles = makeStyles({
WebkitLineClamp: 7,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textAlign: 'justify',
},
memberCount: {
float: 'right',
@@ -60,6 +59,7 @@ const useStyles = makeStyles({
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
height: '5rem',
},
});
@@ -67,10 +67,11 @@ export const ProjectCard = ({
project,
fetchBazaarProjects,
catalogEntities,
height,
}: Props) => {
const classes = useStyles();
const [openCard, setOpenCard] = useState(false);
const { id, name, status, updatedAt, description, membersCount } = project;
const { id, title, status, updatedAt, description, membersCount } = project;
const handleClose = () => {
setOpenCard(false);
@@ -93,7 +94,7 @@ export const ProjectCard = ({
classes={{ root: classes.header }}
title={
<Typography noWrap variant="h6" component="h4">
{name}
{title}
</Typography>
}
subtitle={`updated ${DateTime.fromISO(
@@ -102,7 +103,7 @@ export const ProjectCard = ({
base: DateTime.now(),
})}`}
/>
<CardContent className={classes.content}>
<CardContent className={classes.content} style={{ height: height }}>
<StatusTag styles={classes.statusTag} status={status} />
<Typography variant="body2" className={classes.memberCount}>
{Number(membersCount) === Number(1)
@@ -89,16 +89,14 @@ export const ProjectDialog = ({
</CustomDialogTitle>
<DialogContent style={{ padding: '1rem', paddingTop: '0rem' }} dividers>
<InputField
error={errors.name}
error={errors.title}
control={control}
rules={{
required: true,
pattern: RegExp('^[a-zA-Z0-9_-]*$'),
}}
inputType="name"
helperText="please enter a url safe project name"
inputType="title"
helperText="Please enter a title for your project"
/>
<InputField
error={errors.description}
control={control}
@@ -106,7 +104,7 @@ export const ProjectDialog = ({
required: true,
}}
inputType="description"
helperText="please enter a description"
helperText="Please enter a description"
/>
<InputSelector
@@ -128,7 +126,7 @@ export const ProjectDialog = ({
required: true,
}}
inputType="responsible"
helperText="please enter a contact person"
helperText="Please enter a contact person"
placeholder="Contact person of the project"
/>
@@ -142,7 +140,7 @@ export const ProjectDialog = ({
pattern: RegExp('^(https?)://'),
}}
inputType="community"
helperText="please enter a link starting with http/https"
helperText="Please enter a link starting with http/https"
placeholder="Community link to e.g. Teams or Discord"
/>
@@ -27,6 +27,7 @@ type Props = {
catalogEntities: Entity[];
useTablePagination?: boolean;
gridSize?: GridSize;
height: string;
};
const useStyles = makeStyles({
@@ -55,6 +56,7 @@ export const ProjectPreview = ({
catalogEntities,
useTablePagination = true,
gridSize = 2,
height,
}: Props) => {
const classes = useStyles();
const [page, setPage] = useState(1);
@@ -90,6 +92,7 @@ export const ProjectPreview = ({
key={i}
fetchBazaarProjects={fetchBazaarProjects}
catalogEntities={catalogEntities}
height={height}
/>
</Grid>
);
@@ -27,7 +27,7 @@ import { BazaarProject } from '../../types';
import { bazaarApiRef } from '../../api';
import { Alert } from '@material-ui/lab';
import SearchBar from 'material-ui-search-bar';
import { sortByDate, sortByMembers, sortByName } from '../../util/sortMethods';
import { sortByDate, sortByMembers, sortByTitle } from '../../util/sortMethods';
import { SortMethodSelector } from '../SortMethodSelector';
import { fetchCatalogItems } from '../../util/fetchMethods';
import { parseBazaarProject } from '../../util/parseMethods';
@@ -71,7 +71,7 @@ export const SortView = () => {
const bazaarApi = useApi(bazaarApiRef);
const catalogApi = useApi(catalogApiRef);
const classes = useStyles();
const sortMethods = [sortByDate, sortByName, sortByMembers];
const sortMethods = [sortByDate, sortByTitle, sortByMembers];
const [sortMethodNbr, setSortMethodNbr] = useState(0);
const [openAdd, setOpenAdd] = useState(false);
const [searchValue, setSearchValue] = useState('');
@@ -99,7 +99,7 @@ export const SortView = () => {
const getSearchResults = () => {
return bazaarProjects.value
?.filter(project => project.name.includes(searchValue))
?.filter(project => project.title.includes(searchValue))
.sort(sortMethods[sortMethodNbr]);
};
@@ -199,6 +199,7 @@ export const SortView = () => {
bazaarProjects={getSearchResults() || []}
fetchBazaarProjects={fetchBazaarProjects}
catalogEntities={unlinkedCatalogEntities || []}
height="13rem"
/>
<Content noPadding className={classes.container} />
</Content>
+2 -2
View File
@@ -27,7 +27,7 @@ export type Status = 'ongoing' | 'proposed';
export type Size = 'small' | 'medium' | 'large';
export type BazaarProject = {
name: string;
title: string;
id: number;
entityRef?: string;
community: string;
@@ -42,7 +42,7 @@ export type BazaarProject = {
};
export type FormValues = {
name: string;
title: string;
description: string;
community: string;
status: string;
+1 -1
View File
@@ -20,7 +20,7 @@ export const parseBazaarProject = (metadata: any): BazaarProject => {
return {
id: metadata.id,
entityRef: metadata.entity_ref,
name: metadata.name,
title: metadata.title,
community: metadata.community,
description: metadata.description,
status: metadata.status,
+3 -3
View File
@@ -26,10 +26,10 @@ export const sortByDate = (a: BazaarProject, b: BazaarProject): number => {
return dateB - dateA;
};
export const sortByName = (a: BazaarProject, b: BazaarProject) => {
if (a.name < b.name) {
export const sortByTitle = (a: BazaarProject, b: BazaarProject) => {
if (a.title < b.title) {
return -1;
} else if (a.name > b.name) {
} else if (a.title > b.title) {
return 1;
}
return 0;