Skip to content

Commit b93186c

Browse files
feat: add new column for FAP review of time requested (#1584)
This PR adds a new column to the FAP review table that shows how much time was requested for each instrument. This allows the FAP reviewers to be better and more easily informed when awarding time in this UI.
1 parent bbeccf5 commit b93186c

6 files changed

Lines changed: 103 additions & 13 deletions

File tree

apps/backend/src/datasources/ProposalDataSource.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PaginationSortDirection } from '../utils/pagination';
99
import { ProposalsFilter } from './../resolvers/queries/ProposalsQuery';
1010

1111
export interface ProposalDataSource {
12+
getRequestedTime(proposalPk: number, instrumentId: number): Promise<number>;
1213
getProposalsFromView(
1314
filter?: ProposalsFilter,
1415
first?: number,

apps/backend/src/datasources/mockups/ProposalDataSource.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,4 +374,11 @@ export class ProposalDataSourceMock implements ProposalDataSource {
374374
getInvitedProposal(inviteId: number): Promise<InvitedProposal | null> {
375375
throw new Error('Method not implemented.');
376376
}
377+
378+
async getRequestedTime(
379+
proposalPk: number,
380+
instrumentId: number
381+
): Promise<number> {
382+
return 32;
383+
}
377384
}

apps/backend/src/datasources/postgres/ProposalDataSource.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,32 @@ export default class PostgresProposalDataSource implements ProposalDataSource {
312312
});
313313
}
314314

