added backend test and updated api reports
Signed-off-by: Lykke Axlin <lykkeaxlin@hotmail.com> Co-authored-by: klaraab <klarabroman@live.se>
This commit is contained in:
@@ -20,7 +20,7 @@ export interface RouterOptions {
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
database?: PluginDatabaseManager;
|
||||
database: PluginDatabaseManager;
|
||||
// (undocumented)
|
||||
logger: Logger_2;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"express-promise-router": "^4.1.0",
|
||||
"knex": "^0.95.1",
|
||||
"supertest": "^6.1.6",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.2.1",
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 { DatabaseHandler } from './DatabaseHandler';
|
||||
|
||||
const members: Array<any> = [
|
||||
{
|
||||
entity_ref: 'project1',
|
||||
user_id: 'member1',
|
||||
},
|
||||
];
|
||||
|
||||
describe('DatabaseHandler', () => {
|
||||
let database: DatabaseHandler;
|
||||
beforeAll(async () => {
|
||||
database = await DatabaseHandler.createTestDatabase();
|
||||
await database.addMember(members[0].user_id, members[0].entity_ref);
|
||||
});
|
||||
|
||||
it("can get members that's in the database", async () => {
|
||||
const cov: any[] = await database.getMembers('project1');
|
||||
expect(cov[0].entity_ref).toEqual(members[0].entity_ref);
|
||||
expect(cov[0].user_id).toEqual(members[0].user_id);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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 { resolvePackagePath } from '@backstage/backend-common';
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const migrationsDir = resolvePackagePath(
|
||||
'@backstage/plugin-bazaar-backend',
|
||||
'migrations',
|
||||
);
|
||||
|
||||
type Options = {
|
||||
database: Knex;
|
||||
};
|
||||
|
||||
export class DatabaseHandler {
|
||||
static async create(options: Options): Promise<DatabaseHandler> {
|
||||
const { database } = options;
|
||||
|
||||
await database.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
|
||||
return new DatabaseHandler(options);
|
||||
}
|
||||
|
||||
private readonly database: Knex;
|
||||
|
||||
private constructor(options: Options) {
|
||||
this.database = options.database;
|
||||
}
|
||||
|
||||
public static async createTestDatabase(): Promise<DatabaseHandler> {
|
||||
const knex = await this.createTestDatabaseConnection();
|
||||
return await this.create({ database: knex });
|
||||
}
|
||||
|
||||
public static async createTestDatabaseConnection(): Promise<Knex> {
|
||||
const config: Knex.Config<any> = {
|
||||
client: 'pg',
|
||||
connection: {
|
||||
host: 'localhost',
|
||||
port: 5432,
|
||||
user: 'postgres',
|
||||
password: 'postgres',
|
||||
},
|
||||
/*
|
||||
client: 'sqlite3',
|
||||
connection: ':memory:',
|
||||
useNullAsDefault: true,
|
||||
*/
|
||||
};
|
||||
|
||||
let knex = knexFactory(config);
|
||||
if (typeof config.connection !== 'string') {
|
||||
const tempDbName = `d${uuidv4().replace(/-/g, '')}`;
|
||||
await knex.raw(`CREATE DATABASE ${tempDbName};`);
|
||||
knex = knexFactory({
|
||||
...config,
|
||||
connection: {
|
||||
...config.connection,
|
||||
database: tempDbName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
knex.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
|
||||
resource.run('PRAGMA foreign_keys = ON', () => {});
|
||||
});
|
||||
|
||||
return knex;
|
||||
}
|
||||
|
||||
async getMembers(entityRef: any) {
|
||||
return await this.database
|
||||
.select('*')
|
||||
.from('public.members')
|
||||
.where({ entity_ref: entityRef });
|
||||
}
|
||||
|
||||
async addMember(userId: any, entityRef: any) {
|
||||
await this.database
|
||||
.insert({
|
||||
entity_ref: entityRef,
|
||||
user_id: userId,
|
||||
})
|
||||
.into('public.members');
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
errorHandler,
|
||||
PluginDatabaseManager,
|
||||
resolvePackagePath,
|
||||
} from '@backstage/backend-common';
|
||||
import { errorHandler, PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
import { Logger } from 'winston';
|
||||
import { Config } from '@backstage/config';
|
||||
import { DatabaseHandler } from './DatabaseHandler';
|
||||
|
||||
export interface RouterOptions {
|
||||
logger: Logger;
|
||||
@@ -33,30 +30,20 @@ export interface RouterOptions {
|
||||
export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const { logger } = options;
|
||||
const db = await options.database.getClient();
|
||||
const { logger, database } = options;
|
||||
const db = await database.getClient();
|
||||
|
||||
const dbHandler = await DatabaseHandler.create({ database: db });
|
||||
|
||||
logger.info('Initializing Bazaar backend');
|
||||
|
||||
const migrationsDir = resolvePackagePath(
|
||||
'@backstage/plugin-bazaar-backend',
|
||||
'migrations',
|
||||
);
|
||||
|
||||
await db.migrate.latest({
|
||||
directory: migrationsDir,
|
||||
});
|
||||
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
router.get('/members', async (request, response) => {
|
||||
const entityRef = request.headers.entity_ref;
|
||||
|
||||
const data = await db
|
||||
.select('*')
|
||||
.from('public.members')
|
||||
.where({ entity_ref: entityRef });
|
||||
const data = await dbHandler.getMembers(entityRef);
|
||||
|
||||
if (data?.length) {
|
||||
response.json({ status: 'ok', data: data });
|
||||
@@ -69,12 +56,8 @@ export async function createRouter(
|
||||
const userId = request.headers.user_id;
|
||||
const entityRef = request.headers.entity_ref;
|
||||
|
||||
await db
|
||||
.insert({
|
||||
entity_ref: entityRef,
|
||||
user_id: userId,
|
||||
})
|
||||
.into('public.members');
|
||||
await dbHandler.addMember(userId, entityRef);
|
||||
|
||||
response.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export const bazaarPlugin: BackstagePlugin<
|
||||
// Warning: (ae-missing-release-tag) "EntityBazaarInfoCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const EntityBazaarInfoCard: () => JSX.Element;
|
||||
export const EntityBazaarInfoCard: () => JSX.Element | null;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@backstage/core-components": "^0.5.0",
|
||||
"@backstage/core-plugin-api": "^0.1.3",
|
||||
"@backstage/plugin-catalog": "^0.6.6",
|
||||
"@backstage/plugin-catalog-react": "^0.4.0",
|
||||
"@backstage/plugin-catalog-react": "^0.5.0",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
|
||||
@@ -91,12 +91,14 @@ export class BazaarClient implements BazaarApi {
|
||||
async getMetadata(entity: Entity): Promise<any> {
|
||||
const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
|
||||
|
||||
return await fetch(`${baseUrl}/metadata`, {
|
||||
const response = await fetch(`${baseUrl}/metadata`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
entity_ref: stringifyEntityRef(entity),
|
||||
},
|
||||
});
|
||||
|
||||
return response.ok ? response : null;
|
||||
}
|
||||
|
||||
async getMembers(entity: Entity): Promise<any> {
|
||||
|
||||
@@ -24,7 +24,7 @@ import { bazaarApiRef } from '../../api';
|
||||
type Props = {
|
||||
entity: Entity;
|
||||
bazaarProject: BazaarProject;
|
||||
fetchBazaarProject: () => Promise<BazaarProject>;
|
||||
fetchBazaarProject: () => Promise<BazaarProject | null>;
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
isAddForm: boolean;
|
||||
|
||||
@@ -47,14 +47,9 @@ 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,
|
||||
useRouteRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { useApi, identityApiRef } from '@backstage/core-plugin-api';
|
||||
import { Member, BazaarProject } from '../../types';
|
||||
import { bazaarApiRef } from '../../api';
|
||||
import { rootRouteRef } from '../../routes';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
|
||||
@@ -92,7 +87,6 @@ export const EntityBazaarInfoCard = () => {
|
||||
const [openDelete, setOpenDelete] = useState(false);
|
||||
const [isMember, setIsMember] = useState(false);
|
||||
const [isBazaar, setIsBazaar] = useState(false);
|
||||
const routeRef = useRouteRef(rootRouteRef);
|
||||
const [members, fetchMembers] = useAsyncFn(async () => {
|
||||
const response = await bazaarApi.getMembers(entity);
|
||||
const dbMembers = response.data.map((obj: any) => {
|
||||
@@ -112,17 +106,21 @@ export const EntityBazaarInfoCard = () => {
|
||||
|
||||
const [bazaarProject, fetchBazaarProject] = useAsyncFn(async () => {
|
||||
const response = await bazaarApi.getMetadata(entity);
|
||||
const metadata = await response.json().then((resp: any) => resp.data[0]);
|
||||
|
||||
return {
|
||||
entityRef: metadata.entity_ref,
|
||||
name: metadata.name,
|
||||
community: metadata.community,
|
||||
announcement: metadata.announcement,
|
||||
status: metadata.status,
|
||||
updatedAt: metadata.updated_at,
|
||||
membersCount: metadata.members_count,
|
||||
} as BazaarProject;
|
||||
if (response) {
|
||||
const metadata = await response.json().then((resp: any) => resp.data[0]);
|
||||
|
||||
return {
|
||||
entityRef: metadata.entity_ref,
|
||||
name: metadata.name,
|
||||
community: metadata.community,
|
||||
announcement: metadata.announcement,
|
||||
status: metadata.status,
|
||||
updatedAt: metadata.updated_at,
|
||||
membersCount: metadata.members_count,
|
||||
} as BazaarProject;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -135,7 +133,7 @@ export const EntityBazaarInfoCard = () => {
|
||||
members?.value
|
||||
?.map((member: Member) => member.userId)
|
||||
.indexOf(identity.getUserId()) >= 0;
|
||||
const isBazaarProject = bazaarProject !== undefined;
|
||||
const isBazaarProject = bazaarProject !== null;
|
||||
|
||||
setIsMember(isBazaarMember);
|
||||
setIsBazaar(isBazaarProject);
|
||||
@@ -185,32 +183,14 @@ export const EntityBazaarInfoCard = () => {
|
||||
},
|
||||
];
|
||||
|
||||
if (bazaarProject.loading || members.loading) {
|
||||
if (!isBazaar) {
|
||||
return null;
|
||||
} else if (bazaarProject.loading || members.loading) {
|
||||
return <Progress />;
|
||||
} else if (bazaarProject.error) {
|
||||
return <Alert severity="error">{bazaarProject?.error?.message}</Alert>;
|
||||
} else if (members.error) {
|
||||
return <Alert severity="error">{members?.error?.message}</Alert>;
|
||||
} 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={`/${routeRef()}`}>
|
||||
Bazaar
|
||||
</Link>{' '}
|
||||
to add the project or to{' '}
|
||||
<Link className={classes.link} to={`/${routeRef()}/about`}>
|
||||
read more
|
||||
</Link>
|
||||
.
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Card>
|
||||
|
||||
Reference in New Issue
Block a user