Skip to content

Commit

Permalink
Device/Deployment Frontend changes (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
NickPhura committed Oct 24, 2024
1 parent e18c2ba commit 0557118
Show file tree
Hide file tree
Showing 45 changed files with 1,327 additions and 538 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@ import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles';
import { getDBConnection } from '../../../../../../database/db';
import {
paginationRequestQueryParamSchema,
paginationResponseSchema
} from '../../../../../../openapi/schemas/pagination';
import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization';
import { TelemetryDeploymentService } from '../../../../../../services/telemetry-services/telemetry-deployment-service';
import { getLogger } from '../../../../../../utils/logger';
import {
ensureCompletePaginationOptions,
makePaginationOptionsFromRequest,
makePaginationResponse
} from '../../../../../../utils/pagination';

const defaultLog = getLogger('paths/project/{projectId}/survey/{surveyId}/deployments2/index');

Expand Down Expand Up @@ -57,7 +66,8 @@ GET.apiDoc = {
minimum: 1
},
required: true
}
},
...paginationRequestQueryParamSchema
],
responses: {
200: {
Expand Down Expand Up @@ -188,7 +198,12 @@ GET.apiDoc = {
}
}
}
}
},
count: {
type: 'number',
description: 'Count of telemetry deployments in the respective survey.'
},
pagination: { ...paginationResponseSchema }
}
}
}
Expand Down Expand Up @@ -228,15 +243,27 @@ export function getDeploymentsInSurvey(): RequestHandler {
const connection = getDBConnection(req.keycloak_token);

try {
const paginationOptions = makePaginationOptionsFromRequest(req);

await connection.open();

const telemetryDeploymentService = new TelemetryDeploymentService(connection);

const deployments = await telemetryDeploymentService.getDeploymentsForSurveyId(surveyId);
const [deployments, deploymentsCount] = await Promise.all([
telemetryDeploymentService.getDeploymentsForSurveyId(
surveyId,
ensureCompletePaginationOptions(paginationOptions)
),
telemetryDeploymentService.getDeploymentsCount(surveyId)
]);

await connection.commit();

return res.status(200).json({ deployments: deployments });
return res.status(200).json({
deployments: deployments,
count: deploymentsCount,
pagination: makePaginationResponse(deploymentsCount, paginationOptions)
});
} catch (error) {
defaultLog.error({ label: 'getDeploymentsInSurvey', message: 'error', error });
await connection.rollback();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { expect } from 'chai';
import sinon from 'sinon';
import { getDevices } from '.';
import { getDevicesInSurvey } from '.';
import * as db from '../../../../../../database/db';
import { TelemetryDeviceService } from '../../../../../../services/telemetry-services/telemetry-device-service';
import { getMockDBConnection, getRequestHandlerMocks } from '../../../../../../__mocks__/db';

describe('getDevices', () => {
describe('getDevicesInSurvey', () => {
afterEach(() => {
sinon.restore();
});
Expand Down Expand Up @@ -36,7 +36,7 @@ describe('getDevices', () => {
surveyId: '2'
};

const requestHandler = getDevices();
const requestHandler = getDevicesInSurvey();

await requestHandler(mockReq, mockRes, mockNext);

Expand Down Expand Up @@ -72,7 +72,7 @@ describe('getDevices', () => {
surveyId: '2'
};

const requestHandler = getDevices();
const requestHandler = getDevicesInSurvey();

try {
await requestHandler(mockReq, mockRes, mockNext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ export const GET: Operation = [
]
};
}),
getDevices()
getDevicesInSurvey()
];

GET.apiDoc = {
Expand Down Expand Up @@ -283,16 +283,17 @@ GET.apiDoc = {
*
* @returns {RequestHandler}
*/
export function getDevices(): RequestHandler {
export function getDevicesInSurvey(): RequestHandler {
return async (req, res) => {
const surveyId = Number(req.params.surveyId);

const connection = getDBConnection(req.keycloak_token);

try {
await connection.open();

const paginationOptions = makePaginationOptionsFromRequest(req);

await connection.open();

const telemetryDeviceService = new TelemetryDeviceService(connection);

const [devices, devicesCount] = await Promise.all([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ describe('TelemetryDeploymentRepository', () => {
rows: [mockDeploymentRecord]
} as any as Promise<QueryResult<any>>;

const mockDbConnection = getMockDBConnection({ sql: sinon.stub().resolves(mockResponse) });
const mockDbConnection = getMockDBConnection({ knex: sinon.stub().resolves(mockResponse) });

const telemetryDeploymentRepository = new TelemetryDeploymentRepository(mockDbConnection);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import SQL from 'sql-template-strings';
import { z } from 'zod';
import { DeploymentRecord } from '../../database-models/deployment';
import { getKnex } from '../../database/db';
import { ApiExecuteSQLError } from '../../errors/api-error';
import { ApiPaginationOptions } from '../../zod-schema/pagination';
import { BaseRepository } from '../base-repository';
import {
CreateDeployment,
Expand Down Expand Up @@ -117,45 +119,54 @@ export class TelemetryDeploymentRepository extends BaseRepository {
/**
* Get deployments for a survey ID. Includes additional device and critter data.
*
* @param {number} surveyId The survey ID
* @param {number} surveyId
* @param {ApiPaginationOptions} [pagination]
* @return {*} {Promise<ExtendedDeploymentRecord[]>}
* @memberof TelemetryDeploymentRepository
*/
async getDeploymentsForSurveyId(surveyId: number): Promise<ExtendedDeploymentRecord[]> {
const sqlStatement = SQL`
SELECT
-- deployment data
deployment2.deployment2_id,
deployment2.survey_id,
deployment2.critter_id,
deployment2.device_id,
deployment2.frequency,
deployment2.frequency_unit_id,
deployment2.attachment_start_date,
deployment2.attachment_start_time,
deployment2.attachment_end_date,
deployment2.attachment_end_time,
deployment2.critterbase_start_capture_id,
deployment2.critterbase_end_capture_id,
deployment2.critterbase_end_mortality_id,
-- device data
device.device_make_id,
device.model,
-- critter data
critter.critterbase_critter_id
FROM
deployment2
INNER JOIN
device
ON deployment2.device_id = device.device_id
INNER JOIN
critter
ON deployment2.critter_id = critter.critter_id
WHERE
deployment2.survey_id = ${surveyId};
`;
async getDeploymentsForSurveyId(
surveyId: number,
pagination?: ApiPaginationOptions
): Promise<ExtendedDeploymentRecord[]> {
const knex = getKnex();

const response = await this.connection.sql(sqlStatement, ExtendedDeploymentRecord);
const queryBuilder = knex
.queryBuilder()
.select(
// deployment data
'deployment2.deployment2_id',
'deployment2.survey_id',
'deployment2.critter_id',
'deployment2.device_id',
'deployment2.frequency',
'deployment2.frequency_unit_id',
'deployment2.attachment_start_date',
'deployment2.attachment_start_time',
'deployment2.attachment_end_date',
'deployment2.attachment_end_time',
'deployment2.critterbase_start_capture_id',
'deployment2.critterbase_end_capture_id',
'deployment2.critterbase_end_mortality_id',
// device data
'device.device_make_id',
'device.model',
// critter data
'critter.critterbase_critter_id'
)
.from('deployment2')
.innerJoin('device', 'deployment2.device_id', 'device.device_id')
.innerJoin('critter', 'deployment2.critter_id', 'critter.critter_id')
.where('deployment2.survey_id', surveyId);

if (pagination) {
queryBuilder.limit(pagination.limit).offset((pagination.page - 1) * pagination.limit);

if (pagination.sort && pagination.order) {
queryBuilder.orderBy(pagination.sort, pagination.order);
}
}

const response = await this.connection.knex(queryBuilder, ExtendedDeploymentRecord);

return response.rows;
}
Expand Down Expand Up @@ -193,6 +204,25 @@ export class TelemetryDeploymentRepository extends BaseRepository {
return response.rows;
}

/**
* Get the total count of all deployments for a survey.
*
* @param {number} surveyId
* @return {*} {Promise<number>}
* @memberof TelemetryDeploymentRepository
*/
async getDeploymentsCount(surveyId: number): Promise<number> {
const knex = getKnex();

const queryBuilder = knex
.select(knex.raw('count(*)::integer as count'))
.from('deployments2')
.where('survey_id', surveyId);

const response = await this.connection.knex(queryBuilder, z.object({ count: z.number() }));

return response.rows[0].count;
}
/**
* Update a deployment.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ExtendedDeploymentRecord,
UpdateDeployment
} from '../../repositories/telemetry-repositories/telemetry-deployment-repository.interface';
import { ApiPaginationOptions } from '../../zod-schema/pagination';
import { DBService } from '../db-service';

/**
Expand Down Expand Up @@ -69,12 +70,16 @@ export class TelemetryDeploymentService extends DBService {
/**
* Get deployments for a Survey.
*
* @param {number} surveyId The survey ID
* @param {number} surveyId
* @param {ApiPaginationOptions} [pagination]
* @return {*} {Promise<ExtendedDeploymentRecord[]>}
* @memberof TelemetryDeploymentService
*/
async getDeploymentsForSurveyId(surveyId: number): Promise<ExtendedDeploymentRecord[]> {
return this.telemetryDeploymentRepository.getDeploymentsForSurveyId(surveyId);
async getDeploymentsForSurveyId(
surveyId: number,
pagination?: ApiPaginationOptions
): Promise<ExtendedDeploymentRecord[]> {
return this.telemetryDeploymentRepository.getDeploymentsForSurveyId(surveyId, pagination);
}

/**
Expand All @@ -89,6 +94,17 @@ export class TelemetryDeploymentService extends DBService {
return this.telemetryDeploymentRepository.getDeploymentsForCritterId(surveyId, critterId);
}

/**
* Get the total count of all deployments for a survey.
*
* @param {number} surveyId
* @return {*} {Promise<number>}
* @memberof telemetryDeploymentRepository
*/
async getDeploymentsCount(surveyId: number): Promise<number> {
return this.telemetryDeploymentRepository.getDeploymentsCount(surveyId);
}

/**
* Update a deployment.
*
Expand Down
13 changes: 12 additions & 1 deletion app/src/constants/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,17 @@ export const TelemetryDeviceKeyFileI18N = {
'An error has occurred while attempting to download the device key file, please try again. If the error persists, please contact your system administrator.'
};

export const TelemetryDeviceI18N = {
cancelTitle: 'Discard changes and exit?',
cancelText: 'Any changes you have made will not be saved. Do you want to proceed?',
createErrorTitle: 'Error Creating Device',
createErrorText:
'An error has occurred while attempting to create your device. Please try again. If the error persists, please contact your system administrator.',
editErrorTitle: 'Error Editing Device',
editErrorText:
'An error has occurred while attempting to edit your device. Please try again. If the error persists, please contact your system administrator.'
};

export const CreateAnimalDeploymentI18N = {
cancelTitle: 'Discard changes and exit?',
cancelText: 'Any changes you have made will not be saved. Do you want to proceed?',
Expand All @@ -490,7 +501,7 @@ export const EditAnimalDeploymentI18N = {
cancelText: 'Any changes you have made will not be saved. Do you want to proceed?',
createErrorTitle: 'Error Creating Deployment',
createErrorText:
'An error has occurred while attempting to create your deployment. Please try again. If the error persists, please contact your system administrator.'
'An error has occurred while attempting to edit your deployment. Please try again. If the error persists, please contact your system administrator.'
};

export const SurveyExportI18N = {
Expand Down
18 changes: 15 additions & 3 deletions app/src/features/surveys/SurveyRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ const SurveyRouter: React.FC = () => {
{/* Animals Routes */}
<RouteWithTitle path="/admin/projects/:id/surveys/:survey_id/animals" title={getTitle('Manage Animals')}>
<ProjectRoleRouteGuard
validProjectPermissions={[PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR]}
validProjectPermissions={[
PROJECT_PERMISSION.COORDINATOR,
PROJECT_PERMISSION.COLLABORATOR,
PROJECT_PERMISSION.OBSERVER
]}
validSystemRoles={[SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.DATA_ADMINISTRATOR]}>
<DialogContextProvider>
<AnimalPageContextProvider>
Expand All @@ -59,7 +63,11 @@ const SurveyRouter: React.FC = () => {
{/* Telemetry Routes */}
<RouteWithTitle path="/admin/projects/:id/surveys/:survey_id/telemetry" title={getTitle('Manage Telemetry')}>
<ProjectRoleRouteGuard
validProjectPermissions={[PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR]}
validProjectPermissions={[
PROJECT_PERMISSION.COORDINATOR,
PROJECT_PERMISSION.COLLABORATOR,
PROJECT_PERMISSION.OBSERVER
]}
validSystemRoles={[SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.DATA_ADMINISTRATOR]}>
<AnimalPageContextProvider>
<TelemetryDataContextProvider>
Expand All @@ -75,7 +83,11 @@ const SurveyRouter: React.FC = () => {
path="/admin/projects/:id/surveys/:survey_id/observations"
title={getTitle('Manage Observations')}>
<ProjectRoleRouteGuard
validProjectPermissions={[PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR]}
validProjectPermissions={[
PROJECT_PERMISSION.COORDINATOR,
PROJECT_PERMISSION.COLLABORATOR,
PROJECT_PERMISSION.OBSERVER
]}
validSystemRoles={[SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.DATA_ADMINISTRATOR]}>
<SurveyObservationPage />
</ProjectRoleRouteGuard>
Expand Down
Loading

0 comments on commit 0557118

Please sign in to comment.