Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions apps/backend/src/datasources/CallDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ export interface CallDataSource {
getCallByAnswerIdProposal(answerId: number): Promise<Call>;
getProposalWorkflowByCall(callId: number): Promise<Workflow | null>;
getExperimentWorkflowByCall(callId: number): Promise<Workflow | null>;
getCallsOfFaps(fapIds: number[]): Promise<Call[]>;
}
1 change: 1 addition & 0 deletions apps/backend/src/datasources/InstrumentDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,5 @@ export interface InstrumentDataSource {
instrumentId: number,
proposalPk: number
): Promise<boolean>;
getInstrumentsByFapIds(fapId: number[]): Promise<Instrument[]>;
}
4 changes: 4 additions & 0 deletions apps/backend/src/datasources/mockups/CallDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,8 @@ export class CallDataSourceMock implements CallDataSource {
async getExperimentWorkflowByCall(callId: number): Promise<Workflow | null> {
return dummyWorkflow;
}

getCallsOfFaps(fapIds: number[]): Promise<Call[]> {
throw new Error('Method not implemented.');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,8 @@ export class InstrumentDataSourceMock implements InstrumentDataSource {
): Promise<boolean> {
throw new Error('Method not implemented.');
}

getInstrumentsByFapIds(fapId: number[]): Promise<Instrument[]> {
throw new Error('Method not implemented.');
}
}
11 changes: 11 additions & 0 deletions apps/backend/src/datasources/postgres/CallDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -640,4 +640,15 @@ export default class PostgresCallDataSource implements CallDataSource {
: null
);
}

async getCallsOfFaps(fapIds: number[]): Promise<Call[]> {
return database
.distinct('call.*')
.from('call')
.join('call_has_faps as chf', 'chf.call_id', 'call.call_id')
.whereIn('chf.fap_id', fapIds)
.then((calls: CallRecord[]) => {
return calls.map((call) => createCallObject(call));
});
}
}
16 changes: 16 additions & 0 deletions apps/backend/src/datasources/postgres/InstrumentDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,4 +796,20 @@ export default class PostgresInstrumentDataSource
return result?.count === '1';
});
}

