created bazaarApiRef

Signed-off-by: Klara <klarabroman@live.se>

Co-authored-by: lykkeaxlin <lykkeaxlin@hotmail.com>
This commit is contained in:
Klara
2021-09-16 14:10:47 +02:00
parent c28f97ef45
commit 82c9c4965a
19 changed files with 248 additions and 153 deletions
+18 -2
View File
@@ -70,7 +70,7 @@ export async function createRouter(
}
});
router.put('/members/add', async (request, response) => {
router.put('/member', async (request, response) => {
const userId = request.headers.user_id;
const entityRef = request.headers.entity_ref;
@@ -83,10 +83,12 @@ export async function createRouter(
response.send({ status: 'ok' });
});
router.delete('/members/remove', async (request, response) => {
router.delete('/member', async (request, response) => {
const userId = request.headers.user_id;
const entityRef = request.headers.entity_ref;
console.log('------- user id ----- ', userId, entityRef);
const count = await db?.('public.members')
.where({ entity_ref: entityRef })
.andWhere('user_id', userId)
@@ -99,6 +101,20 @@ export async function createRouter(
}
});
router.delete('/members', async (request, response) => {
const entityRef = request.headers.entity_ref;
const count = await db?.('public.members')
.where({ entity_ref: entityRef })
.del();
if (count) {
response.send({ status: 'ok' });
} else {
response.status(404).json({ message: 'Record not found' });
}
});
router.get('/metadata', async (request, response) => {
const entityRef = request.headers.entity_ref;
+1
View File
@@ -50,6 +50,7 @@ Add a **Bazaar icon** to the Sidebar to easily access the Bazaar. In `packages/a
Add a **Bazaar card** to the overview tab on the `packages/app/src/components/catalog/EntityPage.tsx` add:
```diff
+ import { EntityBazaarInfoCard } from '@backstage/plugin-bazaar';
const overviewContent = (
<Grid item md={8} xs={12}>
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Spotify AB
* 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.
+177
View File
@@ -0,0 +1,177 @@
/*
* 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 { createApiRef, IdentityApi } from '@backstage/core-plugin-api';
import { BazaarProject, Status } from './types';
export const getEntityRef = (entity: Entity) => {
return `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
};
export const bazaarApiRef = createApiRef<BazaarApi>({
id: 'bazaar',
description: 'Used to make requests towards the bazaar backend',
});
export interface BazaarApi {
updateMetadata(
baseUrl: string,
entity: Entity,
name: string,
announcement: string,
status: Status,
): Promise<any>;
getMetadata(baseUrl: string, entity: Entity): Promise<any>;
getMemberCounts(
bazaarProjects: BazaarProject[],
baseUrl: string,
): Promise<Map<string, number>>;
getMembers(baseUrl: string, entity: Entity): Promise<any>;
deleteMember(baseUrl: string, entity: Entity): Promise<void>;
deleteMembers(baseUrl: string, entity: Entity): Promise<void>;
addMember(baseUrl: string, entity: Entity): Promise<void>;
getEntities(baseUrl: string): Promise<any>;
deleteEntity(baseUrl: string, entity: Entity): Promise<void>;
}
export class BazaarClient implements BazaarApi {
private readonly identityApi: IdentityApi;
constructor(options: { identityApi: IdentityApi }) {
this.identityApi = options.identityApi;
}
async updateMetadata(
baseUrl: string,
entity: Entity,
name: string,
announcement: string,
status: Status,
): Promise<any> {
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());
}
async getMetadata(baseUrl: string, entity: Entity): Promise<any> {
return await fetch(`${baseUrl}/api/bazaar/metadata`, {
method: 'GET',
headers: {
entity_ref: getEntityRef(entity),
},
});
}
async getMemberCounts(
bazaarProjects: BazaarProject[],
baseUrl: string,
): Promise<Map<string, number>> {
const members = 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;
members.set(project.entityRef, nbrOfMembers);
}
return members;
}
async getMembers(baseUrl: string, entity: Entity): Promise<any> {
return await fetch(`${baseUrl}/api/bazaar/members`, {
method: 'GET',
headers: {
entity_ref: getEntityRef(entity),
},
}).then(resp => resp.json());
}
async addMember(baseUrl: string, entity: Entity): Promise<void> {
await fetch(`${baseUrl}/api/bazaar/member`, {
method: 'PUT',
headers: {
user_id: this.identityApi.getUserId(),
entity_ref: getEntityRef(entity),
},
});
}
async deleteMember(baseUrl: string, entity: Entity): Promise<void> {
await fetch(`${baseUrl}/api/bazaar/member`, {
method: 'DELETE',
headers: {
user_id: this.identityApi.getUserId(),
entity_ref: getEntityRef(entity),
},
});
}
async deleteMembers(baseUrl: string, entity: Entity): Promise<void> {
await fetch(`${baseUrl}/api/bazaar/members`, {
method: 'DELETE',
headers: {
entity_ref: getEntityRef(entity),
},
});
}
async getEntities(baseUrl: string): Promise<any> {
return await fetch(`${baseUrl}/api/bazaar/entities`, {
method: 'GET',
}).then(resp => resp.json());
}
async deleteEntity(baseUrl: string, entity: Entity): Promise<void> {
await fetch(`${baseUrl}/api/bazaar/metadata`, {
method: 'DELETE',
headers: {
entity_ref: getEntityRef(entity),
},
});
await fetch(`${baseUrl}/api/bazaar/members`, {
method: 'DELETE',
headers: {
user_id: this.identityApi.getUserId(),
entity_ref: getEntityRef(entity),
},
});
}
}
@@ -17,11 +17,11 @@
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';
import { BazaarProject, FormValues, Status } from '../../types';
import { bazaarApiRef } from '../../api';
type Props = {
catalogEntities: Entity[];
@@ -38,6 +38,7 @@ export const AddProjectDialog = ({
setBazaarProjects,
setCatalogEntities,
}: Props) => {
const bazaarApi = useApi(bazaarApiRef);
const baseUrl = useApi(configApiRef)
.getConfig('backend')
.getString('baseUrl');
@@ -83,12 +84,12 @@ export const AddProjectDialog = ({
return oldEntities.filter(entity => entity !== selectedEntity);
});
await updateMetadata(
await bazaarApi.updateMetadata(
baseUrl,
selectedEntity!,
selectedEntity!.metadata.name,
formValues.announcement,
formValues.status,
baseUrl,
);
handleClose();
@@ -30,12 +30,8 @@ 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';
import { useApi, configApiRef } from '@backstage/core-plugin-api';
import { bazaarApiRef } from '../../api';
const styles = (theme: Theme) =>
createStyles({
@@ -113,10 +109,10 @@ export const DeleteProjectDialog = ({
handleClose();
};
const identity = useApi(identityApiRef);
const bazaarApi = useApi(bazaarApiRef);
const handleSubmit = async () => {
await deleteEntity(baseUrl, entity, identity);
await bazaarApi.deleteEntity(baseUrl, entity);
setIsBazaar(false);
handleCloseAndClear();
};
@@ -16,10 +16,10 @@
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';
import { BazaarProject, FormValues } from '../../types';
import { bazaarApiRef } from '../../api';
type Props = {
entity: Entity;
@@ -46,6 +46,8 @@ export const EditProjectDialog = ({
status: bazaarProject.status,
});
const bazaarApi = useApi(bazaarApiRef);
useEffect(() => {
setDefaultValues({
announcement: bazaarProject.announcement,
@@ -56,12 +58,12 @@ export const EditProjectDialog = ({
const handleSave: any = async (getValues: any, _: any) => {
const formValues = getValues();
const updateResponse = await updateMetadata(
const updateResponse = await bazaarApi.updateMetadata(
baseUrl,
entity!,
entity.metadata.name,
formValues.announcement,
formValues.status,
baseUrl,
);
if (updateResponse.status === 'ok')
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Spotify AB
* 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.
@@ -52,9 +52,9 @@ import {
identityApiRef,
configApiRef,
} from '@backstage/core-plugin-api';
import { getEntityRef } from '../../util/dbRequests';
import { useAsync } from 'react-use';
import { Member, BazaarProject } from '../../util/types';
import { Member, BazaarProject } from '../../types';
import { bazaarApiRef, getEntityRef } from '../../api';
const useStyles = makeStyles({
description: {
@@ -82,11 +82,13 @@ const sortMembers = (m1: Member, m2: Member) => {
export const EntityBazaarInfoCard = () => {
const { entity } = useEntity();
const classes = useStyles();
const bazaarApi = useApi(bazaarApiRef);
const identity = useApi(identityApiRef);
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>({
@@ -103,13 +105,7 @@ export const EntityBazaarInfoCard = () => {
.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 response = await bazaarApi.getMembers(baseUrl, entity);
const dbMembers = response.data.map((obj: any) => {
const member: Member = {
userId: obj.user_id,
@@ -131,16 +127,11 @@ export const EntityBazaarInfoCard = () => {
};
const getMetadata = async () => {
const response = await fetch(`${baseUrl}/api/bazaar/metadata`, {
method: 'GET',
headers: {
entity_ref: getEntityRef(entity),
},
});
const response = await bazaarApi.getMetadata(baseUrl, entity);
if (response.status !== 404) {
setIsBazaar(true);
const data = await response.json().then(resp => resp.data);
const data = await response.json().then((resp: any) => resp.data);
setBazaarProject({
entityRef: data[0].entityRef,
@@ -183,32 +174,20 @@ export const EntityBazaarInfoCard = () => {
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,
});
await bazaarApi.addMember(baseUrl, entity);
} else {
setMembers(
members.filter(
(member: Member) => member.userId !== identity.getUserId(),
),
);
await fetch(`${baseUrl}/api/bazaar/members/remove`, {
method: 'DELETE',
headers: headers,
});
await bazaarApi.deleteMember(baseUrl, entity);
}
};
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Spotify AB
* 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.
@@ -17,7 +17,7 @@
import React from 'react';
import { Controller, Control, FieldError } from 'react-hook-form';
import { TextField } from '@material-ui/core';
import { FormValues } from '../../util/types';
import { FormValues } from '../../types';
type Props = {
inputType: string;
@@ -20,7 +20,7 @@ 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';
import { FormValues } from '../../types';
type Props = {
options: string[];
@@ -28,7 +28,7 @@ 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';
import { BazaarProject } from '../../types';
const useStyles = makeStyles({
statusTag: {
@@ -29,7 +29,7 @@ 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';
import { FormValues } from '../../types';
const styles = (theme: Theme) =>
createStyles({
@@ -19,7 +19,7 @@ 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';
import { BazaarProject } from '../../types';
type Props = {
bazaarProjects: BazaarProject[];
@@ -27,13 +27,13 @@ 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';
import { BazaarProject } from '../../types';
import { bazaarApiRef } from '../../api';
const useStyles = makeStyles({
container: {
@@ -50,6 +50,7 @@ export const SortView = () => {
new Map(),
);
const [bazaarProjects, setBazaarProjects] = useState<BazaarProject[]>([]);
const bazaarApi = useApi(bazaarApiRef);
const catalogApi = useApi(catalogApiRef);
const baseUrl = useApi(configApiRef)
.getConfig('backend')
@@ -77,10 +78,7 @@ export const SortView = () => {
fields: ['apiVersion', 'kind', 'metadata', 'spec'],
});
const response = await fetch(`${baseUrl}/api/bazaar/entities`, {
method: 'GET',
}).then(resp => resp.json());
const response = await bazaarApi.getEntities(baseUrl);
const dbProjects: BazaarProject[] = [];
const bazaarProjectRefs: string[] = [];
@@ -96,7 +94,7 @@ export const SortView = () => {
bazaarProjectRefs.push(project.entity_ref);
});
setBazaarMembers(await getBazaarMembers(dbProjects, baseUrl));
setBazaarMembers(await bazaarApi.getMemberCounts(dbProjects, baseUrl));
setBazaarProjects(dbProjects);
setCatalogEntities(
entities.items.filter((entity: Entity) => {
@@ -16,7 +16,7 @@
import React from 'react';
import { StatusOK, StatusWarning } from '@backstage/core-components';
import { Status } from '../../util/types';
import { Status } from '../../types';
interface StatusComponent {
[key: string]: JSX.Element | undefined;
+13 -2
View File
@@ -14,18 +14,29 @@
* limitations under the License.
*/
import { rootRouteRef } from './routes';
import {
createApiFactory,
createPlugin,
createRoutableExtension,
identityApiRef,
} from '@backstage/core-plugin-api';
import { rootRouteRef } from './routes';
import { bazaarApiRef, BazaarClient } from './api';
export const bazaarPlugin = createPlugin({
id: 'bazaar',
routes: {
root: rootRouteRef,
},
apis: [
createApiFactory({
api: bazaarApiRef,
deps: {
identityApi: identityApiRef,
},
factory: ({ identityApi }) => new BazaarClient({ identityApi }),
}),
],
});
export const BazaarPage = bazaarPlugin.provide(
-86
View File
@@ -1,86 +0,0 @@
/*
* 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-plugin-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),
},
});
};