Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BHC-2098: Refactor Functionality Used to Display Points on Map #907

Merged
merged 17 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 100 additions & 0 deletions api/src/paths/dwc/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { getDBConnection } from '../../database/db';
import { OccurrenceService } from '../../services/occurrence-service';
import { getLogger } from '../../utils/logger';

const defaultLog = getLogger('paths/dwc/metadata');

export const GET: Operation = [getSpatialMetadataBySubmissionSpatialComponentIds()];

GET.apiDoc = {
description: 'Retrieves spatial component metadata based on submission spatial component id',
tags: ['spatial'],
security: [
{
Bearer: []
}
],
parameters: [
{
description: 'spatial component submission ids',
in: 'query',
name: 'submissionSpatialComponentIds',
schema: {
type: 'array',
items: {
type: 'number',
minimum: 1
}
},
required: true
}
],
responses: {
200: {
description: 'Spatial metadata response object.',
content: {
'application/json': {
schema: {
type: 'array',
items: {
type: 'object'
}
}
}
}
},
400: {
$ref: '#/components/responses/400'
},
401: {
$ref: '#/components/responses/401'
},
403: {
$ref: '#/components/responses/401'
},
409: {
$ref: '#/components/responses/409'
},
500: {
$ref: '#/components/responses/500'
},
default: {
$ref: '#/components/responses/default'
}
}
};