async getInstrumentsByFapIds(fapId: number[]): Promise<Instrument[]> {
return database
.select('i.*')
.from('fap_proposals as fp')
.join('instruments as i', { 'fp.instrument_id': 'i.instrument_id' })
.where('fp.fap_id', 'in', fapId)
.distinct()
.then((instruments: InstrumentRecord[]) => {
const result = instruments.map((instrument) =>
this.createInstrumentObject(instrument)
);

return result;
});
}
}
19 changes: 18 additions & 1 deletion apps/backend/src/queries/CallQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { inject, injectable } from 'tsyringe';
import { UserAuthorization } from '../auth/UserAuthorization';
import { Tokens } from '../config/Tokens';
import { CallDataSource } from '../datasources/CallDataSource';
import { FapDataSource } from '../datasources/FapDataSource';
import { Authorized } from '../decorators';
import { Roles } from '../models/Role';
import { UserWithRole } from '../models/User';
Expand All @@ -13,7 +14,9 @@ import { PaginationSortDirection } from '../utils/pagination';
export default class CallQueries {
constructor(
@inject(Tokens.CallDataSource) public dataSource: CallDataSource,
@inject(Tokens.UserAuthorization) private userAuth: UserAuthorization
@inject(Tokens.UserAuthorization) private userAuth: UserAuthorization,
@inject(Tokens.FapDataSource)
public fapDataSource: FapDataSource
) {}

@Authorized()
Expand Down Expand Up @@ -67,4 +70,18 @@ export default class CallQueries {
async getCallOfAnswersProposal(user: UserWithRole | null, answerId: number) {
return this.dataSource.getCallByAnswerIdProposal(answerId);
}

@Authorized([Roles.FAP_REVIEWER, Roles.FAP_CHAIR, Roles.FAP_SECRETARY])
Comment thread
TCMeldrum marked this conversation as resolved.
async getCallsOfReviewer(agent: UserWithRole | null) {
Comment thread
TCMeldrum marked this conversation as resolved.
if (!agent || !agent.id || !agent.currentRole) {
return [];
}

const faps = await this.fapDataSource.getUserFapsByRoleAndFapId(
agent.id,
agent.currentRole
);

return this.dataSource.getCallsOfFaps(faps.map((fap) => fap.id));
}
}
27 changes: 19 additions & 8 deletions apps/backend/src/queries/InstrumentQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { inject, injectable } from 'tsyringe';

import { UserAuthorization } from '../auth/UserAuthorization';
import { Tokens } from '../config/Tokens';
import { FapDataSource } from '../datasources/FapDataSource';
import { InstrumentDataSource } from '../datasources/InstrumentDataSource';
import { Authorized } from '../decorators';
import { Instrument, InstrumentWithManagementTime } from '../models/Instrument';
Expand All @@ -14,7 +15,9 @@ export default class InstrumentQueries {
@inject(Tokens.InstrumentDataSource)
public dataSource: InstrumentDataSource,
@inject(Tokens.UserAuthorization)
private userAuth: UserAuthorization
private userAuth: UserAuthorization,
@inject(Tokens.FapDataSource)
public fapDataSource: FapDataSource
) {}

@Authorized()
Expand All @@ -32,13 +35,7 @@ export default class InstrumentQueries {
return await this.dataSource.getInstrumentsByIds(instrumentIds);
}

@Authorized([
Roles.USER_OFFICER,
Roles.FAP_REVIEWER,
Roles.FAP_CHAIR,
Roles.FAP_SECRETARY,
Roles.INSTRUMENT_SCIENTIST,
])
@Authorized([Roles.USER_OFFICER, Roles.INSTRUMENT_SCIENTIST])
async getAll(agent: UserWithRole | null, callIds: number[]) {
if (!callIds || callIds.length === 0) {
return await this.dataSource.getInstruments();
Expand Down Expand Up @@ -98,6 +95,20 @@ export default class InstrumentQueries {
}
}

@Authorized([Roles.FAP_REVIEWER, Roles.FAP_CHAIR, Roles.FAP_SECRETARY])
Comment thread
TCMeldrum marked this conversation as resolved.
async getFapReviewerInstruments(agent: UserWithRole | null) {
if (!agent || !agent.id || !agent.currentRole) {
return [];
}

const faps = await this.fapDataSource.getUserFapsByRoleAndFapId(
agent.id,
agent.currentRole
);

return this.dataSource.getInstrumentsByFapIds(faps.map((fap) => fap.id));
}

@Authorized()
async hasInstrumentScientistInstrument(
agent: UserWithRole | null,
Expand Down
5 changes: 5 additions & 0 deletions apps/backend/src/resolvers/queries/CallsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,9 @@ export class CallsQuery {
scientistId
);
}

@Query(() => [Call], { nullable: true })
callsOfReviewer(@Ctx() context: ResolverContext) {
return context.queries.call.getCallsOfReviewer(context.user);
}
}
7 changes: 7 additions & 0 deletions apps/backend/src/resolvers/queries/InstrumentQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,11 @@ export class InstrumentQuery {
proposalPk
);
}

