From fbff8852f5ca216d8862e524ee52c12667b71ad8 Mon Sep 17 00:00:00 2001 From: Bhaswati Dey Date: Thu, 24 Sep 2026 12:53:47 +0100 Subject: [PATCH 1/9] initial changes for hpl excel sheet download feature --- .../src/datasources/mockups/FapDataSource.ts | 10 +- .../src/datasources/postgres/FapDataSource.ts | 33 ++-- .../src/datasources/postgres/records.ts | 5 +- apps/backend/src/factory/service.ts | 1 + apps/backend/src/factory/xlsx/FapDataRow.ts | 31 ++++ apps/backend/src/factory/xlsx/fap.ts | 78 ++++++++- apps/backend/src/middlewares/factory/xlsx.ts | 78 ++++++++- apps/backend/src/models/Fap.ts | 5 +- apps/e2e/cypress/e2e/FAPs.cy.ts | 148 +++++++++++++++++- .../fixtures/exampleFapReviewsExport.json | 12 ++ .../FapReviewersAndAssignmentsTable.tsx | 48 ++++++ .../src/context/DownloadContextProvider.tsx | 21 +++ .../hooks/fap/useDownloadXLSXFapReviews.ts | 30 ++++ 13 files changed, 477 insertions(+), 23 deletions(-) create mode 100644 apps/e2e/cypress/fixtures/exampleFapReviewsExport.json create mode 100644 apps/frontend/src/hooks/fap/useDownloadXLSXFapReviews.ts diff --git a/apps/backend/src/datasources/mockups/FapDataSource.ts b/apps/backend/src/datasources/mockups/FapDataSource.ts index 64d84c6b0b..fe4a17380b 100644 --- a/apps/backend/src/datasources/mockups/FapDataSource.ts +++ b/apps/backend/src/datasources/mockups/FapDataSource.ts @@ -118,7 +118,10 @@ export const dummyFapAssignment = new FapAssignment( false, null, false, - null + null, + 'dummy comment', + 'dummy grade', + 1 ); export const anotherDummyFapAssignment = new FapAssignment( @@ -129,7 +132,10 @@ export const anotherDummyFapAssignment = new FapAssignment( false, null, false, - null + null, + 'dummy comment', + 'dummy grade', + 1 ); export const dummyFapReview = new Review( diff --git a/apps/backend/src/datasources/postgres/FapDataSource.ts b/apps/backend/src/datasources/postgres/FapDataSource.ts index 69c198839c..51af5fc38e 100644 --- a/apps/backend/src/datasources/postgres/FapDataSource.ts +++ b/apps/backend/src/datasources/postgres/FapDataSource.ts @@ -287,13 +287,13 @@ export default class PostgresFapDataSource implements FapDataSource { ): Promise { const fapAssignments: ReviewRecord[] = await database .from('fap_reviews') + .where('fap_id', fapId) + .andWhere('proposal_pk', proposalPk) .modify((query) => { if (reviewerId !== null) { - query.where('user_id', reviewerId); + query.andWhere('user_id', reviewerId); } - }) - .where('fap_id', fapId) - .andWhere('proposal_pk', proposalPk); + }); return fapAssignments.map((fapAssignment) => createFapAssignmentObject(fapAssignment) @@ -1322,20 +1322,21 @@ export default class PostgresFapDataSource implements FapDataSource { fapId: number, instrumentId?: number | null ): Promise { - if (instrumentId) { - return await database - .select('*') - .from('review_data') - .where('fap_id', fapId) - .andWhere('call_id', callId) - .andWhere('instrument_id', instrumentId); - } - - return await database + logger.logInfo(`Chilllllllllllllllll callid ${callId}`, {}); + const query = database .select('*') .from('review_data') - .where('fap_id', fapId) - .andWhere('call_id', callId); + .where('fap_id', fapId); + + if (callId != 0) { + query.andWhere('call_id', callId); + } + + if (instrumentId != null) { + query.andWhere('instrument_id', instrumentId); + } + + return await query; } async submitFapMeetings( diff --git a/apps/backend/src/datasources/postgres/records.ts b/apps/backend/src/datasources/postgres/records.ts index b14d371ac4..cfbe1ab0d4 100644 --- a/apps/backend/src/datasources/postgres/records.ts +++ b/apps/backend/src/datasources/postgres/records.ts @@ -1190,7 +1190,10 @@ export const createFapAssignmentObject = (fapAssignment: ReviewRecord) => { fapAssignment.reassigned, fapAssignment.date_reassigned, fapAssignment.email_sent, - fapAssignment.rank + fapAssignment.rank, + fapAssignment.comment, + fapAssignment.grade, + fapAssignment.status ); }; diff --git a/apps/backend/src/factory/service.ts b/apps/backend/src/factory/service.ts index 24a048190b..5c45f22e80 100644 --- a/apps/backend/src/factory/service.ts +++ b/apps/backend/src/factory/service.ts @@ -19,6 +19,7 @@ export enum XLSXType { PROPOSAL = 'proposal', FAP = 'fap', CALL_FAP = 'call_fap', + FAP_REVIEWS = 'fap_reviews', TECHNIQUE = 'technique', MANAGEMENT_DECISION = 'management_decision', } diff --git a/apps/backend/src/factory/xlsx/FapDataRow.ts b/apps/backend/src/factory/xlsx/FapDataRow.ts index c3e90ab32d..1c1112f085 100644 --- a/apps/backend/src/factory/xlsx/FapDataRow.ts +++ b/apps/backend/src/factory/xlsx/FapDataRow.ts @@ -23,6 +23,37 @@ export type FapDataRowInput = { export type FapDataRow = (input: FapDataRowInput) => RowObj | Promise; +export type ReviewRowInput = { + proposalId: number | null; + title: string | null; + instrumentName: string | null; + dateAssigned: Date | string | null; + rank: number | null; + grade: string | null; + comment: string | null; + status: string | null; +}; + +export const buildReviewRow = ({ + proposalId, + title, + instrumentName, + dateAssigned, + rank, + grade, + comment, + status, +}: ReviewRowInput): Array => [ + proposalId ?? '-', + title ?? '-', + instrumentName ?? '-', + dateAssigned ? String(dateAssigned) : '-', + rank ?? '-', + grade ?? '-', + comment ?? '-', + status ?? '-', +]; + export function getDataRow({ proposalPk, piName, diff --git a/apps/backend/src/factory/xlsx/fap.ts b/apps/backend/src/factory/xlsx/fap.ts index 358e1846e1..602fff5db9 100644 --- a/apps/backend/src/factory/xlsx/fap.ts +++ b/apps/backend/src/factory/xlsx/fap.ts @@ -1,8 +1,9 @@ import { groupBy } from 'lodash'; +import { DateTime } from 'luxon'; import { container } from 'tsyringe'; import { collectCallFapXLSXData } from './callFaps'; -import { FapDataRow, FapDataRowInput } from './FapDataRow'; +import { buildReviewRow, FapDataRow, FapDataRowInput } from './FapDataRow'; import baseContext from '../../buildContext'; import { Tokens } from '../../config/Tokens'; import { FapDataSource } from '../../datasources/FapDataSource'; @@ -174,3 +175,78 @@ export const collectFapXLSXData = async ( data: transformedData, }; }; +export const collectFapReviewXLSXData = async ( + fapId: number, + callId: number, + reviewerProposals: Record, + user: UserWithRole +): Promise<{ data: FapXLSXData; filename: string }> => { + const reviewData = await fapDataSource.getFapReviewData(callId, fapId); + + const data: FapXLSXData = []; + for (const [reviewerIdString, proposalPks] of Object.entries( + reviewerProposals + )) { + const reviewerId = Number(reviewerIdString); + + const reviewer = await baseContext.queries.user.getBasic(user, reviewerId); + + const reviewerName = + `${reviewer?.firstname ?? ''} ${reviewer?.lastname ?? ''}`.trim(); + const rows: Array> = []; + for (const proposalPk of proposalPks) { + const review = reviewData.find((item) => item.proposal_pk === proposalPk); + + if (!review) { + continue; + } + + const assignments = await fapDataSource.getFapProposalAssignments( + fapId, + proposalPk, + reviewerId + ); + + const assignment = assignments.find( + (item) => item.fapMemberUserId === reviewerId + ); + + if (!assignment) { + continue; + } + + rows.push( + buildReviewRow({ + proposalId: review.proposal_id, + title: review.title ?? '-', + instrumentName: review.instrument_name ?? '-', + dateAssigned: formatDate(assignment.dateAssigned), + rank: assignment.rank ?? null, + grade: assignment.grade, + comment: stripHtml(assignment.comment ?? '-'), + status: assignment.status == 1 ? 'Complete' : 'Draft', + }) + ); + } + data.push({ + sheetName: reviewerName.substring(0, 31), + rows, + }); + } + + return { + filename: 'fap_reviews.xlsx', + data, + }; +}; + +const formatDate = (value: string | Date | null | undefined): string => { + if (!value) return '-'; + + const dt = + typeof value === 'string' + ? DateTime.fromISO(value) + : DateTime.fromJSDate(value); + + return dt.isValid ? dt.toFormat('dd-MM-yyyy') : '-'; +}; diff --git a/apps/backend/src/middlewares/factory/xlsx.ts b/apps/backend/src/middlewares/factory/xlsx.ts index 07e5a377bc..109c1f0401 100644 --- a/apps/backend/src/middlewares/factory/xlsx.ts +++ b/apps/backend/src/middlewares/factory/xlsx.ts @@ -14,7 +14,10 @@ import { DefaultCallExtraFapDataColumns, collectCallFapXLSXData, } from '../../factory/xlsx/callFaps'; -import { collectFapXLSXData } from '../../factory/xlsx/fap'; +import { + collectFapXLSXData, + collectFapReviewXLSXData, +} from '../../factory/xlsx/fap'; import { collectManagementDecisionXLSXData } from '../../factory/xlsx/managementDecision'; import { collectProposalXLSXData, @@ -130,6 +133,79 @@ router.get(`/${XLSXType.FAP}/:fap_id/call/:call_id`, async (req, res, next) => { } }); +router.get( + `/${XLSXType.FAP_REVIEWS}/:fap_id/call/:call_id`, + async (req, res, next) => { + try { + if (!req.user) { + throw new Error('Not authorized'); + } + + const userWithRole = { + ...res.locals.agent, + }; + + const fapId = parseInt(req.params.fap_id); + const callId = parseInt(req.params.call_id); + + if (isNaN(fapId) || isNaN(callId)) { + throw new Error( + `Invalid Fap or call ID: Fap ${req.params.fap_id}, Call ${req.params.call_id}` + ); + } + + const reviewerProposalsParam = req.query.reviewerProposals; + + if (typeof reviewerProposalsParam !== 'string') { + throw new Error('reviewerProposals is required'); + } + + const reviewerProposals: Record = JSON.parse( + reviewerProposalsParam + ); + + const { data, filename } = await collectFapReviewXLSXData( + fapId, + callId, + reviewerProposals, + userWithRole + ); + + const meta: XLSXMetaBase = { + singleFilename: filename, + collectionFilename: filename, + columns: [ + 'Proposal ID', + 'Proposal title', + 'Instrument', + 'Date assigned', + 'Rank', + 'Grade', + 'Comment', + 'Status', + ], + }; + + const userRole = req.user.currentRole; + + callFactoryService( + DownloadType.XLSX, + XLSXType.FAP_REVIEWS, + { + data, + meta, + userRole, + }, + req, + res, + next + ); + } catch (e) { + next(e); + } + } +); + router.get(`/${XLSXType.CALL_FAP}/:call_id`, async (req, res, next) => { try { if (!req.user) { diff --git a/apps/backend/src/models/Fap.ts b/apps/backend/src/models/Fap.ts index 794c3d8bb1..b570857692 100644 --- a/apps/backend/src/models/Fap.ts +++ b/apps/backend/src/models/Fap.ts @@ -72,6 +72,9 @@ export class FapAssignment { public reassigned: boolean, public dateReassigned: Date | null, public emailSent: boolean, - public rank: number | null + public rank: number | null, + public comment: string, + public grade: string, + public status: number ) {} } diff --git a/apps/e2e/cypress/e2e/FAPs.cy.ts b/apps/e2e/cypress/e2e/FAPs.cy.ts index 19274a5473..f30aa8f941 100644 --- a/apps/e2e/cypress/e2e/FAPs.cy.ts +++ b/apps/e2e/cypress/e2e/FAPs.cy.ts @@ -1266,7 +1266,7 @@ context('Fap reviews tests', () => { cy.contains(firstCreatedProposalId).should('be.visible'); }); - it.only('Should be able to assign proposals to reviewers in the Reviewers to Assignments view', () => { + it('Should be able to assign proposals to reviewers in the Reviewers to Assignments view', () => { cy.assignProposalsToFaps({ fapInstruments: [ { instrumentId: newlyCreatedInstrumentId, fapId: createdFapId }, @@ -4071,6 +4071,152 @@ context('Fap meeting components tests', () => { cy.get('[data-cy="proposal-' + proposalId + '"]').should('exist'); }); }); + + it('FAP Secretary should be able to download the fap reviews excel sheet', () => { + cy.assignProposalsToFaps({ + fapInstruments: [ + { instrumentId: newlyCreatedInstrumentId, fapId: createdFapId }, + ], + proposalPks: [firstCreatedProposalPk], + }); + cy.assignReviewersToFap({ + fapId: createdFapId, + memberIds: [fapMembers.reviewer.id], + }); + cy.assignFapReviewersToProposals({ + assignments: [ + { + memberId: fapMembers.reviewer.id, + proposalPk: firstCreatedProposalPk, + }, + { + memberId: fapMembers.reviewer2.id, + proposalPk: firstCreatedProposalPk, + }, + ], + fapId: createdFapId, + }); + cy.changeProposalsStatus({ + workflowStatusId: fapReviewWorkflowStatusId, + proposalPks: [firstCreatedProposalPk], + }); + + cy.getProposalReviews({ + proposalPk: firstCreatedProposalPk, + }).then(({ proposalReviews }) => { + if (proposalReviews) { + proposalReviews.forEach((review, index) => { + cy.updateReview({ + reviewID: review.id, + comment: comment1, + // NOTE: Make first proposal with lower standard deviation. Grades are 2 and 4 + grade: index ? '2' : '4', + status: ReviewStatus.SUBMITTED, + fapID: createdFapId, + questionaryID: review.questionaryID, + }); + }); + } + }); + + cy.login(fapMembers.secretary); + cy.changeActiveRole(initialDBData.roles.fapSecretary); + cy.visit(`/FapPage/${createdFapId}?tab=4`); + cy.finishedLoading(); + + cy.contains(fapMembers.reviewer.lastName).should('be.visible'); + cy.contains(fapMembers.reviewer.lastName) + .parent() + .find('input[type="checkbox"]') + .click(); + + cy.get('[data-cy="export-reviews-in-excel"]').click(); + cy.get('[data-cy="preparing-download-dialog"]').should('exist'); + cy.get('[data-cy="preparing-download-dialog-item"]').contains( + 'fap-reviews.xlsx' + ); + }); + }); + + it.only('Check the contents of the FAP reviews excel sheet', function () { + cy.assignProposalsToFaps({ + fapInstruments: [ + { instrumentId: newlyCreatedInstrumentId, fapId: createdFapId }, + ], + proposalPks: [firstCreatedProposalPk], + }); + cy.assignReviewersToFap({ + fapId: createdFapId, + memberIds: [fapMembers.reviewer.id], + }); + cy.assignFapReviewersToProposals({ + assignments: [ + { + memberId: fapMembers.reviewer.id, + proposalPk: firstCreatedProposalPk, + }, + { + memberId: fapMembers.reviewer2.id, + proposalPk: firstCreatedProposalPk, + }, + ], + fapId: createdFapId, + }); + cy.changeProposalsStatus({ + workflowStatusId: fapReviewWorkflowStatusId, + proposalPks: [firstCreatedProposalPk], + }); + + cy.getProposalReviews({ + proposalPk: firstCreatedProposalPk, + }).then(({ proposalReviews }) => { + if (proposalReviews) { + proposalReviews.forEach((review, index) => { + cy.updateReview({ + reviewID: review.id, + comment: comment1, + // NOTE: Make first proposal with lower standard deviation. Grades are 2 and 4 + grade: index ? '2' : '4', + status: ReviewStatus.SUBMITTED, + fapID: createdFapId, + questionaryID: review.questionaryID, + }); + }); + } + }); + cy.login(fapMembers.secretary); + cy.changeActiveRole(initialDBData.roles.fapSecretary); + cy.visit(`/FapPage/${createdFapId}?tab=4`); + cy.finishedLoading(); + + cy.contains(fapMembers.reviewer.lastName).should('be.visible'); + cy.contains(fapMembers.reviewer.lastName) + .parent() + .find('input[type="checkbox"]') + .click(); + + cy.get('[data-cy="export-reviews-in-excel"]').click(); + cy.get('[data-cy="preparing-download-dialog"]').should('exist'); + + const downloadsFolder = Cypress.config('downloadsFolder'); + const fileName = `fap_reviews.xlsx`; + const fileUri = `${downloadsFolder}/${fileName}`; + + cy.readFile(`${downloadsFolder}/${fileName}`) + .should('exist') + .then(() => { + cy.task('convertXlsxToJson', `${downloadsFolder}/${fileName}`).then( + (actualExport) => { + cy.fixture('exampleFapReviewsExport.json').then( + (expectedExport) => { + expect(expectedExport).to.deep.equal(actualExport); + } + ); + } + ); + }); + + cy.task('deleteFile', fileUri); }); describe('Fap Reviewer role', () => { diff --git a/apps/e2e/cypress/fixtures/exampleFapReviewsExport.json b/apps/e2e/cypress/fixtures/exampleFapReviewsExport.json new file mode 100644 index 0000000000..1368e84982 --- /dev/null +++ b/apps/e2e/cypress/fixtures/exampleFapReviewsExport.json @@ -0,0 +1,12 @@ +[ + { + "Proposal ID": "567122", + "Proposal title": "lumen proofread hertz", + "Instrument": "Central lavender", + "Date assigned": "UNITED KINGDOM", + "Rank": "1", + "Grade": "Central lavender", + "Comment": "earum ante at accusantium crustulum", + "Status": "Submitted" + } +] \ No newline at end of file diff --git a/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx b/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx index f04f3caea3..0bc99ef32d 100644 --- a/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx +++ b/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx @@ -1,9 +1,11 @@ import MaterialTable, { Action, Column } from '@material-table/core'; import AssignmentInd from '@mui/icons-material/AssignmentInd'; +import GridOnIcon from '@mui/icons-material/GetApp'; import React from 'react'; import { useSearchParams } from 'react-router-dom'; import { Fap } from 'generated/sdk'; +import { useDownloadXLSXFapReviews } from 'hooks/fap/useDownloadXLSXFapReviews'; import { useExpandCollapseAll } from 'hooks/fap/useExpandCollapseAll'; import { FapMember, useFapMembersData } from 'hooks/fap/useFapMembersData'; import { @@ -122,12 +124,15 @@ const FapReviewersAndAssignmentsTable = ({ const page = searchParams.get('page'); const pageSize = searchParams.get('pageSize'); + const call = searchParams.get('call'); const { tableRef, expandCollapseAllButton } = useExpandCollapseAll( '[data-cy="fap-reviewers-assignments-table"]', [loadingMembers, loadingFapProposals] ); + const downloadFapReviewsXLSX = useDownloadXLSXFapReviews(); + const reviewersAndProposals: ReviewerAndProposals[] = FapMembersData.map( (member) => { const assignedProposals = fapProposals.FapProposalsData.filter( @@ -237,6 +242,43 @@ const FapReviewersAndAssignmentsTable = ({ setMembersToAssign(fapMemberUsersToAssign); }; + const handleBulkDownloadClick = ( + event: React.MouseEventHandler, + rowData: ReviewerAndProposals | ReviewerAndProposals[] + ) => { + if (!Array.isArray(rowData)) { + return; + } + const reviewerProposalMap = new Map(); + // const reviewerIds = rowData.map((reviewer) => reviewer.user.userId); + // const reviewerRole = rowData.map((data) => data.user.role); + + rowData.forEach((reviewer) => { + reviewer.assignedProposals.forEach(({ assignment }) => { + const reviewerId = assignment.fapMemberUserId; + const proposalPk = assignment.proposalPk; + + if (reviewerId === null) { + return; + } + + const proposals = reviewerProposalMap.get(reviewerId) ?? []; + + proposals.push(proposalPk); + + reviewerProposalMap.set(reviewerId, proposals); + }); + }); + const reviewerProposals = Object.fromEntries(reviewerProposalMap); + reviewerProposalMap.forEach((key, value) => { + console.log('reviewerid ' + key + `proposals ` + value); + }); + + downloadFapReviewsXLSX(fap.id, Number(call), 'fap-review.xlsx', { + reviewerProposals, + }); + }; + const tableActions: Action[] = []; tableActions.push({ icon: () => , @@ -244,6 +286,12 @@ const FapReviewersAndAssignmentsTable = ({ onClick: handleAssignProposalsToMembers, position: 'toolbarOnSelect', }); + tableActions.push({ + icon: () => , + tooltip: 'Export reviews in excel', + onClick: handleBulkDownloadClick, + position: 'toolbarOnSelect', + }); const maxPageLength = reviewersAndProposals.length; diff --git a/apps/frontend/src/context/DownloadContextProvider.tsx b/apps/frontend/src/context/DownloadContextProvider.tsx index 77beb19e54..1b90216f49 100644 --- a/apps/frontend/src/context/DownloadContextProvider.tsx +++ b/apps/frontend/src/context/DownloadContextProvider.tsx @@ -118,12 +118,19 @@ export enum PREPARE_DOWNLOAD_TYPE { XLSX_PROPOSAL, XLSX_FAP, XLSX_CALL_FAP, + XLSX_FAP_REVIEWS, XLSX_PROPOSAL_TECHNIQUE, XLSX_MANAGEMENT_DECISION, } +export type ReviewerProposalData = { + proposalPks: number[]; + role: string | null; +}; + export type DownloadOptions = { questionIds?: string; + reviewerProposals?: Record; }; export interface DownloadContextData { @@ -174,6 +181,20 @@ function generateLink( const [fapId, callId] = params; return `/download/xlsx/fap/${fapId}/call/${callId}`; + case PREPARE_DOWNLOAD_TYPE.XLSX_FAP_REVIEWS: { + // const [params] = ids; + + // if (!Array.isArray(params)) { + // throw new Error('Invalid params: ' + params); + // } + + const [fapId, callId] = ids; + const reviewerProposals = encodeURIComponent( + JSON.stringify(options?.reviewerProposals ?? {}) + ); + + return `/download/xlsx/fap_reviews/${fapId}/call/${callId}?reviewerProposals=${reviewerProposals}`; + } case PREPARE_DOWNLOAD_TYPE.ZIP_ATTACHMENT: if (!options?.questionIds) { throw new Error('Question ids are require'); diff --git a/apps/frontend/src/hooks/fap/useDownloadXLSXFapReviews.ts b/apps/frontend/src/hooks/fap/useDownloadXLSXFapReviews.ts new file mode 100644 index 0000000000..5399330eaa --- /dev/null +++ b/apps/frontend/src/hooks/fap/useDownloadXLSXFapReviews.ts @@ -0,0 +1,30 @@ +import { useCallback, useContext } from 'react'; + +import { + DownloadContext, + DownloadOptions, + PREPARE_DOWNLOAD_TYPE, +} from 'context/DownloadContextProvider'; + +export function useDownloadXLSXFapReviews() { + const { prepareDownload } = useContext(DownloadContext); + + const downloadFapReviewsXLSX = useCallback( + ( + fapId: number, + callId: number, + name: string, + options?: DownloadOptions + ) => { + prepareDownload( + PREPARE_DOWNLOAD_TYPE.XLSX_FAP_REVIEWS, + [fapId, callId], + name, + options + ); + }, + [prepareDownload] + ); + + return downloadFapReviewsXLSX; +} From 0bfb265469ffaa29700f5b145495ba86ebfdb89e Mon Sep 17 00:00:00 2001 From: Bhaswati Dey Date: Mon, 28 Sep 2026 11:10:33 +0100 Subject: [PATCH 2/9] add e2e test for export to excel functionality --- .../src/datasources/postgres/FapDataSource.ts | 1 - apps/backend/src/factory/xlsx/fap.ts | 2 +- apps/e2e/cypress/e2e/FAPs.cy.ts | 225 ++++++------------ .../fixtures/exampleFapReviewsExport.json | 34 ++- apps/e2e/cypress/support/fileUtilTasks.ts | 14 +- 5 files changed, 115 insertions(+), 161 deletions(-) diff --git a/apps/backend/src/datasources/postgres/FapDataSource.ts b/apps/backend/src/datasources/postgres/FapDataSource.ts index 51af5fc38e..4e4daf5733 100644 --- a/apps/backend/src/datasources/postgres/FapDataSource.ts +++ b/apps/backend/src/datasources/postgres/FapDataSource.ts @@ -1322,7 +1322,6 @@ export default class PostgresFapDataSource implements FapDataSource { fapId: number, instrumentId?: number | null ): Promise { - logger.logInfo(`Chilllllllllllllllll callid ${callId}`, {}); const query = database .select('*') .from('review_data') diff --git a/apps/backend/src/factory/xlsx/fap.ts b/apps/backend/src/factory/xlsx/fap.ts index 602fff5db9..e43701f702 100644 --- a/apps/backend/src/factory/xlsx/fap.ts +++ b/apps/backend/src/factory/xlsx/fap.ts @@ -224,7 +224,7 @@ export const collectFapReviewXLSXData = async ( rank: assignment.rank ?? null, grade: assignment.grade, comment: stripHtml(assignment.comment ?? '-'), - status: assignment.status == 1 ? 'Complete' : 'Draft', + status: assignment.status == 1 ? 'Submitted' : 'Draft', }) ); } diff --git a/apps/e2e/cypress/e2e/FAPs.cy.ts b/apps/e2e/cypress/e2e/FAPs.cy.ts index 554d994dfc..491dd8ef9a 100644 --- a/apps/e2e/cypress/e2e/FAPs.cy.ts +++ b/apps/e2e/cypress/e2e/FAPs.cy.ts @@ -1702,6 +1702,85 @@ context('Fap reviews tests', () => { cy.contains('Technical reviews').click(); cy.contains(comment1).should('exist'); }); + + it('FAP Secretary should be able to download the fap reviews excel sheet', () => { + cy.assignFapReviewersToProposals({ + assignments: [ + { + memberId: fapMembers.reviewer.id, + proposalPk: firstCreatedProposalPk, + rank: 1, + }, + { + memberId: fapMembers.reviewer2.id, + proposalPk: firstCreatedProposalPk, + rank: 2, + }, + ], + fapId: createdFapId, + }); + cy.changeProposalsStatus({ + workflowStatusId: fapReviewWorkflowStatusId, + proposalPks: [firstCreatedProposalPk], + }); + + cy.getProposalReviews({ + proposalPk: firstCreatedProposalPk, + }).then(({ proposalReviews }) => { + if (proposalReviews) { + proposalReviews.forEach((review, index) => { + cy.updateReview({ + reviewID: review.id, + comment: index === 0 ? comment1 : comment2, + grade: index == 0 ? '4' : '6', + status: index == 0 ? ReviewStatus.SUBMITTED : ReviewStatus.DRAFT, + fapID: createdFapId, + questionaryID: review.questionaryID, + }); + }); + } + }); + + cy.login(fapMembers.secretary); + cy.visit(`/FapPage/2?tab=4`); + cy.finishedLoading(); + + cy.contains(fapMembers.reviewer.lastName).should('be.visible'); + cy.contains(fapMembers.reviewer.lastName) + .parent() + .find('input[type="checkbox"]') + .click(); + + cy.contains(fapMembers.reviewer2.lastName).should('be.visible'); + cy.contains(fapMembers.reviewer2.lastName) + .parent() + .find('input[type="checkbox"]') + .click(); + + cy.get('[data-cy="export-reviews-in-excel"]').click(); + cy.readFile(`${Cypress.config('downloadsFolder')}/fap_reviews.xlsx`) + .should('exist') + .then(() => { + cy.task( + 'convertXlsxToJson', + `${Cypress.config('downloadsFolder')}/fap_reviews.xlsx` + ).then((actualExport) => { + const today = new Date(); + const currentDate = [ + String(today.getDate()).padStart(2, '0'), + String(today.getMonth() + 1).padStart(2, '0'), + today.getFullYear(), + ].join('-'); + cy.fixture('exampleFapReviewsExport.json').then( + (expectedExport) => { + expectedExport[0][0]['Date assigned'] = currentDate; + expectedExport[1][0]['Date assigned'] = currentDate; + expect(expectedExport).to.deep.equal(actualExport); + } + ); + }); + }); + }); }); describe('Fap Reviewer role', () => { @@ -4071,152 +4150,6 @@ context('Fap meeting components tests', () => { cy.get('[data-cy="proposal-' + proposalId + '"]').should('exist'); }); }); - - it('FAP Secretary should be able to download the fap reviews excel sheet', () => { - cy.assignProposalsToFaps({ - fapInstruments: [ - { instrumentId: newlyCreatedInstrumentId, fapId: createdFapId }, - ], - proposalPks: [firstCreatedProposalPk], - }); - cy.assignReviewersToFap({ - fapId: createdFapId, - memberIds: [fapMembers.reviewer.id], - }); - cy.assignFapReviewersToProposals({ - assignments: [ - { - memberId: fapMembers.reviewer.id, - proposalPk: firstCreatedProposalPk, - }, - { - memberId: fapMembers.reviewer2.id, - proposalPk: firstCreatedProposalPk, - }, - ], - fapId: createdFapId, - }); - cy.changeProposalsStatus({ - workflowStatusId: fapReviewWorkflowStatusId, - proposalPks: [firstCreatedProposalPk], - }); - - cy.getProposalReviews({ - proposalPk: firstCreatedProposalPk, - }).then(({ proposalReviews }) => { - if (proposalReviews) { - proposalReviews.forEach((review, index) => { - cy.updateReview({ - reviewID: review.id, - comment: comment1, - // NOTE: Make first proposal with lower standard deviation. Grades are 2 and 4 - grade: index ? '2' : '4', - status: ReviewStatus.SUBMITTED, - fapID: createdFapId, - questionaryID: review.questionaryID, - }); - }); - } - }); - - cy.login(fapMembers.secretary); - cy.changeActiveRole(initialDBData.roles.fapSecretary); - cy.visit(`/FapPage/${createdFapId}?tab=4`); - cy.finishedLoading(); - - cy.contains(fapMembers.reviewer.lastName).should('be.visible'); - cy.contains(fapMembers.reviewer.lastName) - .parent() - .find('input[type="checkbox"]') - .click(); - - cy.get('[data-cy="export-reviews-in-excel"]').click(); - cy.get('[data-cy="preparing-download-dialog"]').should('exist'); - cy.get('[data-cy="preparing-download-dialog-item"]').contains( - 'fap-reviews.xlsx' - ); - }); - }); - - it.only('Check the contents of the FAP reviews excel sheet', function () { - cy.assignProposalsToFaps({ - fapInstruments: [ - { instrumentId: newlyCreatedInstrumentId, fapId: createdFapId }, - ], - proposalPks: [firstCreatedProposalPk], - }); - cy.assignReviewersToFap({ - fapId: createdFapId, - memberIds: [fapMembers.reviewer.id], - }); - cy.assignFapReviewersToProposals({ - assignments: [ - { - memberId: fapMembers.reviewer.id, - proposalPk: firstCreatedProposalPk, - }, - { - memberId: fapMembers.reviewer2.id, - proposalPk: firstCreatedProposalPk, - }, - ], - fapId: createdFapId, - }); - cy.changeProposalsStatus({ - workflowStatusId: fapReviewWorkflowStatusId, - proposalPks: [firstCreatedProposalPk], - }); - - cy.getProposalReviews({ - proposalPk: firstCreatedProposalPk, - }).then(({ proposalReviews }) => { - if (proposalReviews) { - proposalReviews.forEach((review, index) => { - cy.updateReview({ - reviewID: review.id, - comment: comment1, - // NOTE: Make first proposal with lower standard deviation. Grades are 2 and 4 - grade: index ? '2' : '4', - status: ReviewStatus.SUBMITTED, - fapID: createdFapId, - questionaryID: review.questionaryID, - }); - }); - } - }); - cy.login(fapMembers.secretary); - cy.changeActiveRole(initialDBData.roles.fapSecretary); - cy.visit(`/FapPage/${createdFapId}?tab=4`); - cy.finishedLoading(); - - cy.contains(fapMembers.reviewer.lastName).should('be.visible'); - cy.contains(fapMembers.reviewer.lastName) - .parent() - .find('input[type="checkbox"]') - .click(); - - cy.get('[data-cy="export-reviews-in-excel"]').click(); - cy.get('[data-cy="preparing-download-dialog"]').should('exist'); - - const downloadsFolder = Cypress.config('downloadsFolder'); - const fileName = `fap_reviews.xlsx`; - const fileUri = `${downloadsFolder}/${fileName}`; - - cy.readFile(`${downloadsFolder}/${fileName}`) - .should('exist') - .then(() => { - cy.task('convertXlsxToJson', `${downloadsFolder}/${fileName}`).then( - (actualExport) => { - cy.fixture('exampleFapReviewsExport.json').then( - (expectedExport) => { - expect(expectedExport).to.deep.equal(actualExport); - } - ); - } - ); - }); - - cy.task('deleteFile', fileUri); }); describe('Fap Reviewer role', () => { diff --git a/apps/e2e/cypress/fixtures/exampleFapReviewsExport.json b/apps/e2e/cypress/fixtures/exampleFapReviewsExport.json index 1368e84982..cd40d91c83 100644 --- a/apps/e2e/cypress/fixtures/exampleFapReviewsExport.json +++ b/apps/e2e/cypress/fixtures/exampleFapReviewsExport.json @@ -1,12 +1,26 @@ [ - { - "Proposal ID": "567122", - "Proposal title": "lumen proofread hertz", - "Instrument": "Central lavender", - "Date assigned": "UNITED KINGDOM", - "Rank": "1", - "Grade": "Central lavender", - "Comment": "earum ante at accusantium crustulum", - "Status": "Submitted" - } + [ + { + "Proposal ID": "567122", + "Proposal title": "stultus comedo stipes", + "Instrument": "odit subseco", + "Date assigned": "", + "Rank": 1, + "Grade": "4", + "Comment": "turbo ante cibo occaecati volubilis", + "Status": "Submitted" + } + ], + [ + { + "Proposal ID": "567122", + "Proposal title": "stultus comedo stipes", + "Instrument": "odit subseco", + "Date assigned": "", + "Rank": 2, + "Grade": "6", + "Comment": "laudantium accusamus thesaurus candidus timor", + "Status": "Draft" + } + ] ] \ No newline at end of file diff --git a/apps/e2e/cypress/support/fileUtilTasks.ts b/apps/e2e/cypress/support/fileUtilTasks.ts index 0f4d4c03b7..9afee7648a 100644 --- a/apps/e2e/cypress/support/fileUtilTasks.ts +++ b/apps/e2e/cypress/support/fileUtilTasks.ts @@ -77,10 +77,18 @@ export const unzip = (args: { source: string; destination: string }) => { export const convertXlsxToJson = (filePath: string) => { const workbook = xlsx.readFile(filePath); - const worksheet = workbook.Sheets[workbook.SheetNames[0]]; - const jsonData = xlsx.utils.sheet_to_json(worksheet); + if (workbook.SheetNames.length === 1) { + const worksheet = workbook.Sheets[workbook.SheetNames[0]]; + const jsonData = xlsx.utils.sheet_to_json(worksheet); - return jsonData; + return jsonData; + } + + return workbook.SheetNames.map((sheetName) => { + const worksheet = workbook.Sheets[sheetName]; + + return xlsx.utils.sheet_to_json(worksheet); + }); }; export const deleteFile = (filePath: string) => { From 6b3ba406123e8582ace6fcf27b9d9c453b242ff6 Mon Sep 17 00:00:00 2001 From: Bhaswati Dey Date: Mon, 28 Sep 2026 16:53:12 +0100 Subject: [PATCH 3/9] skip the new test for the time being until the Factory changes are available. --- apps/e2e/cypress/e2e/FAPs.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/e2e/cypress/e2e/FAPs.cy.ts b/apps/e2e/cypress/e2e/FAPs.cy.ts index 491dd8ef9a..6328615638 100644 --- a/apps/e2e/cypress/e2e/FAPs.cy.ts +++ b/apps/e2e/cypress/e2e/FAPs.cy.ts @@ -1703,7 +1703,7 @@ context('Fap reviews tests', () => { cy.contains(comment1).should('exist'); }); - it('FAP Secretary should be able to download the fap reviews excel sheet', () => { + it.skip('FAP Secretary should be able to download the fap reviews excel sheet', () => { cy.assignFapReviewersToProposals({ assignments: [ { From 402cb575e46e5b6f874abd2ef2ef1ab8c5ecb1af Mon Sep 17 00:00:00 2001 From: Bhaswati Dey Date: Wed, 30 Sep 2026 16:24:06 +0100 Subject: [PATCH 4/9] fix failing e2e tests --- apps/e2e/cypress/e2e/FAPs.cy.ts | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/apps/e2e/cypress/e2e/FAPs.cy.ts b/apps/e2e/cypress/e2e/FAPs.cy.ts index 6328615638..315af9b054 100644 --- a/apps/e2e/cypress/e2e/FAPs.cy.ts +++ b/apps/e2e/cypress/e2e/FAPs.cy.ts @@ -417,17 +417,12 @@ context('Fap reviews tests', () => { 'not.have.class', 'Mui-disabled' ); - cy.get('[data-cy="fap-selection"]').contains(instrument.name); cy.get('[data-cy="fap-selection"]').click(); + cy.get('[data-cy="fap-selection"]').contains(instrument.name); cy.get('[role="listbox"] li[role="option"]').contains(fap1.code).click(); cy.get('[data-cy="submit"]').click(); - cy.notification({ - text: 'Proposal/s assigned to the selected Fap successfully', - variant: 'success', - }); - cy.visit(`/FapPage/${createdFapId}?tab=3`); cy.finishedLoading(); @@ -531,7 +526,7 @@ context('Fap reviews tests', () => { }); cy.login('officer'); - cy.visit(`/FapPage/${createdFapId}?tab=3&pageSize=5`); + cy.visit(`/FapPage/${createdFapId}?tab=3&pa-page=0&pa-pageSize=5`); //should go to the second page cy.get('button[aria-label="Next Page"]').click(); cy.contains(proposal1.title).should('not.exist'); @@ -1414,7 +1409,7 @@ context('Fap reviews tests', () => { cy.get('[role="dialog"]').contains('Download PDF'); }); - it('Fap Chair should be able to read/write/submit non-submitted reviews', () => { + it('Fap Chair should be able to read/write non-submitted reviews', () => { cy.assignFapReviewersToProposals({ assignments: { memberId: fapMembers.reviewer.id, @@ -1444,9 +1439,7 @@ context('Fap reviews tests', () => { .click(); cy.get('[data-cy="save-and-continue-button"]').focus().click(); - cy.contains('Submit').click(); - cy.contains('OK').click(); - cy.contains('Submitted').should('be.disabled'); + cy.contains('Submit').should('be.disabled'); cy.visit(`/FapPage/${createdFapId}?tab=3`); cy.finishedLoading(); @@ -1577,9 +1570,7 @@ context('Fap reviews tests', () => { .click(); cy.get('[data-cy="save-and-continue-button"]').focus().click(); - cy.contains('Submit').click(); - cy.contains('OK').click(); - cy.contains('Submitted').should('be.disabled'); + cy.contains('Submit').should('be.disabled'); cy.finishedLoading(); }); @@ -1703,7 +1694,7 @@ context('Fap reviews tests', () => { cy.contains(comment1).should('exist'); }); - it.skip('FAP Secretary should be able to download the fap reviews excel sheet', () => { + it('FAP Secretary should be able to download the fap reviews excel sheet', () => { cy.assignFapReviewersToProposals({ assignments: [ { @@ -4617,7 +4608,7 @@ context( cy.updateQuestionTemplateRelationSettings({ questionId: instrumentPickerQuestionId, templateId: initialDBData.template.id, - config: `{"variant":"dropdown","isMultipleSelect":true,"required":true,"requestTime":false,"readPermissions":[]}`, + config: `{"variant":"dropdown","isMultipleSelect":true,"required":true,"requestTime":false,"instruments": [],"readPermissions":[]}`, dependencies: [], }); From e155f24382b28a4fea6adeeeac2dc38b67ccc076 Mon Sep 17 00:00:00 2001 From: Bhaswati Dey Date: Thu, 1 Oct 2026 11:46:53 +0100 Subject: [PATCH 5/9] fix few more failing e2e tests --- apps/e2e/cypress/e2e/FAPs.cy.ts | 28 ++++++++++++++++++- .../fixtures/exampleCallFapExportSTFC.json | 22 +++++++-------- .../fixtures/exampleFapExportSTFC.json | 20 ++++++------- .../src/context/DownloadContextProvider.tsx | 6 ---- 4 files changed, 48 insertions(+), 28 deletions(-) diff --git a/apps/e2e/cypress/e2e/FAPs.cy.ts b/apps/e2e/cypress/e2e/FAPs.cy.ts index 315af9b054..01397c32b2 100644 --- a/apps/e2e/cypress/e2e/FAPs.cy.ts +++ b/apps/e2e/cypress/e2e/FAPs.cy.ts @@ -4983,6 +4983,17 @@ context('Fap meeting exports test', () => { cy.task('convertXlsxToJson', `${downloadsFolder}/${fileName}`).then( (actualExport) => { cy.fixture('exampleFapExportSTFC.json').then((expectedExport) => { + expectedExport[0]['Instrument Name'] = instrument1.name; + expectedExport[0]['Proposal Title'] = proposal1.title; + expectedExport[0]['Technical Review Comment'] = comment1; + expectedExport[0]['Reviewer 1 review comment'] = comment1; + expectedExport[0]['Reviewer 2 review comment'] = comment1; + + expectedExport[1]['Instrument Name'] = instrument1.name; + expectedExport[1]['Proposal Title'] = proposal2.title; + expectedExport[1]['Technical Review Comment'] = comment2; + expectedExport[1]['Reviewer 1 review comment'] = comment2; + expectedExport[1]['Reviewer 2 review comment'] = comment2; expect(expectedExport).to.deep.equal(actualExport); }); } @@ -5004,7 +5015,7 @@ context('Fap meeting exports test', () => { cy.contains(updatedCall.shortCode) .parent() - .find('[aria-label="Export Fap Data"]') + .find('[aria-label="Export FAP Data"]') .click(); cy.get('[data-cy=preparing-download-dialog').should('not.exist'); @@ -5020,6 +5031,21 @@ context('Fap meeting exports test', () => { (actualExport) => { cy.fixture('exampleCallFapExportSTFC.json').then( (expectedExport) => { + expectedExport[0]['Proposal Reference Number'] = + instrument1.name; + + expectedExport[1]['Instrument Name'] = instrument1.name; + expectedExport[1]['Proposal Title'] = proposal1.title; + expectedExport[1]['Technical Review Comment'] = comment1; + expectedExport[1]['Reviewer 1 review comment'] = comment1; + expectedExport[1]['Reviewer 2 review comment'] = comment1; + + expectedExport[2]['Instrument Name'] = instrument1.name; + expectedExport[2]['Proposal Title'] = proposal2.title; + expectedExport[2]['Technical Review Comment'] = comment2; + expectedExport[2]['Reviewer 1 review comment'] = comment2; + expectedExport[2]['Reviewer 2 review comment'] = comment2; + expect(expectedExport).to.deep.equal(actualExport); } ); diff --git a/apps/e2e/cypress/fixtures/exampleCallFapExportSTFC.json b/apps/e2e/cypress/fixtures/exampleCallFapExportSTFC.json index 79e3836dac..bd8b458e59 100755 --- a/apps/e2e/cypress/fixtures/exampleCallFapExportSTFC.json +++ b/apps/e2e/cypress/fixtures/exampleCallFapExportSTFC.json @@ -1,6 +1,6 @@ [ { - "Proposal Reference Number": "Central lavender" + "Proposal Reference Number": "" }, { "Proposal Reference Number": "567122", @@ -8,17 +8,17 @@ "Principal Investigator": "Carl Carlsson", "PI Country": "UNITED KINGDOM", "PI Organisation": "STFC", - "Instrument Name": "Central lavender", + "Instrument Name": "", "Requested Time": "", - "Proposal Title": "lumen proofread hertz", - "Technical Review Comment": "earum ante at accusantium crustulum", + "Proposal Title": "", + "Technical Review Comment": "", "Average score": 3, "Reviewer 1": "Nils Nilsson ", "Reviewer 1 score": "4", - "Reviewer 1 review comment": "earum ante at accusantium crustulum", + "Reviewer 1 review comment": "", "Reviewer 2": " Dawson ", "Reviewer 2 score": "2", - "Reviewer 2 review comment": "earum ante at accusantium crustulum", + "Reviewer 2 review comment": "", "Reviewer 3": "No reviewer", "Reviewer 3 score": "-", "Reviewer 3 review comment": "-", @@ -33,17 +33,17 @@ "Principal Investigator": "Carl Carlsson", "PI Country": "UNITED KINGDOM", "PI Organisation": "STFC", - "Instrument Name": "Central lavender", + "Instrument Name": "", "Requested Time": "", - "Proposal Title": "web Connecticut driver", - "Technical Review Comment": "acquiro amita ad dolore cariosus", + "Proposal Title": "", + "Technical Review Comment": "", "Average score": 3, "Reviewer 1": "Nils Nilsson ", "Reviewer 1 score": "5", - "Reviewer 1 review comment": "acquiro amita ad dolore cariosus", + "Reviewer 1 review comment": "", "Reviewer 2": " Dawson ", "Reviewer 2 score": "1", - "Reviewer 2 review comment": "acquiro amita ad dolore cariosus", + "Reviewer 2 review comment": "", "Reviewer 3": "No reviewer", "Reviewer 3 score": "-", "Reviewer 3 review comment": "-", diff --git a/apps/e2e/cypress/fixtures/exampleFapExportSTFC.json b/apps/e2e/cypress/fixtures/exampleFapExportSTFC.json index a4572b0a88..9e6ef17de9 100755 --- a/apps/e2e/cypress/fixtures/exampleFapExportSTFC.json +++ b/apps/e2e/cypress/fixtures/exampleFapExportSTFC.json @@ -5,17 +5,17 @@ "Principal Investigator": "Carl Carlsson", "PI Country": "UNITED KINGDOM", "PI Organisation": "STFC", - "Instrument Name": "Central lavender", + "Instrument Name": "", "Requested Time": "", - "Proposal Title": "lumen proofread hertz", - "Technical Review Comment": "earum ante at accusantium crustulum", + "Proposal Title": "", + "Technical Review Comment": "", "Average score": 3, "Reviewer 1": "Nils Nilsson ", "Reviewer 1 score": "4", - "Reviewer 1 review comment": "earum ante at accusantium crustulum", + "Reviewer 1 review comment": "", "Reviewer 2": " Dawson ", "Reviewer 2 score": "2", - "Reviewer 2 review comment": "earum ante at accusantium crustulum", + "Reviewer 2 review comment": "", "Reviewer 3": "No reviewer", "Reviewer 3 score": "-", "Reviewer 3 review comment": "-" @@ -26,17 +26,17 @@ "Principal Investigator": "Carl Carlsson", "PI Country": "UNITED KINGDOM", "PI Organisation": "STFC", - "Instrument Name": "Central lavender", + "Instrument Name": "", "Requested Time": "", - "Proposal Title": "web Connecticut driver", - "Technical Review Comment": "acquiro amita ad dolore cariosus", + "Proposal Title": "", + "Technical Review Comment": "", "Average score": 3, "Reviewer 1": "Nils Nilsson ", "Reviewer 1 score": "5", - "Reviewer 1 review comment": "acquiro amita ad dolore cariosus", + "Reviewer 1 review comment": "", "Reviewer 2": " Dawson ", "Reviewer 2 score": "1", - "Reviewer 2 review comment": "acquiro amita ad dolore cariosus", + "Reviewer 2 review comment": "", "Reviewer 3": "No reviewer", "Reviewer 3 score": "-", "Reviewer 3 review comment": "-" diff --git a/apps/frontend/src/context/DownloadContextProvider.tsx b/apps/frontend/src/context/DownloadContextProvider.tsx index 1b90216f49..10ef7df3c7 100644 --- a/apps/frontend/src/context/DownloadContextProvider.tsx +++ b/apps/frontend/src/context/DownloadContextProvider.tsx @@ -182,12 +182,6 @@ function generateLink( return `/download/xlsx/fap/${fapId}/call/${callId}`; case PREPARE_DOWNLOAD_TYPE.XLSX_FAP_REVIEWS: { - // const [params] = ids; - - // if (!Array.isArray(params)) { - // throw new Error('Invalid params: ' + params); - // } - const [fapId, callId] = ids; const reviewerProposals = encodeURIComponent( JSON.stringify(options?.reviewerProposals ?? {}) From 256d89ba63eae82521da65504afaaebb68c8f6ae Mon Sep 17 00:00:00 2001 From: Bhaswati Dey Date: Fri, 2 Oct 2026 11:48:32 +0100 Subject: [PATCH 6/9] clean up --- .../ReviewersView/FapReviewersAndAssignmentsTable.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx b/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx index b26bed8561..f68d20d57c 100644 --- a/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx +++ b/apps/frontend/src/components/fap/Proposals/ReviewersView/FapReviewersAndAssignmentsTable.tsx @@ -254,9 +254,6 @@ const FapReviewersAndAssignmentsTable = ({ return; } const reviewerProposalMap = new Map(); - // const reviewerIds = rowData.map((reviewer) => reviewer.user.userId); - // const reviewerRole = rowData.map((data) => data.user.role); - rowData.forEach((reviewer) => { reviewer.assignedProposals.forEach(({ assignment }) => { const reviewerId = assignment.fapMemberUserId; @@ -274,9 +271,6 @@ const FapReviewersAndAssignmentsTable = ({ }); }); const reviewerProposals = Object.fromEntries(reviewerProposalMap); - reviewerProposalMap.forEach((key, value) => { - console.log('reviewerid ' + key + `proposals ` + value); - }); downloadFapReviewsXLSX(fap.id, Number(call), 'fap-review.xlsx', { reviewerProposals, From 20724b834d1a9c9dcbe2a8b4b602a67350a6d1a1 Mon Sep 17 00:00:00 2001 From: Bhaswati Dey Date: Tue, 6 Oct 2026 11:08:38 +0100 Subject: [PATCH 7/9] correction of the exception message --- apps/backend/src/middlewares/factory/xlsx.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/backend/src/middlewares/factory/xlsx.ts b/apps/backend/src/middlewares/factory/xlsx.ts index 109c1f0401..b8b5984929 100644 --- a/apps/backend/src/middlewares/factory/xlsx.ts +++ b/apps/backend/src/middlewares/factory/xlsx.ts @@ -157,7 +157,7 @@ router.get( const reviewerProposalsParam = req.query.reviewerProposals; if (typeof reviewerProposalsParam !== 'string') { - throw new Error('reviewerProposals is required'); + throw new Error('Proposal reviewer is required'); } const reviewerProposals: Record = JSON.parse( From 03d1db866eec357df97a971ed831b3e9ff09468a Mon Sep 17 00:00:00 2001 From: Bhaswati Dey Date: Tue, 6 Oct 2026 16:27:54 +0100 Subject: [PATCH 8/9] sort the rows in ascending rank order and descending grades --- apps/backend/src/factory/xlsx/fap.ts | 40 ++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/apps/backend/src/factory/xlsx/fap.ts b/apps/backend/src/factory/xlsx/fap.ts index e43701f702..7c78e2c4c9 100644 --- a/apps/backend/src/factory/xlsx/fap.ts +++ b/apps/backend/src/factory/xlsx/fap.ts @@ -193,7 +193,11 @@ export const collectFapReviewXLSXData = async ( const reviewerName = `${reviewer?.firstname ?? ''} ${reviewer?.lastname ?? ''}`.trim(); - const rows: Array> = []; + const rankedRows: Array<{ + rank: number | null; + grade: number | null; + row: Array; + }> = []; for (const proposalPk of proposalPks) { const review = reviewData.find((item) => item.proposal_pk === proposalPk); @@ -215,8 +219,11 @@ export const collectFapReviewXLSXData = async ( continue; } - rows.push( - buildReviewRow({ + const grade = Number(assignment.grade); + rankedRows.push({ + rank: assignment.rank, + grade: Number.isFinite(grade) ? grade : null, + row: buildReviewRow({ proposalId: review.proposal_id, title: review.title ?? '-', instrumentName: review.instrument_name ?? '-', @@ -225,9 +232,32 @@ export const collectFapReviewXLSXData = async ( grade: assignment.grade, comment: stripHtml(assignment.comment ?? '-'), status: assignment.status == 1 ? 'Submitted' : 'Draft', - }) - ); + }), + }); } + + const rows = rankedRows + .sort((a, b) => { + if (a.rank === null || b.rank === null) { + if (a.rank !== b.rank) { + return a.rank === null ? 1 : -1; + } + } else if (a.rank !== b.rank) { + return a.rank - b.rank; + } + + if (a.grade === null || b.grade === null) { + if (a.grade !== b.grade) { + return a.grade === null ? 1 : -1; + } + } else if (a.grade !== b.grade) { + return b.grade - a.grade; + } + + return 0; + }) + .map(({ row }) => row); + data.push({ sheetName: reviewerName.substring(0, 31), rows, From 839d7b9c1d9cc884be61d3de1ff6922c0a098709 Mon Sep 17 00:00:00 2001 From: Bhaswati Dey Date: Wed, 7 Oct 2026 11:46:59 +0100 Subject: [PATCH 9/9] fix failing e2e test --- apps/e2e/cypress/e2e/FAPs.cy.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/e2e/cypress/e2e/FAPs.cy.ts b/apps/e2e/cypress/e2e/FAPs.cy.ts index 01397c32b2..7c0a99eb09 100644 --- a/apps/e2e/cypress/e2e/FAPs.cy.ts +++ b/apps/e2e/cypress/e2e/FAPs.cy.ts @@ -570,7 +570,9 @@ context('Fap reviews tests', () => { cy.get('[role=presentation]').contains(instrument.name).click(); cy.get('body').type('{esc}'); - cy.get('[data-cy="fap-assignments-table"]').contains(instrument.name); + cy.get('[data-cy="fap-assignments-table"]').contains( + instrument.shortCode + ); cy.get('[data-cy="fap-assignments-table"]').contains( firstCreatedProposalPk );