Skip to content
Open
80 changes: 63 additions & 17 deletions apps/backend/src/datasources/postgres/ProposalDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ProposalDataSource } from '../ProposalDataSource';
import { TagDataSource } from '../TagDataSource';
import { WorkflowDataSource } from '../WorkflowDataSource';
import {
InstrumentFilterInput,
ProposalsFilter,
QuestionFilterInput,
} from './../../resolvers/queries/ProposalsQuery';
Expand Down Expand Up @@ -94,6 +95,29 @@ export async function calculateReferenceNumber(
return prefix + paddedSequence;
}

/**
* Resolves instrument IDs from an InstrumentFilterInput.
* Supports both `instrumentIds` and `instrumentId`(deprecated).
*/
export function resolveInstrumentIds(
instrumentFilter?: InstrumentFilterInput
): number[] | undefined {
if (!instrumentFilter) {
return undefined;
}
if (
instrumentFilter.instrumentIds &&
instrumentFilter.instrumentIds.length > 0
) {
return instrumentFilter.instrumentIds;
}
if (instrumentFilter.instrumentId) {
return [instrumentFilter.instrumentId];
}

return undefined;
}

@injectable()
export default class PostgresProposalDataSource implements ProposalDataSource {
constructor(
Expand Down Expand Up @@ -471,11 +495,20 @@ export default class PostgresProposalDataSource implements ProposalDataSource {

if (filter?.instrumentFilter?.showMultiInstrumentProposals) {
query.whereRaw('jsonb_array_length(instruments) > 1');
} else if (filter?.instrumentFilter?.instrumentId) {
query.whereRaw(
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
{ instrumentId: filter.instrumentFilter.instrumentId }
} else {
const effectiveInstrumentIds = resolveInstrumentIds(
filter?.instrumentFilter
);
if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
query.where(function () {
effectiveInstrumentIds.forEach((id) => {
this.orWhereRaw(
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
{ instrumentId: id }
);
});
});
}
}

if (filter?.proposalStatusId) {
Expand Down Expand Up @@ -626,16 +659,19 @@ export default class PostgresProposalDataSource implements ProposalDataSource {
);
}

if (filter?.instrumentFilter?.instrumentId) {
const effectiveInstrumentIds = resolveInstrumentIds(
filter?.instrumentFilter
);
if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
query
.leftJoin(
'instrument_has_proposals',
'instrument_has_proposals.proposal_pk',
'proposals.proposal_pk'
)
.where(
.whereIn(
'instrument_has_proposals.instrument_id',
filter.instrumentFilter.instrumentId
effectiveInstrumentIds
);
}

Expand Down Expand Up @@ -782,12 +818,21 @@ export default class PostgresProposalDataSource implements ProposalDataSource {

if (filter?.instrumentFilter?.showMultiInstrumentProposals) {
query.whereRaw('jsonb_array_length(instruments) > 1');
} else if (filter?.instrumentFilter?.instrumentId) {
// NOTE: Using jsonpath we check the jsonb (instruments) field if it contains object with id equal to filter.instrumentId
query.whereRaw(
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
{ instrumentId: filter.instrumentFilter?.instrumentId }
} else {
const effectiveInstrumentIds = resolveInstrumentIds(
filter?.instrumentFilter
);
if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
// NOTE: Using jsonpath we check the jsonb (instruments) field if it contains object with id equal to filter.instrumentId
query.where(function () {
effectiveInstrumentIds.forEach((id) => {
this.orWhereRaw(
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
{ instrumentId: id }
);
});
});
}
}

if (filter?.proposalStatusId) {
Expand Down Expand Up @@ -1108,14 +1153,15 @@ export default class PostgresProposalDataSource implements ProposalDataSource {
'ins.instrument_id'
)
.modify((query) => {
const instrumentId = filter?.instrumentFilter?.instrumentId;
const effectiveInstrumentIds = resolveInstrumentIds(
filter?.instrumentFilter
);

if (instrumentId && !isNaN(instrumentId)) {
if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
query.join('instrument_has_proposals as ihp', function () {
this.on('ihp.proposal_pk', '=', 'proposals.proposal_pk').andOnVal(
this.on('ihp.proposal_pk', '=', 'proposals.proposal_pk').andOnIn(
'ihp.instrument_id',
'=',
instrumentId
effectiveInstrumentIds
);
});
}
Expand Down
27 changes: 20 additions & 7 deletions apps/backend/src/datasources/stfc/StfcProposalDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ import { UserWithRole } from '../../models/User';
import { ProposalViewTechnicalReview } from '../../resolvers/types/ProposalView';
import { removeDuplicates } from '../../utils/helperFunctions';
import { PaginationSortDirection } from '../../utils/pagination';
import { CallDataSource } from '../CallDataSource';
import { StfcUserDataSource } from './StfcUserDataSource';
import PostgresAdminDataSource from '../postgres/AdminDataSource';
import PostgresCallDataSource from '../postgres/CallDataSource';
import database from '../postgres/database';
import PostgresProposalDataSource, {
resolveInstrumentIds,
} from '../postgres/ProposalDataSource';
import {
CallRecord,
createCallObject,
Expand All @@ -24,8 +29,6 @@ import PostgresTagDataSource from '../postgres/TagDataSource';
import PostgresUserDataSource from '../postgres/UserDataSource';
import PostgresWorkflowDataSource from '../postgres/WorkflowDataSource';
import { ProposalsFilter } from './../../resolvers/queries/ProposalsQuery';
import PostgresProposalDataSource from './../postgres/ProposalDataSource';
import { StfcUserDataSource } from './StfcUserDataSource';

const postgresProposalDataSource = new PostgresProposalDataSource(
new PostgresWorkflowDataSource(new PostgresStatusDataSource()),
Expand Down Expand Up @@ -163,12 +166,22 @@ export default class StfcProposalDataSource extends PostgresProposalDataSource {
}
if (filter?.instrumentFilter?.showMultiInstrumentProposals) {
query.whereRaw('jsonb_array_length(instruments) > 1');
} else if (filter?.instrumentFilter?.instrumentId) {
// NOTE: Using jsonpath we check the jsonb (instruments) field if it contains object with id equal to filter.instrumentId
query.whereRaw(
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
{ instrumentId: filter?.instrumentFilter?.instrumentId }
} else {
const effectiveInstrumentIds = resolveInstrumentIds(
filter?.instrumentFilter
);

if (effectiveInstrumentIds && effectiveInstrumentIds.length > 0) {
// NOTE: Using jsonpath we check the jsonb (instruments) field if it contains object with id equal to filter.instrumentId
query.where(function () {
effectiveInstrumentIds.forEach((id) => {
this.orWhereRaw(
'jsonb_path_exists(instruments, \'$[*].id \\? (@.type() == "number" && @ == :instrumentId:)\')',
{ instrumentId: id }
);
});
});
}
}

if (filter?.proposalStatusId) {
Expand Down
11 changes: 9 additions & 2 deletions apps/backend/src/resolvers/queries/ProposalsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,15 @@ export class QuestionFilterInput {

@InputType()
export class InstrumentFilterInput {
@Field(() => Int, { nullable: true })
public instrumentId: number;
/** @deprecated Use instrumentIds instead */
@Field(() => Int, {
nullable: true,
deprecationReason: 'Use instrumentIds instead',
})
public instrumentId?: number;

@Field(() => [Int], { nullable: true })
public instrumentIds?: number[];

@Field(() => Boolean)
public showMultiInstrumentProposals: boolean;
Expand Down
1 change: 1 addition & 0 deletions apps/e2e/cypress/e2e/FAPs.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ context('Fap reviews tests', () => {

cy.get('[data-cy=instrument-filter]').click();
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(
Expand Down
Loading
Loading