@Query(() => [Instrument], { nullable: true })
async getInstrumentsOfReviewer(
@Ctx() context: ResolverContext
): Promise<Instrument[]> {
return context.queries.instrument.getFapReviewerInstruments(context.user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import {
UserRole,
UserWithReviewsQuery,
} from 'generated/sdk';
import { useCallsData } from 'hooks/call/useCallsData';
import { useReviewerCallsData } from 'hooks/call/useReviewerCallData';
import { useCheckAccess } from 'hooks/common/useCheckAccess';
import { useInstrumentsMinimalData } from 'hooks/instrument/useInstrumentsMinimalData';
import { useReviewerInstrumentData } from 'hooks/instrument/useReviewerInstrumentData';
import { useDownloadPDFProposal } from 'hooks/proposal/useDownloadPDFProposal';
import { useUserWithReviewsData } from 'hooks/user/useUserData';
import { capitalize, setSortDirectionOnSortField } from 'utils/helperFunctions';
Expand Down Expand Up @@ -84,8 +84,8 @@ const columns: (

const ProposalTableReviewer = ({ confirm }: { confirm: WithConfirmType }) => {
const downloadPDFProposal = useDownloadPDFProposal();
const { calls, loadingCalls } = useCallsData();
const { instruments, loadingInstruments } = useInstrumentsMinimalData();
const { calls, loadingCalls } = useReviewerCallsData();
const { instruments, loadingInstruments } = useReviewerInstrumentData();
const { api } = useDataApiWithFeedback();
const { t } = useTranslation();
const isFapReviewer = useCheckAccess([UserRole.FAP_REVIEWER]);
Expand Down
6 changes: 6 additions & 0 deletions apps/frontend/src/graphql/call/getReviewerCalls.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
query getReviewerCalls {
callsOfReviewer {
id
shortCode
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
query getReviewerInstruments {
getInstrumentsOfReviewer {
...instrumentMinimal
}
}
47 changes: 47 additions & 0 deletions apps/frontend/src/hooks/call/useReviewerCallData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useEffect, useState, SetStateAction } from 'react';

import { Call } from 'generated/sdk';
import { useDataApi } from 'hooks/common/useDataApi';

export enum CallsDataQuantity {
EXTENDED,
MINIMAL,
}

export function useReviewerCallsData() {
const [calls, setCalls] = useState<Call[]>([]);
const [loadingCalls, setLoadingCalls] = useState(true);

const api = useDataApi();

const setCallsWithLoading = (data: SetStateAction<Call[]>) => {
setLoadingCalls(true);
setCalls(data);
setLoadingCalls(false);
};

useEffect(() => {
let unmounted = false;

setLoadingCalls(true);

api()
.getReviewerCalls()
.then((data) => {
if (unmounted) {
return;
}

if (data.callsOfReviewer) {
setCalls(data.callsOfReviewer as Call[]);
}
setLoadingCalls(false);
});

return () => {
unmounted = true;
};
}, [api]);

return { loadingCalls, calls, setCallsWithLoading };
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,7 @@ export function useInstrumentsMinimalData(callIds?: number[]): {
setLoadingInstruments(true);
if (
currentRole &&
[
UserRole.USER_OFFICER,
UserRole.FAP_REVIEWER,
UserRole.FAP_CHAIR,
UserRole.FAP_SECRETARY,
UserRole.USER,
].includes(currentRole)
[UserRole.USER_OFFICER, UserRole.USER].includes(currentRole)
) {
api()
.getInstrumentsMinimal({ callIds })
Expand Down
47 changes: 47 additions & 0 deletions apps/frontend/src/hooks/instrument/useReviewerInstrumentData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useEffect, useState, SetStateAction, Dispatch } from 'react';

import { InstrumentMinimalFragment } from 'generated/sdk';
import { useDataApi } from 'hooks/common/useDataApi';

export function useReviewerInstrumentData(): {
loadingInstruments: boolean;
instruments: InstrumentMinimalFragment[];
setInstruments: Dispatch<SetStateAction<InstrumentMinimalFragment[]>>;
} {
const api = useDataApi();

const [instruments, setInstruments] = useState<InstrumentMinimalFragment[]>(
[]
);
const [loadingInstruments, setLoadingInstruments] = useState(true);

useEffect(() => {
let unmounted = false;

setLoadingInstruments(true);

api()
.getReviewerInstruments()
.then((data) => {
if (unmounted) {
return;
}

if (data.getInstrumentsOfReviewer) {
setInstruments(data.getInstrumentsOfReviewer);
}
setLoadingInstruments(false);
});

return () => {
// used to avoid unmounted component state update error
unmounted = true;
};
}, [api]);

return {
loadingInstruments,
instruments,
setInstruments,
};
}
Loading