/**
* Retrieves dataset metadata from Elastic Search.
*
* @returns {RequestHandler}
*/
export function getSpatialMetadataBySubmissionSpatialComponentIds(): RequestHandler {
return async (req, res) => {
const submissionSpatialComponentIds = ((req.query.submissionSpatialComponentIds || []) as string[]).map(Number);

const connection = getDBConnection(req['keycloak_token']);

try {
await connection.open();

const occurrenceService = new OccurrenceService(connection);

const response = await occurrenceService.findSpatialMetadataBySubmissionSpatialComponentIds(
submissionSpatialComponentIds
);

await connection.commit();

res.status(200).json(response);
} catch (error) {
defaultLog.error({ label: 'getSpatialMetadataBySubmissionSpatialComponentIds', message: 'error', error });
await connection.rollback();
throw error;
} finally {
connection.release();
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@ import chai, { expect } from 'chai';
import { describe } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import SQL from 'sql-template-strings';
import * as db from '../../../../../../../../database/db';
import { HTTPError } from '../../../../../../../../errors/http-error';
import survey_queries from '../../../../../../../../queries/survey';
import { OccurrenceService } from '../../../../../../../../services/occurrence-service';
import { getMockDBConnection } from '../../../../../../../../__mocks__/db';
import * as delete_submission from './delete';

chai.use(sinonChai);

describe('deleteOccurrenceSubmission', () => {
const dbConnectionObj = getMockDBConnection();

const sampleReq = {
keycloak_token: {},
params: {
Expand All @@ -39,119 +35,31 @@ describe('deleteOccurrenceSubmission', () => {
sinon.restore();
});

it('should throw a 400 error when no projectId is provided', async () => {
it('should return false if no rows were deleted', async () => {
const dbConnectionObj = getMockDBConnection();
sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

try {
const result = delete_submission.deleteOccurrenceSubmission();
await result(
{ ...sampleReq, params: { ...sampleReq.params, projectId: null } },
(null as unknown) as any,
(null as unknown) as any
);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required path param `projectId`');
}
});

it('should throw a 400 error when no surveyId is provided', async () => {
sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

try {
const result = delete_submission.deleteOccurrenceSubmission();
await result(
{ ...sampleReq, params: { ...sampleReq.params, surveyId: null } },
(null as unknown) as any,
(null as unknown) as any
);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required path param `surveyId`');
}
});

it('should throw a 400 error when no submissionId is provided', async () => {
sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

try {
const result = delete_submission.deleteOccurrenceSubmission();
await result(
{ ...sampleReq, params: { ...sampleReq.params, submissionId: null } },
(null as unknown) as any,
(null as unknown) as any
);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required path param `submissionId`');
}
});

it('should throw a 400 error when no sql statement returned for deleteOccurrenceSubmissionSQL', async () => {
sinon.stub(db, 'getDBConnection').returns({
...dbConnectionObj,
systemUserId: () => {
return 20;
}
});

sinon.stub(survey_queries, 'deleteOccurrenceSubmissionSQL').returns(null);

try {
const result = delete_submission.deleteOccurrenceSubmission();

await result(sampleReq, (null as unknown) as any, (null as unknown) as any);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Failed to build SQL delete statement');
}
});

it('should return null when no rowCount', async () => {
const mockQuery = sinon.stub();

mockQuery.resolves({ rowCount: null });

sinon.stub(db, 'getDBConnection').returns({
...dbConnectionObj,
systemUserId: () => {
return 20;
},
query: mockQuery
});

sinon.stub(survey_queries, 'deleteOccurrenceSubmissionSQL').returns(SQL`something`);
sinon.stub(OccurrenceService.prototype, 'deleteOccurrenceSubmission').resolves([]);

const result = delete_submission.deleteOccurrenceSubmission();

await result(sampleReq, sampleRes as any, (null as unknown) as any);

expect(actualResult).to.equal(null);
expect(actualResult).to.equal(false);
});

it('should return rowCount on success', async () => {
const mockQuery = sinon.stub();

mockQuery.resolves({ rowCount: 1 });

sinon.stub(db, 'getDBConnection').returns({
...dbConnectionObj,
systemUserId: () => {
return 20;
},
query: mockQuery
});
it('should return true if occurrence submission was deleted', async () => {
const dbConnectionObj = getMockDBConnection();
sinon.stub(db, 'getDBConnection').returns(dbConnectionObj);

sinon.stub(survey_queries, 'deleteOccurrenceSubmissionSQL').returns(SQL`something`);
sinon
.stub(OccurrenceService.prototype, 'deleteOccurrenceSubmission')
.resolves([{ submission_spatial_component_id: 1 }]);

const result = delete_submission.deleteOccurrenceSubmission();

await result(sampleReq, sampleRes as any, (null as unknown) as any);

expect(actualResult).to.equal(1);
expect(actualResult).to.equal(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { PROJECT_ROLE } from '../../../../../../../../constants/roles';
import { getDBConnection } from '../../../../../../../../database/db';
import { HTTP400 } from '../../../../../../../../errors/http-error';
import { queries } from '../../../../../../../../queries/queries';
import { authorizeRequestHandler } from '../../../../../../../../request-handlers/security/authorization';
import { OccurrenceService } from '../../../../../../../../services/occurrence-service';
import { getLogger } from '../../../../../../../../utils/logger';

const defaultLog = getLogger('/api/project/{projectId}/survey/{surveyId}/observation/submission/{submissionId}/delete');
Expand Down Expand Up @@ -60,12 +59,12 @@ DELETE.apiDoc = {
],
responses: {
200: {
description: 'Observation submission csv details response.',
description: 'Boolean true value representing successful deletion.',
content: {
'application/json': {
schema: {
title: 'Row count of soft deleted records',
type: 'number'
title: 'Occurrence delete response',
type: 'boolean'
}
}
}
Expand Down Expand Up @@ -96,41 +95,18 @@ export function deleteOccurrenceSubmission(): RequestHandler {
req_params: req.params
});

if (!req.params.projectId) {
throw new HTTP400('Missing required path param `projectId`');
}

if (!req.params.surveyId) {
throw new HTTP400('Missing required path param `surveyId`');
}

if (!req.params.submissionId) {
throw new HTTP400('Missing required path param `submissionId`');
}

const connection = getDBConnection(req['keycloak_token']);

try {
const deleteSubmissionSQLStatement = queries.survey.deleteOccurrenceSubmissionSQL(
Number(req.params.submissionId)
);

if (!deleteSubmissionSQLStatement) {
throw new HTTP400('Failed to build SQL delete statement');
}

await connection.open();

const deleteResult = await connection.query(
deleteSubmissionSQLStatement.text,
deleteSubmissionSQLStatement.values
);
const occurrenceService = new OccurrenceService(connection);

await connection.commit();
const response = await occurrenceService.deleteOccurrenceSubmission(Number(req.params.submissionId));

const deleteResponse = (deleteResult && deleteResult.rowCount) || null;
await connection.commit();

return res.status(200).json(deleteResponse);
return res.status(200).json(!!response.length);
} catch (error) {
defaultLog.error({ label: 'deleteOccurrenceSubmission', message: 'error', error });
await connection.rollback();
Expand Down
Loading