Skip to content

Commit

Permalink
feat: Instrument assignment implemented via Event Bus.
Browse files Browse the repository at this point in the history
  • Loading branch information
yoganandaness committed Jun 9, 2023
1 parent c0d283a commit ca5d5be
Show file tree
Hide file tree
Showing 12 changed files with 138 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface ProposalDataSource {
): Promise<{ totalCount: number; proposalViews: ProposalView[] }>;
// Read
get(primaryKey: number): Promise<Proposal | null>;

getByQuestionaryid(questionaryId: number): Promise<Proposal | null>;
getProposals(
filter?: ProposalsFilter,
first?: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
Questionary,
QuestionaryStep,
} from '../models/Questionary';
import { Template } from '../models/Template';
import { DataType, Question, Template } from '../models/Template';

export interface QuestionaryDataSource {
getCount(templateId: number): Promise<number>;
Expand All @@ -18,6 +18,10 @@ export interface QuestionaryDataSource {
getBlankQuestionarySteps(templateId: number): Promise<QuestionaryStep[]>;
getBlankQuestionaryStepsByCallId(callId: number): Promise<QuestionaryStep[]>;
getAnswers(questionId: string): Promise<AnswerBasic[]>;
getLatestAnswerByQuestionaryIdAndDataType(
questionaryId: number,
dataType: DataType
): Promise<{ answer: AnswerBasic; question: Question } | null>;
getTemplates(questionId: string): Promise<Template[]>;
getIsCompleted(questionaryId: number): Promise<boolean>;
updateAnswer(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,14 @@ export class ProposalDataSourceMock implements ProposalDataSource {
return allProposals.find((proposal) => proposal.primaryKey === id) || null;
}

async getByQuestionaryid(qestionaryid: number) {
return (
allProposals.find(
(proposal) => proposal.questionaryId === qestionaryid
) || null
);
}

async create(proposerId: number, callId: number, questionaryId: number) {
const newProposal = dummyProposalFactory({
proposerId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,14 @@ export class QuestionaryDataSourceMock implements QuestionaryDataSource {
async getAnswers(questionId: string): Promise<AnswerBasic[]> {
return [];
}

async getLatestAnswerByQuestionaryIdAndDataType(
questionaryId: number,
dataType: DataType
): Promise<{ answer: AnswerBasic; question: Question } | null> {
return null;
}

async getTemplates(questionId: string): Promise<Template[]> {
return [];
}
Expand All @@ -411,6 +419,12 @@ export class QuestionaryDataSourceMock implements QuestionaryDataSource {
return dummyQuestionarySteps;
}

async getBlankQuestionaryStepsByCallId(
_templateId: number
): Promise<QuestionaryStep[]> {
return dummyQuestionarySteps;
}

async delete(questionaryId: number): Promise<Questionary> {
return createDummyQuestionary({ questionaryId });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,17 @@ export default class PostgresProposalDataSource implements ProposalDataSource {
});
}

async getByQuestionaryid(questionaryId: number): Promise<Proposal | null> {
return database
.select()
.from('proposals')
.where('questionary_id', questionaryId)
.first()
.then((proposal: ProposalRecord) => {
return proposal ? createProposalObject(proposal) : null;
});
}

async create(
proposer_id: number,
call_id: number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import {
DataType,
FieldDependency,
Question,
Template,
Topic,
} from '../../models/Template';
Expand All @@ -36,6 +37,7 @@ import {
ProposalRecord,
createProposalObject,
InstrumentWithAvailabilityTimeRecord,
createQuestionObject,
} from './records';

type AnswerRecord<T extends DataType> = QuestionRecord &
Expand Down Expand Up @@ -128,6 +130,34 @@ export default class PostgresQuestionaryDataSource
return rows.map((row) => createAnswerBasic(row));
});
}

async getLatestAnswerByQuestionaryIdAndDataType(
questionaryId: number,
dataType: DataType
): Promise<{ answer: AnswerBasic; question: Question } | null> {
return database('answers')
.leftJoin(
'questions',
'questions.question_id',
'=',
'answers.question_id'
)
.where('answers.questionary_id', questionaryId)
.where('questions.data_type', dataType)
.orderBy('answers.created_at', 'desc')
.limit(1)
.then((rows) => {
if (rows.length == 0) {
return null;
}

return {
answer: createAnswerBasic(rows[0]),
question: createQuestionObject(rows[0]),
};
});
}

async getTemplates(questionId: string): Promise<Template[]> {
return database('templates_has_questions')
.leftJoin(
Expand Down
2 changes: 2 additions & 0 deletions apps/user-office-backend/src/eventHandlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import createCustomHandler from './customHandler';
import createLoggingHandler from './logging';
import { createPostToQueueHandler } from './messageBroker';
import createProposalWorkflowHandler from './proposalWorkflow';
import createInstrumentPickerHandler from './questionary/instrumentPicker';

export default function createEventHandlers() {
const emailHandler = container.resolve<
Expand All @@ -18,5 +19,6 @@ export default function createEventHandlers() {
createPostToQueueHandler(),
createProposalWorkflowHandler(),
createCustomHandler(),
createInstrumentPickerHandler(),
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { container } from 'tsyringe';

import { Tokens } from '../../config/Tokens';
import { InstrumentDataSource } from '../../datasources/InstrumentDataSource';
import { ProposalDataSource } from '../../datasources/ProposalDataSource';
import { QuestionaryDataSource } from '../../datasources/QuestionaryDataSource';
import { ApplicationEvent } from '../../events/applicationEvents';
import { Event } from '../../events/event.enum';
import { DataType } from '../../models/Template';

export default function createHandler() {
const questionaryDataSource = container.resolve<QuestionaryDataSource>(
Tokens.QuestionaryDataSource
);

const instrumentDataSource = container.resolve<InstrumentDataSource>(
Tokens.InstrumentDataSource
);

const proposalDataSource = container.resolve<ProposalDataSource>(
Tokens.ProposalDataSource
);

return async function proposalWorkflowHandler(event: ApplicationEvent) {
if (event.isRejection) {
return;
}

switch (event.type) {
case Event.TOPIC_ANSWERED: {
const {
questionarystep: { questionaryId },
} = event;

const instrumentPickerAnswer =

This comment has been minimized.

Copy link
@enthusiastio

enthusiastio Jun 12, 2023

Contributor

This assumes there is only one question about the instrument. Could there ever be two?

await questionaryDataSource.getLatestAnswerByQuestionaryIdAndDataType(
questionaryId,
DataType.INSTRUMENT_PICKER
);

const instrumentId = instrumentPickerAnswer?.answer.answer.value;
if (!instrumentId)
throw new Error(`Invalid Instrument id ${instrumentId}`);

const instrument = await instrumentDataSource.getInstrument(
instrumentId
);

if (!instrument)
throw new Error(`Instrument with id ${instrumentId} not found`);

const proposal = await proposalDataSource.getByQuestionaryid(
questionaryId
);
if (!proposal)
throw new Error(
`Proposal with questionary id ${questionaryId} not found`
);

await instrumentDataSource.assignProposalsToInstrument(
[proposal.primaryKey],
instrumentId
);
}
}
};
}
13 changes: 0 additions & 13 deletions apps/user-office-backend/src/models/ProposalModelFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
import { Answer, QuestionaryStep } from './Questionary';
import { getQuestionDefinition } from './questionTypes/QuestionRegistry';
import {
DataType,
FieldDependency,
QuestionTemplateRelation,
TemplateStep,
Expand Down Expand Up @@ -125,15 +124,3 @@ export function transformAnswerValueIfNeeded(

return definition.transform(questionTemplateRelation, value);
}

export async function proposalAnswerAfterSave(
dataType: DataType,
questionaryId: number,
value: any
) {
const definition = getQuestionDefinition(dataType);

if (definition.afterSave) {
await definition.afterSave(questionaryId, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,10 @@
import { logger } from '@user-office-software/duo-logger';
import { GraphQLError } from 'graphql';

import database from '../../datasources/postgres/database';
import {
InstrumentRecord,
ProposalRecord,
createInstrumentObject,
createProposalObject,
} from '../../datasources/postgres/records';
import { InstrumentPickerConfig } from '../../resolvers/types/FieldConfig';
import { QuestionFilterCompareOperator } from '../Questionary';
import { DataType, QuestionTemplateRelation } from '../Template';
import { Question } from './QuestionRegistry';

export class InstrumentOptionClass {
constructor(public id: number, public name: string) {}
}
Expand Down Expand Up @@ -74,51 +66,4 @@ export const instrumentPickerDefinition: Question<DataType.INSTRUMENT_PICKER> =

return fallBackConfig;
},
afterSave: async (questionaryId, value) => {
// Get Proposal
const proposal = await database
.select()
.from('proposals')
.where('questionary_id', questionaryId)
.first()
.then((proposal: ProposalRecord | null) =>
proposal ? createProposalObject(proposal) : null
);
// Get Instrument
const instrument = await database
.select()
.from('instruments')
.where('instrument_id', value)
.first()
.then((instrument: InstrumentRecord | null) =>
instrument ? createInstrumentObject(instrument) : null
);
if (!instrument || !proposal) return;
await database.transaction(async (trx) => {
try {
/**
* NOTE: First delete all connections that should be changed,
* because currently we only support one proposal to be assigned on one instrument.
* So we don't end up in a situation that one proposal is assigned to multiple instruments
* which is not supported scenario by the frontend because it only shows one instrument per proposal.
*/
await database('instrument_has_proposals')
.del()
.where('proposal_pk', proposal.primaryKey)
.transacting(trx);

const result = await database('instrument_has_proposals')
.insert({
instrument_id: value,
proposal_pk: proposal.primaryKey,
})
.returning(['*'])
.transacting(trx);

await trx.commit(result);
} catch (error) {
logger.logException('Could not assign Instrument to Proposal', error);
}
});
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,6 @@ export interface Question<T extends DataType> {
helpers: QuestionDataTypeHelpersMapping<T>,
callId?: number
) => Promise<QuestionDataTypeConfigMapping<T>>;

/**
* Function to execute after the Question has been answered. Ex., Attach an instrument to a proposal, when the Instrument Proposal Question has been answered.
*/
readonly afterSave?: (questionaryId: number, value: any) => any;
}

// Add new component definitions here
Expand Down
11 changes: 0 additions & 11 deletions apps/user-office-backend/src/mutations/QuestionaryMutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { Authorized, EventBus } from '../decorators';
import { Event } from '../events/event.enum';
import {
isMatchingConstraints,
proposalAnswerAfterSave,
transformAnswerValueIfNeeded,
} from '../models/ProposalModelFunctions';
import { rejection } from '../models/Rejection';
Expand Down Expand Up @@ -136,16 +135,6 @@ export default class QuestionaryMutations {
answer.questionId,
answer.value
);

/**
* After Effect hook for an Answer save. Any operation that needs to be done, when a specific Question has been answered will be executed here
* Note: Questionary Component Definition that implements the function afterSave, will only be executed.
*/
await proposalAnswerAfterSave(
questionTemplateRelation.question.dataType,
questionaryId,
value
);
}
}
if (!isPartialSave) {
Expand Down

0 comments on commit ca5d5be

Please sign in to comment.