diff --git a/apps/backend/src/datasources/mockups/QuestionaryDataSource.ts b/apps/backend/src/datasources/mockups/QuestionaryDataSource.ts index 5bfd4895d3..53ef8bae35 100644 --- a/apps/backend/src/datasources/mockups/QuestionaryDataSource.ts +++ b/apps/backend/src/datasources/mockups/QuestionaryDataSource.ts @@ -1,3 +1,4 @@ +import { DeepPartial } from './ProposalDataSource'; import { DependenciesLogicOperator, EvaluatorOperator, @@ -29,7 +30,6 @@ import { TextInputConfig, } from '../../resolvers/types/FieldConfig'; import { QuestionaryDataSource } from '../QuestionaryDataSource'; -import { DeepPartial } from './ProposalDataSource'; export let dummyQuestionarySteps: QuestionaryStep[]; export let dummyQuestionary: Questionary; @@ -74,6 +74,7 @@ export const dummyQuestionTemplateRelationFactory = ( dummyQuestionFactory(values?.question), values?.sortOrder || Math.round(Math.random() * 100), values?.topicId || Math.round(Math.random() * 10), + values?.templateId || Math.round(Math.random() * 100), values?.config || { ...new BooleanConfig(), readPermissions: [] }, values?.dependencies as FieldDependency[], values?.dependenciesOperator as DependenciesLogicOperator diff --git a/apps/backend/src/datasources/postgres/records.ts b/apps/backend/src/datasources/postgres/records.ts index e7be025f9d..b14d371ac4 100644 --- a/apps/backend/src/datasources/postgres/records.ts +++ b/apps/backend/src/datasources/postgres/records.ts @@ -931,6 +931,7 @@ export const createQuestionTemplateRelationObject = async ( ), record.topic_id, record.sort_order, + record.template_id, createConfig(record.data_type as DataType, transformedConfig), dependencies, record.dependencies_operator diff --git a/apps/backend/src/models/Questionary.ts b/apps/backend/src/models/Questionary.ts index 6a8130a5ce..620ee1a1f3 100644 --- a/apps/backend/src/models/Questionary.ts +++ b/apps/backend/src/models/Questionary.ts @@ -20,6 +20,7 @@ export class Answer extends QuestionTemplateRelation { questionTemplateRelation.question, questionTemplateRelation.topicId, questionTemplateRelation.sortOrder, + questionTemplateRelation.templateId, questionTemplateRelation.config, questionTemplateRelation.dependencies, questionTemplateRelation.dependenciesOperator diff --git a/apps/backend/src/models/Template.ts b/apps/backend/src/models/Template.ts index 4127fb0742..ead41414ba 100644 --- a/apps/backend/src/models/Template.ts +++ b/apps/backend/src/models/Template.ts @@ -80,6 +80,7 @@ export class QuestionTemplateRelation { public question: Question, public topicId: number, public sortOrder: number, + public templateId: number, public config: typeof FieldConfigType, public dependencies: FieldDependency[], public dependenciesOperator?: DependenciesLogicOperator diff --git a/apps/backend/src/queries/TemplateQueries.spec.ts b/apps/backend/src/queries/TemplateQueries.spec.ts index 42cd2afc0a..fb96f4b931 100644 --- a/apps/backend/src/queries/TemplateQueries.spec.ts +++ b/apps/backend/src/queries/TemplateQueries.spec.ts @@ -152,4 +152,37 @@ describe('getDynamicMultipleChoiceOptions', () => { expect(options).toEqual(['option1', 'option2']); }); + + it('should use the question template relation if a template ID is supplied', async () => { + const templateDataSource = container.resolve( + Tokens.TemplateDataSource + ); + const getQuestionSpy = jest.spyOn(templateDataSource, 'getQuestion'); + const getQuestionTemplateRelationSpy = jest.spyOn( + templateDataSource, + 'getQuestionTemplateRelation' + ); + jest.spyOn(global, 'fetch').mockImplementation(() => + Promise.resolve({ + json: () => Promise.resolve(['option1', 'option2']), + ok: true, + } as Response) + ); + + const options = await templateQueries.getDynamicMultipleChoiceOptions( + dummyUserWithRole, + 'dmcQuestionEmptyJsonPath', + 1 + ); + + expect(options).toEqual(['option1', 'option2']); + expect(getQuestionTemplateRelationSpy).toHaveBeenCalledWith( + 'dmcQuestionEmptyJsonPath', + 1 + ); + expect(getQuestionSpy).not.toHaveBeenCalled(); + + getQuestionSpy.mockRestore(); + getQuestionTemplateRelationSpy.mockRestore(); + }); }); diff --git a/apps/backend/src/queries/TemplateQueries.ts b/apps/backend/src/queries/TemplateQueries.ts index a3cd77b33b..b054ee4da5 100644 --- a/apps/backend/src/queries/TemplateQueries.ts +++ b/apps/backend/src/queries/TemplateQueries.ts @@ -94,9 +94,13 @@ export default class TemplateQueries { @Authorized() async getDynamicMultipleChoiceOptions( user: UserWithRole | null, - questionId: string + questionId: string, + templateId?: number | null ) { - const question = await this.dataSource.getQuestion(questionId); + const question = await this.getDynamicMultipleChoiceQuestion( + questionId, + templateId + ); if (!question) return []; const config = question.config as DynamicMultipleChoiceConfig; @@ -139,4 +143,26 @@ export default class TemplateQueries { return []; } + + private async getDynamicMultipleChoiceQuestion( + questionId: string, + templateId?: number | null + ): Promise { + if (templateId !== null && templateId !== undefined) { + const questionTemplateRelation = + await this.dataSource.getQuestionTemplateRelation( + questionId, + templateId + ); + + if (!questionTemplateRelation) return null; + + return { + ...questionTemplateRelation.question, + config: questionTemplateRelation.config, + }; + } + + return this.dataSource.getQuestion(questionId); + } } diff --git a/apps/backend/src/resolvers/queries/DynamicMultipleChoiceQuery.ts b/apps/backend/src/resolvers/queries/DynamicMultipleChoiceQuery.ts index 1da558a777..00f8409acd 100644 --- a/apps/backend/src/resolvers/queries/DynamicMultipleChoiceQuery.ts +++ b/apps/backend/src/resolvers/queries/DynamicMultipleChoiceQuery.ts @@ -1,4 +1,4 @@ -import { Query, Ctx, Resolver, Arg } from 'type-graphql'; +import { Query, Ctx, Resolver, Arg, Int } from 'type-graphql'; import { ResolverContext } from '../../context'; @@ -7,11 +7,14 @@ export class DynamicMultipleChoiceQuery { @Query(() => [String], { nullable: true }) getDynamicMultipleChoiceOptions( @Arg('questionId', () => String) questionId: string, + @Arg('templateId', () => Int, { nullable: true }) + templateId: number | null, @Ctx() context: ResolverContext ) { return context.queries.template.getDynamicMultipleChoiceOptions( context.user, - questionId + questionId, + templateId ); } } diff --git a/apps/backend/src/resolvers/types/QuestionTemplateRelation.ts b/apps/backend/src/resolvers/types/QuestionTemplateRelation.ts index 4b4945a151..46cc2b24ad 100644 --- a/apps/backend/src/resolvers/types/QuestionTemplateRelation.ts +++ b/apps/backend/src/resolvers/types/QuestionTemplateRelation.ts @@ -16,6 +16,9 @@ export class QuestionTemplateRelation @Field(() => Int) public sortOrder: number; + @Field(() => Int) + public templateId: number; + @Field(() => Int) public topicId: number; diff --git a/apps/e2e/cypress/fixtures/template_export.json b/apps/e2e/cypress/fixtures/template_export.json index 51f43d8e65..3d1957f07f 100644 --- a/apps/e2e/cypress/fixtures/template_export.json +++ b/apps/e2e/cypress/fixtures/template_export.json @@ -37,6 +37,7 @@ }, "topicId": 1, "sortOrder": 0, + "templateId": 1, "config": { "tooltip": "", "required": false, @@ -78,6 +79,7 @@ }, "topicId": 8, "sortOrder": 0, + "templateId": 1, "config": { "addEntryButtonLabel": "Add", "templateCategory": "SAMPLE_DECLARATION", @@ -129,6 +131,7 @@ }, "topicId": 8, "sortOrder": 2, + "templateId": 1, "config": { "small_label": "", "required": false, @@ -175,6 +178,7 @@ }, "topicId": 8, "sortOrder": 3, + "templateId": 1, "config": { "small_label": "", "required": false, @@ -209,6 +213,7 @@ }, "topicId": 8, "sortOrder": 4, + "templateId": 1, "config": { "small_label": "", "required": false, @@ -244,6 +249,7 @@ }, "topicId": 8, "sortOrder": 5, + "templateId": 1, "config": { "small_label": "", "required": false, @@ -277,6 +283,7 @@ }, "topicId": 8, "sortOrder": 6, + "templateId": 1, "config": { "small_label": "", "required": false, @@ -316,6 +323,7 @@ }, "topicId": 8, "sortOrder": 7, + "templateId": 1, "config": { "required": false, "small_label": "", @@ -350,6 +358,7 @@ }, "topicId": 8, "sortOrder": 8, + "templateId": 1, "config": { "required": false, "small_label": "", @@ -385,6 +394,7 @@ }, "topicId": 8, "sortOrder": 8, + "templateId": 1, "config": { "small_label": "", "required": false, @@ -421,6 +431,7 @@ }, "topicId": 8, "sortOrder": 9, + "templateId": 1, "config": { "html": "", "plain": "", @@ -454,6 +465,7 @@ }, "topicId": 8, "sortOrder": 10, + "templateId": 1, "config": { "required": false, "small_label": "", @@ -492,6 +504,7 @@ }, "topicId": 8, "sortOrder": 11, + "templateId": 1, "config": { "small_label": "", "required": false, @@ -532,6 +545,7 @@ }, "topicId": 8, "sortOrder": 12, + "templateId": 1, "config": { "small_label": "", "required": false, @@ -854,6 +868,7 @@ }, "topicId": 7, "sortOrder": 0, + "templateId": 7, "config": { "titlePlaceholder": "Title", "tooltip": "", @@ -880,6 +895,7 @@ }, "topicId": 7, "sortOrder": 3, + "templateId": 7, "config": { "small_label": "", "required": false, @@ -906,6 +922,7 @@ }, "topicId": 7, "sortOrder": 4, + "templateId": 7, "config": { "small_label": "", "required": false, @@ -940,6 +957,7 @@ }, "topicId": 7, "sortOrder": 5, + "templateId": 7, "config": { "small_label": "", "required": false, @@ -982,6 +1000,7 @@ }, "topicId": 7, "sortOrder": 6, + "templateId": 7, "config": { "small_label": "", "required": false, @@ -1037,6 +1056,7 @@ }, "topicId": 7, "sortOrder": 7, + "templateId": 7, "config": { "small_label": "", "required": false, @@ -1085,6 +1105,7 @@ }, "topicId": 7, "sortOrder": 8, + "templateId": 7, "config": { "required": false, "small_label": "", @@ -1119,6 +1140,7 @@ }, "topicId": 7, "sortOrder": 9, + "templateId": 7, "config": { "small_label": "", "required": false, @@ -1156,6 +1178,7 @@ }, "topicId": 7, "sortOrder": 10, + "templateId": 7, "config": { "required": false, "small_label": "", @@ -1193,6 +1216,7 @@ }, "topicId": 7, "sortOrder": 11, + "templateId": 7, "config": { "required": false, "small_label": "", diff --git a/apps/frontend/src/components/common/proposalFilters/QuestionaryFilter.tsx b/apps/frontend/src/components/common/proposalFilters/QuestionaryFilter.tsx index 72efdf6735..5ba47236b5 100644 --- a/apps/frontend/src/components/common/proposalFilters/QuestionaryFilter.tsx +++ b/apps/frontend/src/components/common/proposalFilters/QuestionaryFilter.tsx @@ -159,6 +159,7 @@ function QuestionaryFilter({ onSubmit, callId }: QuestionaryFilterProps) { }} questionTemplateRelation={selectedQuestion} callId={callId} + templateId={selectedQuestion.templateId} /> )} diff --git a/apps/frontend/src/components/proposal/SearchCriteriaInputProps.tsx b/apps/frontend/src/components/proposal/SearchCriteriaInputProps.tsx index ef8019dcdd..34267dd7ac 100644 --- a/apps/frontend/src/components/proposal/SearchCriteriaInputProps.tsx +++ b/apps/frontend/src/components/proposal/SearchCriteriaInputProps.tsx @@ -15,4 +15,5 @@ export interface SearchCriteriaInputProps { ) => unknown; questionTemplateRelation: QuestionTemplateRelationFragment; callId?: InputMaybe; + templateId?: number; } diff --git a/apps/frontend/src/components/questionary/questionaryComponents/DynamicMultipleChoice/DynamicMultipleChoiceSearchCriteriaComponent.tsx b/apps/frontend/src/components/questionary/questionaryComponents/DynamicMultipleChoice/DynamicMultipleChoiceSearchCriteriaComponent.tsx index c76e69ba6d..bbb365bc89 100644 --- a/apps/frontend/src/components/questionary/questionaryComponents/DynamicMultipleChoice/DynamicMultipleChoiceSearchCriteriaComponent.tsx +++ b/apps/frontend/src/components/questionary/questionaryComponents/DynamicMultipleChoice/DynamicMultipleChoiceSearchCriteriaComponent.tsx @@ -10,10 +10,12 @@ function DynamicMultipleChoiceSearchCriteriaComponent({ onChange, questionTemplateRelation, searchCriteria, + templateId, }: SearchCriteriaInputProps) { const [value, setValue] = useState(searchCriteria?.value ?? ''); const { options, loadingOptions } = useGetDynamicMultipleChoiceOptions( - questionTemplateRelation.question.id + questionTemplateRelation.question.id, + templateId ); return ( diff --git a/apps/frontend/src/components/questionary/questionaryComponents/DynamicMultipleChoice/QuestionaryComponentDynamicMultipleChoice.tsx b/apps/frontend/src/components/questionary/questionaryComponents/DynamicMultipleChoice/QuestionaryComponentDynamicMultipleChoice.tsx index 0bdea9eb47..2c6c38ca6a 100644 --- a/apps/frontend/src/components/questionary/questionaryComponents/DynamicMultipleChoice/QuestionaryComponentDynamicMultipleChoice.tsx +++ b/apps/frontend/src/components/questionary/questionaryComponents/DynamicMultipleChoice/QuestionaryComponentDynamicMultipleChoice.tsx @@ -8,11 +8,12 @@ import Radio from '@mui/material/Radio'; import RadioGroup from '@mui/material/RadioGroup'; import Select, { SelectChangeEvent } from '@mui/material/Select'; import { getIn } from 'formik'; -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import MultiMenuItem from 'components/common/MultiMenuItem'; import UOLoader from 'components/common/UOLoader'; import { BasicComponentProps } from 'components/proposal/IBasicComponentProps'; +import { QuestionaryContext } from 'components/questionary/QuestionaryContext'; import { DynamicMultipleChoiceConfig } from 'generated/sdk'; import { useGetDynamicMultipleChoiceOptions } from 'hooks/template/useGetDynamicMultipleChoiceOptions'; import { toArray } from 'utils/helperFunctions'; @@ -28,13 +29,17 @@ export function QuestionaryComponentDynamicMultipleChoice( const { question: { id, question, naturalKey }, } = answer; + const { state } = useContext(QuestionaryContext); const config = answer.config as DynamicMultipleChoiceConfig; const fieldError = getIn(errors, id); const isError = getIn(touched, id) && !!fieldError; - const { options, loadingOptions } = useGetDynamicMultipleChoiceOptions(id); + const { options, loadingOptions } = useGetDynamicMultipleChoiceOptions( + id, + state?.questionary.templateId + ); const [stateValue, setStateValue] = useState>([]); useEffect(() => { diff --git a/apps/frontend/src/graphql/questionary/fragment.answer.graphql b/apps/frontend/src/graphql/questionary/fragment.answer.graphql index 7f43811a41..21b274f99f 100644 --- a/apps/frontend/src/graphql/questionary/fragment.answer.graphql +++ b/apps/frontend/src/graphql/questionary/fragment.answer.graphql @@ -4,6 +4,7 @@ fragment answer on Answer { ...question } sortOrder + templateId topicId config { ...fieldConfig diff --git a/apps/frontend/src/graphql/template/fragment.questionRel.graphql b/apps/frontend/src/graphql/template/fragment.questionRel.graphql index 60299dba78..21436fd9ba 100644 --- a/apps/frontend/src/graphql/template/fragment.questionRel.graphql +++ b/apps/frontend/src/graphql/template/fragment.questionRel.graphql @@ -3,6 +3,7 @@ fragment questionTemplateRelation on QuestionTemplateRelation { ...question } sortOrder + templateId topicId config { ...fieldConfig diff --git a/apps/frontend/src/graphql/template/getDynamicMultipleChoiceOptions.graphql b/apps/frontend/src/graphql/template/getDynamicMultipleChoiceOptions.graphql index d5ddbad549..bf2f27622c 100644 --- a/apps/frontend/src/graphql/template/getDynamicMultipleChoiceOptions.graphql +++ b/apps/frontend/src/graphql/template/getDynamicMultipleChoiceOptions.graphql @@ -1,3 +1,6 @@ -query getDynamicMultipleChoiceOptions($questionId: String!) { - options: getDynamicMultipleChoiceOptions(questionId: $questionId) +query getDynamicMultipleChoiceOptions($questionId: String!, $templateId: Int) { + options: getDynamicMultipleChoiceOptions( + questionId: $questionId + templateId: $templateId + ) } diff --git a/apps/frontend/src/hooks/template/useGetDynamicMultipleChoiceOptions.ts b/apps/frontend/src/hooks/template/useGetDynamicMultipleChoiceOptions.ts index d9cdd1e419..7bde074b40 100644 --- a/apps/frontend/src/hooks/template/useGetDynamicMultipleChoiceOptions.ts +++ b/apps/frontend/src/hooks/template/useGetDynamicMultipleChoiceOptions.ts @@ -2,7 +2,10 @@ import { useEffect, useState } from 'react'; import { useDataApi } from 'hooks/common/useDataApi'; -export function useGetDynamicMultipleChoiceOptions(questionId: string) { +export function useGetDynamicMultipleChoiceOptions( + questionId: string, + templateId?: number | null +) { const [options, setOptions] = useState([]); const [loadingOptions, setLoadingOptions] = useState(true); @@ -13,7 +16,7 @@ export function useGetDynamicMultipleChoiceOptions(questionId: string) { setLoadingOptions(true); api() - .getDynamicMultipleChoiceOptions({ questionId }) + .getDynamicMultipleChoiceOptions({ questionId, templateId }) .then(({ options }) => { if (unmounted) { return; @@ -29,7 +32,7 @@ export function useGetDynamicMultipleChoiceOptions(questionId: string) { return () => { unmounted = true; }; - }, [api, questionId]); + }, [api, questionId, templateId]); return { options,