Skip to content

Commit

Permalink
Merge pull request #194 from CDLUC3/feature/JS-updates-for-edit-question
Browse files Browse the repository at this point in the history
Updates required for the CDLUC3/dmsp_frontend_prototype#188 frontend Edit Question ticket
  • Loading branch information
jupiter007 authored Feb 7, 2025
2 parents 9ef9b8a + 8e049c4 commit 424b4d8
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,5 @@ CREATE TABLE `questionOptions` (
`createdById` int,
`modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`modifiedById` int,
CONSTRAINT unique_question_option_text UNIQUE (`questionId`, `text`),
CONSTRAINT unique_question_option_orderNumber UNIQUE (`questionId`, `orderNumber`),
FOREIGN KEY (questionId) REFERENCES questions(id) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3;
25 changes: 22 additions & 3 deletions src/resolvers/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MyContext } from "../context";
import { QuestionOption } from "../models/QuestionOption";
import { Question } from "../models/Question";
import { Section } from "../models/Section";
import { hasPermissionOnQuestion } from "../services/questionService";
import { getQuestionOptionsToRemove, hasPermissionOnQuestion } from "../services/questionService";
import { BadUserInputError, ForbiddenError, NotFoundError } from "../utils/graphQLErrors";
import { QuestionCondition } from "../models/QuestionCondition";

Expand Down Expand Up @@ -140,15 +140,33 @@ export const resolvers: Resolvers = {
existingQuestionOptions.map(option => [option.id, option])
);


// Get list of options that need to be removed
const optionsToRemove = await getQuestionOptionsToRemove(questionOptions as QuestionOption[], context, questionId);

// Remove question options that are no longer in the updated questionOptions array
if (optionsToRemove.length > 0) {
await Promise.all(
optionsToRemove.map(async (option) => {
const questionOption = new QuestionOption({
questionId: option.questionId,
id: option.id
});

await questionOption.delete(context);
})
);
}

// Separate incoming options into "to update" and "to create"
const optionsToUpdate = [];
const optionsToCreate = [];

questionOptions.forEach(option => {
if (existingOptionsMap.has(option.questionOptionId)) {
if (existingOptionsMap.has(option.id)) {
// Add to update list, merging the new data with the existing data
optionsToUpdate.push({
...existingOptionsMap.get(option.questionOptionId), // existing option data
...existingOptionsMap.get(option.id), // existing option data
...option // updated fields from input
});
} else {
Expand Down Expand Up @@ -180,6 +198,7 @@ export const resolvers: Resolvers = {
);
}


// Return newly updated question
return await Question.findById('updateQuestion resolver', context, updatedQuestion.id);
}
Expand Down
27 changes: 5 additions & 22 deletions src/resolvers/questionOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export const resolvers: Resolvers = {
questionOptions: async (_, { questionId }, context: MyContext): Promise<QuestionOption[]> => {
return await QuestionOption.findByQuestionId('questionOption resolver', context, questionId);
},
questionOption: async (_, { questionOptionId }, context: MyContext): Promise<QuestionOption> => {
return await QuestionOption.findByQuestionOptionId('questionOption resolver', context, questionOptionId);
questionOption: async (_, { id }, context: MyContext): Promise<QuestionOption> => {
return await QuestionOption.findByQuestionOptionId('questionOption resolver', context, id);
},
},
Mutation: {
Expand Down Expand Up @@ -42,21 +42,21 @@ export const resolvers: Resolvers = {
}
},
updateQuestionOption: async (_, { input: {
questionOptionId,
id,
text,
orderNumber,
isDefault } }, context: MyContext): Promise<QuestionOption> => {

// Get QuestionOption based on provided questionOptionId
const questionOptionData = await QuestionOption.findByQuestionOptionId('questionOption resolver', context, questionOptionId);
const questionOptionData = await QuestionOption.findByQuestionOptionId('questionOption resolver', context, id);

// Throw Not Found error if QuestionOption data is not found
if (!questionOptionData) {
throw NotFoundError('QuestionOption not found')
}

const questionOption = new QuestionOption({
id: questionOptionId,
id: questionOptionData.id,
questionId: questionOptionData.questionId,
text: text || questionOptionData.text,
orderNumber: orderNumber || questionOptionData.orderNumber,
Expand All @@ -74,22 +74,5 @@ export const resolvers: Resolvers = {
return await QuestionOption.findByQuestionOptionId('updateQuestion resolver', context, updatedQuestionOption.id);
}
},
removeQuestionOption: async (_, { questionOptionId }, context: MyContext): Promise<QuestionOption> => {
// Retrieve existing questionOption
const questionOptionData = await QuestionOption.findByQuestionOptionId('removeQuestion resolver', context, questionOptionId);

// Throw Not Found error if QuestionOption is not found
if (!questionOptionData) {
throw NotFoundError('QuestionOption not found')
}

//Need to create a new instance of QuestionOption so that it recognizes the 'delete' function of that instance
const questionOption = new QuestionOption({
...questionOptionData,
id: questionOptionId
});

return await questionOption.delete(context);
},
},
};
10 changes: 6 additions & 4 deletions src/schemas/questionOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export const typeDefs = gql`
extend type Query {
"Get the Question Options that belong to the associated questionId"
questionOptions(questionId: Int!): [QuestionOption]
"Get the specific Question Option based on questionOptionId"
questionOption(questionOptionId: Int!): QuestionOption
"Get the specific Question Option based on question option id"
questionOption(id: Int!): QuestionOption
}
extend type Mutation {
Expand All @@ -14,7 +14,7 @@ extend type Mutation {
"Update a QuestionOption"
updateQuestionOption(input: UpdateQuestionOptionInput!): QuestionOption!
"Delete a QuestionOption"
removeQuestionOption(questionOptionId: Int!): QuestionOption
removeQuestionOption(id: Int!): QuestionOption
}
"QuestionOption always belongs to a Question"
Expand Down Expand Up @@ -66,12 +66,14 @@ input AddQuestionOptionInput {
input UpdateQuestionOptionInput {
"The id of the QuestionOption"
questionOptionId: Int
id: Int
"The option text"
text: String!
"The option order number"
orderNumber: Int!
"Whether the option is the default selected one"
isDefault: Boolean
"id of parent question"
questionId: Int
}
`
18 changes: 18 additions & 0 deletions src/services/questionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Question } from "../models/Question";
import { VersionedQuestion } from "../models/VersionedQuestion";
import { NotFoundError } from "../utils/graphQLErrors";
import { QuestionCondition } from "../models/QuestionCondition";
import { QuestionOption } from "../models/QuestionOption";
import { VersionedQuestionCondition } from "../models/VersionedQuestionCondition";
import { formatLogMessage } from "../logger";

Expand Down Expand Up @@ -168,3 +169,20 @@ export const generateQuestionConditionVersion = async (
throw new Error(msg);
}
}


export const getQuestionOptionsToRemove = async (questionOptions: QuestionOption[], context: MyContext, questionId: number): Promise<QuestionOption[]> => {
//Get all the existing question options associated with this question
const existingOptions = await QuestionOption.findByQuestionId('questionService', context, questionId);

// Create a Set of question option ids
const questionOptionIds = new Set(
questionOptions.map(option => option.id)
);

// Get options that exist in questionOptions table, but are not included in updated questionOptions
const optionsToRemove = existingOptions.filter(existing => !questionOptionIds.has(existing.id))

return Array.isArray(optionsToRemove) ? optionsToRemove : [];
}

16 changes: 9 additions & 7 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,7 @@ export type MutationRemoveQuestionConditionArgs = {


export type MutationRemoveQuestionOptionArgs = {
questionOptionId: Scalars['Int']['input'];
id: Scalars['Int']['input'];
};


Expand Down Expand Up @@ -1598,7 +1598,7 @@ export type Query = {
question?: Maybe<Question>;
/** Get the QuestionConditions that belong to a specific question */
questionConditions?: Maybe<Array<Maybe<QuestionCondition>>>;
/** Get the specific Question Option based on questionOptionId */
/** Get the specific Question Option based on question option id */
questionOption?: Maybe<QuestionOption>;
/** Get the Question Options that belong to the associated questionId */
questionOptions?: Maybe<Array<Maybe<QuestionOption>>>;
Expand Down Expand Up @@ -1816,7 +1816,7 @@ export type QueryQuestionConditionsArgs = {


export type QueryQuestionOptionArgs = {
questionOptionId: Scalars['Int']['input'];
id: Scalars['Int']['input'];
};


Expand Down Expand Up @@ -2356,12 +2356,14 @@ export type UpdateQuestionInput = {
};

export type UpdateQuestionOptionInput = {
/** The id of the QuestionOption */
id?: InputMaybe<Scalars['Int']['input']>;
/** Whether the option is the default selected one */
isDefault?: InputMaybe<Scalars['Boolean']['input']>;
/** The option order number */
orderNumber: Scalars['Int']['input'];
/** The id of the QuestionOption */
questionOptionId?: InputMaybe<Scalars['Int']['input']>;
/** id of parent question */
questionId?: InputMaybe<Scalars['Int']['input']>;
/** The option text */
text: Scalars['String']['input'];
};
Expand Down Expand Up @@ -3166,7 +3168,7 @@ export type MutationResolvers<ContextType = MyContext, ParentType extends Resolv
removeProjectOutputFromPlan?: Resolver<Maybe<ResolversTypes['ProjectOutput']>, ParentType, ContextType, RequireFields<MutationRemoveProjectOutputFromPlanArgs, 'planId' | 'projectOutputId'>>;
removeQuestion?: Resolver<Maybe<ResolversTypes['Question']>, ParentType, ContextType, RequireFields<MutationRemoveQuestionArgs, 'questionId'>>;
removeQuestionCondition?: Resolver<Maybe<ResolversTypes['QuestionCondition']>, ParentType, ContextType, RequireFields<MutationRemoveQuestionConditionArgs, 'questionConditionId'>>;
removeQuestionOption?: Resolver<Maybe<ResolversTypes['QuestionOption']>, ParentType, ContextType, RequireFields<MutationRemoveQuestionOptionArgs, 'questionOptionId'>>;
removeQuestionOption?: Resolver<Maybe<ResolversTypes['QuestionOption']>, ParentType, ContextType, RequireFields<MutationRemoveQuestionOptionArgs, 'id'>>;
removeRepository?: Resolver<Maybe<ResolversTypes['Repository']>, ParentType, ContextType, RequireFields<MutationRemoveRepositoryArgs, 'repositoryId'>>;
removeSection?: Resolver<ResolversTypes['Section'], ParentType, ContextType, RequireFields<MutationRemoveSectionArgs, 'sectionId'>>;
removeTag?: Resolver<Maybe<ResolversTypes['Tag']>, ParentType, ContextType, RequireFields<MutationRemoveTagArgs, 'tagId'>>;
Expand Down Expand Up @@ -3416,7 +3418,7 @@ export type QueryResolvers<ContextType = MyContext, ParentType extends Resolvers
publishedTemplates?: Resolver<Maybe<Array<Maybe<ResolversTypes['VersionedTemplate']>>>, ParentType, ContextType, Partial<QueryPublishedTemplatesArgs>>;
question?: Resolver<Maybe<ResolversTypes['Question']>, ParentType, ContextType, RequireFields<QueryQuestionArgs, 'questionId'>>;
questionConditions?: Resolver<Maybe<Array<Maybe<ResolversTypes['QuestionCondition']>>>, ParentType, ContextType, RequireFields<QueryQuestionConditionsArgs, 'questionId'>>;
questionOption?: Resolver<Maybe<ResolversTypes['QuestionOption']>, ParentType, ContextType, RequireFields<QueryQuestionOptionArgs, 'questionOptionId'>>;
questionOption?: Resolver<Maybe<ResolversTypes['QuestionOption']>, ParentType, ContextType, RequireFields<QueryQuestionOptionArgs, 'id'>>;
questionOptions?: Resolver<Maybe<Array<Maybe<ResolversTypes['QuestionOption']>>>, ParentType, ContextType, RequireFields<QueryQuestionOptionsArgs, 'questionId'>>;
questionTypes?: Resolver<Maybe<Array<Maybe<ResolversTypes['QuestionType']>>>, ParentType, ContextType>;
questions?: Resolver<Maybe<Array<Maybe<ResolversTypes['Question']>>>, ParentType, ContextType, RequireFields<QueryQuestionsArgs, 'sectionId'>>;
Expand Down

0 comments on commit 424b4d8

Please sign in to comment.