diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts
index 7cce5ee414..3afde44d5e 100644
--- a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts
+++ b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts
@@ -18,14 +18,17 @@ import { DatabaseHandler } from './DatabaseHandler';
import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
const bazaarProject: any = {
- name: 'name',
- entityRef: 'ref',
+ name: 'n1',
+ entityRef: 'ref1',
community: '',
status: 'proposed',
announcement: 'a',
membersCount: 0,
+ startDate: '2021-11-07T13:27:00.000Z',
+ endDate: null,
+ size: 'small',
+ responsible: 'r',
};
-
describe('DatabaseHandler', () => {
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
@@ -33,18 +36,40 @@ describe('DatabaseHandler', () => {
async function createDatabaseHandler(databaseId: TestDatabaseId) {
const knex = await databases.init(databaseId);
- return await DatabaseHandler.create({ database: knex });
+ return {
+ knex,
+ dbHandler: await DatabaseHandler.create({ database: knex }),
+ };
}
it.each(databases.eachSupportedId())(
- 'should do a full sync with the locations on connect, %p',
+ 'should insert and get entity, %p',
async databaseId => {
- const db = await createDatabaseHandler(databaseId);
- await db.insertMetadata(bazaarProject);
+ const { knex, dbHandler } = await createDatabaseHandler(databaseId);
- const entities = await db.getEntities();
- expect(entities.length).toEqual(1);
- expect(entities[0].entity_ref).toEqual(bazaarProject.entityRef);
+ await knex('metadata').insert({
+ entity_ref: bazaarProject.entityRef,
+ name: bazaarProject.name,
+ announcement: bazaarProject.announcement,
+ community: bazaarProject.community,
+ status: bazaarProject.status,
+ updated_at: new Date().toISOString(),
+ start_date: bazaarProject.startDate,
+ end_date: bazaarProject.endDate,
+ size: bazaarProject.size,
+ responsible: bazaarProject.responsible,
+ });
+
+ const res = await dbHandler.getMetadata('ref1');
+
+ expect(res).toHaveLength(1);
+ expect(res[0].announcement).toEqual('a');
+ expect(res[0].community).toEqual('');
+ expect(res[0].status).toEqual('proposed');
+ expect(res[0].start_date).toEqual('2021-11-07T13:27:00.000Z');
+ expect(res[0].end_date).toEqual(null);
+ expect(res[0].size).toEqual('small');
+ expect(res[0].responsible).toEqual('r');
},
60_000,
);
diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.ts
index d4da6e65e7..60f579af2d 100644
--- a/plugins/bazaar-backend/src/service/DatabaseHandler.ts
+++ b/plugins/bazaar-backend/src/service/DatabaseHandler.ts
@@ -43,6 +43,20 @@ export class DatabaseHandler {
this.database = options.database;
}
+ private columns = [
+ 'members.entity_ref',
+ 'metadata.entity_ref',
+ 'metadata.name',
+ 'metadata.announcement',
+ 'metadata.status',
+ 'metadata.updated_at',
+ 'metadata.community',
+ 'metadata.size',
+ 'metadata.start_date',
+ 'metadata.end_date',
+ 'metadata.responsible',
+ ];
+
async getMembers(entityRef: string) {
return await this.database
.select('*')
@@ -72,25 +86,25 @@ export class DatabaseHandler {
'coalesce(count(members.entity_ref), 0) as members_count',
);
- const columns = [
- 'members.entity_ref',
- 'metadata.entity_ref',
- 'metadata.name',
- 'metadata.announcement',
- 'metadata.status',
- 'metadata.updated_at',
- 'metadata.community',
- ];
-
return await this.database('metadata')
- .select([...columns, coalesce])
+ .select([...this.columns, coalesce])
.where({ 'metadata.entity_ref': entityRef })
- .groupBy(columns)
+ .groupBy(this.columns)
.leftJoin('members', 'metadata.entity_ref', '=', 'members.entity_ref');
}
async insertMetadata(bazaarProject: any) {
- const { name, entityRef, community, announcement, status } = bazaarProject;
+ const {
+ name,
+ entityRef,
+ community,
+ announcement,
+ status,
+ size,
+ startDate,
+ endDate,
+ responsible,
+ } = bazaarProject;
await this.database
.insert({
@@ -100,12 +114,25 @@ export class DatabaseHandler {
announcement: announcement,
status: status,
updated_at: new Date().toISOString(),
+ size,
+ start_date: startDate,
+ end_date: endDate,
+ responsible,
})
.into('metadata');
}
async updateMetadata(bazaarProject: any) {
- const { entityRef, community, announcement, status } = bazaarProject;
+ const {
+ entityRef,
+ community,
+ announcement,
+ status,
+ size,
+ startDate,
+ endDate,
+ responsible,
+ } = bazaarProject;
return await this.database('metadata')
.where({ entity_ref: entityRef })
@@ -114,6 +141,10 @@ export class DatabaseHandler {
community: community,
status: status,
updated_at: new Date().toISOString(),
+ size,
+ start_date: startDate,
+ end_date: endDate,
+ responsible,
});
}
@@ -128,18 +159,9 @@ export class DatabaseHandler {
'coalesce(count(members.entity_ref), 0) as members_count',
);
- const columns = [
- 'members.entity_ref',
- 'metadata.entity_ref',
- 'metadata.name',
- 'metadata.announcement',
- 'metadata.status',
- 'metadata.updated_at',
- 'metadata.community',
- ];
return await this.database('metadata')
- .select([...columns, coalesce])
- .groupBy(columns)
+ .select([...this.columns, coalesce])
+ .groupBy(this.columns)
.leftJoin('members', 'metadata.entity_ref', '=', 'members.entity_ref');
}
}
diff --git a/plugins/bazaar/README.md b/plugins/bazaar/README.md
index cd26f01997..c807b94639 100644
--- a/plugins/bazaar/README.md
+++ b/plugins/bazaar/README.md
@@ -58,7 +58,7 @@ const overviewContent = (
-+
++
+
+
@@ -75,12 +75,23 @@ The latest modified Bazaar projects are displayed in the Bazaar landing page, lo
### Workflow
-To add a project to the Bazaar, you need to create a project with one of the templates in Backstage. Click the add project-button, choose the project and fill in the form. You will be asked to add an announcement for new team members. The purpose of the announcement is for you to present your ideas and what skills you are looking for. Further you need to provide the status of the project.
+To add a project to the Bazaar, you need to create a project with one of the templates in Backstage. Click the add project-button, choose the project and fill in the form.
+
+The following fields are mandatory:
+
+- announcement - present your idea and what skills you are looking for
+- status - whether or not the project has started
+- size - small, medium or large
+
+The other fields are:
+
+- start date
+- end date
+- responsible - main contact person of the project
+- community link - link where the project members can chat, e.g. Teams or Discord link
When the project is added, you will see the Bazaar information in the Bazaar card on the entity page. There you can join a project, edit or delete it.
-
-
### Database
The metadata related to the Bazaar is stored in a database. Right now there are two tables, one for storing the metadata and one for storing the members of a Bazaar project.
@@ -92,6 +103,10 @@ The metadata related to the Bazaar is stored in a database. Right now there are
- announcement - announcement of the project and its current need of skills/team member
- status - status of the project, 'proposed' or 'ongoing'
- updated_at - date when the Bazaar information was last modified (ISO 8601 format)
+- size - small, medium or large
+- start_date - date when the project is estimated to start (ISO 8601 format)
+- end_date - date when the project is estimated to end (ISO 8601 format)
+- responsible - main contact person of the project
**members**:
@@ -107,12 +122,8 @@ The metadata related to the Bazaar is stored in a database. Right now there are
- Bazaar landing page
- - Add a tab 'My page', where your personal data is displayed. For example: your projects and its latest activities, projects or tags you are following etc.
- - Make it possible to sort the project based on the number of members
-
-- Bazaar card
-
- - Make it possible to follow tags/projects
+ - Add a tab 'My page', where your personal data is displayed. For example: your projects and its latest activities etc.
+ - Make it possible to sort the project based on e.g. the number of members
- Bazaar tab on the EntityPage
diff --git a/plugins/bazaar/media/bazaar_demo.gif b/plugins/bazaar/media/bazaar_demo.gif
deleted file mode 100644
index c5383de477..0000000000
Binary files a/plugins/bazaar/media/bazaar_demo.gif and /dev/null differ
diff --git a/plugins/bazaar/media/bazaar_pr_fullscreen.png b/plugins/bazaar/media/bazaar_pr_fullscreen.png
index d0187977db..6c5cf1277b 100644
Binary files a/plugins/bazaar/media/bazaar_pr_fullscreen.png and b/plugins/bazaar/media/bazaar_pr_fullscreen.png differ
diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json
index 5a98abfc3a..556218c85a 100644
--- a/plugins/bazaar/package.json
+++ b/plugins/bazaar/package.json
@@ -22,7 +22,7 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.5",
- "@backstage/cli": "^0.8.0",
+ "@backstage/cli": "^0.8.1",
"@backstage/core-components": "^0.7.2",
"@backstage/core-plugin-api": "^0.1.12",
"@backstage/plugin-catalog": "^0.7.2",
@@ -30,7 +30,9 @@
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
+ "@material-ui/pickers": "^3.3.10",
"@testing-library/jest-dom": "^5.10.1",
+ "@date-io/luxon": "1.x",
"luxon": "^2.0.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
diff --git a/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx b/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx
index 82d682b4df..310ffe051d 100644
--- a/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx
+++ b/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx
@@ -16,11 +16,11 @@
import React, { useState, useEffect } from 'react';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
-import { SubmitHandler } from 'react-hook-form';
+import { UseFormReset, UseFormGetValues } from 'react-hook-form';
import { useApi } from '@backstage/core-plugin-api';
import { ProjectDialog } from '../ProjectDialog';
import { ProjectSelector } from '../ProjectSelector';
-import { BazaarProject, FormValues, Status } from '../../types';
+import { BazaarProject, FormValues, Size, Status } from '../../types';
import { bazaarApiRef } from '../../api';
type Props = {
@@ -52,6 +52,10 @@ export const AddProjectDialog = ({
community: '',
announcement: '',
status: 'proposed' as Status,
+ size: 'medium' as Size,
+ responsible: '',
+ startDate: null,
+ endDate: null,
};
const handleListItemClick = (entity: Entity) => {
@@ -63,9 +67,9 @@ export const AddProjectDialog = ({
handleClose();
};
- const handleSave: SubmitHandler = async (
- getValues: any,
- reset: any,
+ const handleSave: any = async (
+ getValues: UseFormGetValues,
+ reset: UseFormReset,
) => {
const formValues = getValues();
@@ -77,6 +81,10 @@ export const AddProjectDialog = ({
status: formValues.status,
community: formValues.community,
membersCount: 0,
+ size: formValues.size,
+ startDate: formValues.startDate ?? null,
+ endDate: formValues.endDate ?? null,
+ responsible: formValues.responsible,
} as BazaarProject);
fetchBazaarProjects();
diff --git a/plugins/bazaar/src/components/DateSelector/DateSelector.tsx b/plugins/bazaar/src/components/DateSelector/DateSelector.tsx
new file mode 100644
index 0000000000..db564a7b68
--- /dev/null
+++ b/plugins/bazaar/src/components/DateSelector/DateSelector.tsx
@@ -0,0 +1,72 @@
+/*
+ * 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 FormControl from '@material-ui/core/FormControl';
+import { Controller, Control, UseFormSetValue } from 'react-hook-form';
+import { FormValues } from '../../types';
+import {
+ KeyboardDatePicker,
+ MuiPickersUtilsProvider,
+} from '@material-ui/pickers';
+import LuxonUtils from '@date-io/luxon';
+import { IconButton } from '@material-ui/core';
+import ClearIcon from '@material-ui/icons/Clear';
+
+type Props = {
+ name: 'startDate' | 'endDate';
+ control: Control;
+ setValue: UseFormSetValue;
+};
+
+export const DateSelector = ({ name, control, setValue }: Props) => {
+ const label = `${
+ name.charAt(0).toLocaleUpperCase('en-US') + name.slice(1, name.indexOf('D'))
+ } date`;
+
+ return (
+ (
+
+
+ {
+ setValue(name, date?.toISO());
+ }}
+ InputProps={{
+ endAdornment: (
+ setValue(name, null)}>
+
+
+ ),
+ }}
+ InputAdornmentProps={{
+ position: 'start',
+ }}
+ />
+
+
+ )}
+ />
+ );
+};
diff --git a/plugins/bazaar/src/components/DateSelector/index.ts b/plugins/bazaar/src/components/DateSelector/index.ts
new file mode 100644
index 0000000000..9bdb078164
--- /dev/null
+++ b/plugins/bazaar/src/components/DateSelector/index.ts
@@ -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 { DateSelector } from './DateSelector';
diff --git a/plugins/bazaar/src/components/DoubleDateSelector/DoubleDateSelector.tsx b/plugins/bazaar/src/components/DoubleDateSelector/DoubleDateSelector.tsx
new file mode 100644
index 0000000000..b37e734453
--- /dev/null
+++ b/plugins/bazaar/src/components/DoubleDateSelector/DoubleDateSelector.tsx
@@ -0,0 +1,67 @@
+/*
+ * 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 { Control, UseFormSetValue } from 'react-hook-form';
+import { FormValues } from '../../types';
+import { Typography } from '@material-ui/core';
+import { DateSelector } from '../DateSelector/DateSelector';
+
+type Props = {
+ control: Control;
+ setValue: UseFormSetValue;
+};
+
+export const DoubleDateSelector = ({ control, setValue }: Props) => {
+ return (
+
+
+
+
+
+
+ -
+
+
+
+
+
+ );
+};
diff --git a/plugins/bazaar/src/components/DoubleDateSelector/index.ts b/plugins/bazaar/src/components/DoubleDateSelector/index.ts
new file mode 100644
index 0000000000..9162827b35
--- /dev/null
+++ b/plugins/bazaar/src/components/DoubleDateSelector/index.ts
@@ -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 { DoubleDateSelector } from './DoubleDateSelector';
diff --git a/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx b/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx
index 172bdf548d..41c9e474d4 100644
--- a/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx
+++ b/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx
@@ -18,8 +18,9 @@ import React, { useState, useEffect } from 'react';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import { ProjectDialog } from '../ProjectDialog';
-import { BazaarProject, FormValues } from '../../types';
+import { BazaarProject, FormValues, Size, Status } from '../../types';
import { bazaarApiRef } from '../../api';
+import { UseFormGetValues } from 'react-hook-form';
type Props = {
entity: Entity;
@@ -41,6 +42,10 @@ export const EditProjectDialog = ({
announcement: bazaarProject.announcement,
community: bazaarProject.community,
status: bazaarProject.status,
+ size: bazaarProject.size,
+ startDate: bazaarProject?.startDate ?? null,
+ endDate: bazaarProject?.endDate ?? null,
+ responsible: bazaarProject.responsible,
});
const bazaarApi = useApi(bazaarApiRef);
@@ -50,19 +55,27 @@ export const EditProjectDialog = ({
announcement: bazaarProject.announcement,
community: bazaarProject.community,
status: bazaarProject.status,
+ size: bazaarProject.size,
+ startDate: bazaarProject?.startDate ?? null,
+ endDate: bazaarProject?.endDate ?? null,
+ responsible: bazaarProject.responsible,
});
}, [bazaarProject]);
- const handleSave: any = async (getValues: any, _: any) => {
+ const handleSave: any = async (getValues: UseFormGetValues) => {
const formValues = getValues();
const updateResponse = await bazaarApi.updateMetadata({
name: entity.metadata.name,
entityRef: stringifyEntityRef(entity),
announcement: formValues.announcement,
- status: formValues.status,
+ status: formValues.status as Status,
community: formValues.community,
membersCount: bazaarProject.membersCount,
+ size: formValues.size as Size,
+ startDate: formValues?.startDate ?? null,
+ endDate: formValues?.endDate ?? null,
+ responsible: formValues.responsible,
});
if (updateResponse.status === 'ok') fetchBazaarProject();
diff --git a/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx b/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx
index 6bf844d592..acae8f5342 100644
--- a/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx
+++ b/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx
@@ -121,6 +121,10 @@ export const EntityBazaarInfoCard = () => {
status: metadata.status,
updatedAt: metadata.updated_at,
membersCount: metadata.members_count,
+ size: metadata.size,
+ startDate: metadata.start_date,
+ endDate: metadata.end_date,
+ responsible: metadata.responsible,
} as BazaarProject;
}
}
@@ -259,7 +263,7 @@ export const EntityBazaarInfoCard = () => {
-
+
{bazaarProject?.value?.announcement
? bazaarProject?.value?.announcement
@@ -278,17 +282,11 @@ export const EntityBazaarInfoCard = () => {
-
-
-
-
-
-
-
+
{' '}
{members?.value?.length ? (
- members.value.slice(0, 3).map((member: Member) => {
+ members.value.slice(0, 7).map((member: Member) => {
return (