315+
async getRequestedTime(
316+
proposalPk: number,
317+
instrumentId: number
318+
): Promise<number> {
319+
//Non-nullable, time is either requested for the instrument or it is zero.
320+
const result = await database('proposals as p')
321+
.sum({
322+
total_time_requested: database.raw(
323+
"(a.answer->'value'->>'timeRequested')::numeric"
324+
),
325+
})
326+
.innerJoin('questionaries as q2', 'q2.questionary_id', 'p.questionary_id')
327+
.innerJoin('answers as a', 'a.questionary_id', 'q2.questionary_id')
328+
.innerJoin('questions as q', 'q.question_id', 'a.question_id')
329+
.where('p.proposal_pk', proposalPk)
330+
.where('q.data_type', 'INSTRUMENT_PICKER')
331+
.whereRaw("a.answer->'value'->>'instrumentId' = ?", [
332+
instrumentId.toString(),
333+
])
334+
.first();
335+
336+
return result?.total_time_requested
337+
? Number(result.total_time_requested)
338+
: 0;
339+
}
340+
315341
async create(
316342
proposer_id: number,
317343
call_id: number,

apps/backend/src/resolvers/queries/ProposalQuery.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import { container } from 'tsyringe';
12
import { Query, Ctx, Resolver, Arg, Int } from 'type-graphql';
23

4+
import { Tokens } from '../../config/Tokens';
35
import { ResolverContext } from '../../context';
6+
import { ProposalDataSource } from '../../datasources/ProposalDataSource';
47
import { Proposal } from '../types/Proposal';
8+
59
@Resolver()
610
export class ProposalQuery {
711
@Query(() => Proposal, { nullable: true })
@@ -19,4 +23,21 @@ export class ProposalQuery {
1923
): Promise<boolean> {
2024
return context.queries.proposal.get(context.user, proposalPk) !== null;
2125
}
26+
27+
@Query(() => Number, { nullable: false })
28+
async proposalTimeRequested(
29+
@Arg('proposalPk', () => Int) proposalPk: number,
30+
@Arg('instrumentId', () => Int) instrumentId: number,
31+
@Ctx() context: ResolverContext
32+
): Promise<number> {
33+
const proposalDataSource = container.resolve<ProposalDataSource>(
34+
Tokens.ProposalDataSource
35+
);
36+
const timeRequested = await proposalDataSource.getRequestedTime(
37+
proposalPk,
38+
instrumentId
39+
);
40+
41+
return timeRequested || 0;
42+
}
2243
}

apps/frontend/src/components/fap/MeetingComponents/FapInstrumentProposalsTable.tsx

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type FapProposalWithAverageScoreAndAvailabilityZone = FapProposal & {
4242
proposalAverageScore: number | string;
4343
proposalDeviation: number | string;
4444
isInAvailabilityZone: boolean;
45+
timeRequested: number;
4546
tableData?: { index: number; id: number };
4647
};
4748

@@ -152,7 +153,7 @@ const FapInstrumentProposalsTable = ({
152153
},
153154
},
154155
{
155-
title: 'Principal Investigator',
156+
title: 'Principal investigator',
156157
render: (rowData: FapProposal) => {
157158
return getFullUserName(rowData.proposal.proposer);
158159
},
@@ -185,6 +186,10 @@ const FapInstrumentProposalsTable = ({
185186
return rankOrder || '-';
186187
},
187188
},
189+
{
190+
title: 'Time requested',
191+
field: 'timeRequested',
192+
},
188193
{
189194
title: 'Time allocation',
190195
field: 'timeAllocation',
@@ -223,13 +228,16 @@ const FapInstrumentProposalsTable = ({
223228
];
224229

225230
// NOTE: This is needed for adding the allocation time unit information on the column title without causing some console warning on re-rendering.
226-
const columns = assignmentColumns.map((column) => ({
227-
...column,
228-
title:
229-
column.field === 'timeAllocation'
230-
? `${column.title} (${selectedCall?.allocationTimeUnit}s)`
231-
: column.title,
232-
}));
231+
const columns = assignmentColumns.map((column) => {
232+
if (column.field === 'timeAllocation' || column.field === 'timeRequested') {
233+
return {
234+
...column,
235+
title: `${column.title} (${selectedCall?.allocationTimeUnit}s)`,
236+
};
237+
}
238+
239+
return column;
240+
});
233241

234242
const DragState = {
235243
row: -1,
@@ -272,18 +280,25 @@ const FapInstrumentProposalsTable = ({
272280
useState<FapProposalWithAverageScoreAndAvailabilityZone[]>([]);
273281

274282
useEffect(() => {
275-
const sortByRankOrAverageScore = (data: FapProposal[]) => {
283+
const sortByRankOrAverageScore = async (data: FapProposal[]) => {
276284
let allocationTimeSum = 0;
277285

278-
return data
279-
.map((proposalData) => {
286+
const returnData = await Promise.all(
287+
data.map(async (proposalData) => {
280288
const proposalAverageScore = average(
281289
getGradesFromReviews(proposalData.proposal.reviews ?? [])
282290
);
283291
const proposalDeviation = standardDeviation(
284292
getGradesFromReviews(proposalData.proposal.reviews ?? [])
285293
);
286294

295+
const result = await api().getProposalTimeRequested({
296+
proposalPk: proposalData.proposal.primaryKey,
297+
instrumentId: fapInstrument.id,
298+
});
299+
300+
const timeRequested = result.proposalTimeRequested;
301+
287302
return {
288303
...proposalData,
289304
proposalAverageScore: isNaN(proposalAverageScore)
@@ -292,8 +307,12 @@ const FapInstrumentProposalsTable = ({
292307
proposalDeviation: isNaN(proposalDeviation)
293308
? '-'
294309
: proposalDeviation,
310+
timeRequested,
295311
};
296312
})
313+
);
314+
315+
const sortedData = await returnData
297316
.sort((a, b) => {
298317
if (
299318
typeof a.proposalDeviation === 'number' &&
@@ -343,10 +362,20 @@ const FapInstrumentProposalsTable = ({
343362
};
344363
}
345364
});
365+
366+
return sortedData;
346367
};
347368

348-
const sortedProposals = sortByRankOrAverageScore(instrumentProposalsData);
349-
setSortedProposalsWithAverageScore(sortedProposals);
369+
const run = async () => {
370+
const sortedProposals = await sortByRankOrAverageScore(
371+
instrumentProposalsData
372+
);
373+
setSortedProposalsWithAverageScore(sortedProposals);
374+
};
375+
376+
if (instrumentProposalsData.length) {
377+
run();
378+
}
350379
}, [instrumentProposalsData, fapInstrument.availabilityTime]);
351380

352381
const ProposalTimeAllocationColumn = (
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
query getProposalTimeRequested($proposalPk: Int!, $instrumentId: Int!) {
2+
proposalTimeRequested(
3+
proposalPk: $proposalPk,
4+
instrumentId: $instrumentId
5+
)
6+
}

0 commit comments

Comments
 (0)