Commit 0ed45206 authored by Martin Juhás's avatar Martin Juhás
Browse files

feat: implement free-form questions

### Additions

* added new `RadioQuestionDetailsType`
* added new `FreeFormQuestionDetailsType`
* added new `QuestionDetails`
* added new `QuestionRelatedMilestonesType`
* added new `AnswerType`
* added new `QuestionnaireInputType`
* added new `QuestionnaireReviewInputType`
* added new `QuestionnaireReviewInput`
* added new `QuestionTypes`
* added field `type: QuestionTypes!` to `QuestionType`
* added field `details: QuestionDetails!` to `QuestionType`
* added field `timestampReviewed: DateTime` to `TeamQuestionnaireStateType`
* added field `reviewedBy: RestrictedUser` to `TeamQuestionnaireStateType`
* added field `relatedMilestones: [QuestionRelatedMilestonesType!]!` to `TeamQuestionnaireStateType`

### Changes

* changed field `choice: Int!` on `AnswerInput` to `value: String!`
* changed field `choice: Int!` on `AnswerType` to `value: String!`
* field `answers` type changed to `[AnswerType!]!` on `QuestionnaireInputType`
* field `answers` type changed to `[AnswerInput!]!` on `QuestionnaireInput`
* removed field `max` from `QuestionType`
* removed field `correct` from `QuestionType`
* removed field `labels` from `QuestionType`
* removed field `controls` from `QuestionType`

Closes #340
parent 9ee0bd2d
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -2,12 +2,14 @@ import graphene
from django.db import models

from exercise.models import TeamQuestionnaireState
from exercise_definition.models import QuestionTypes
from running_exercise.models import LogType
from user.models import User

QuestionnaireStateEnum = graphene.Enum.from_enum(TeamQuestionnaireState.Status)
GroupEnum = graphene.Enum.from_enum(User.AuthGroup)
LogTypeEnum = graphene.Enum.from_enum(LogType)
QuestionTypeEnum = graphene.Enum.from_enum(QuestionTypes)


class ExerciseEventTypeEnum(models.TextChoices):
+83 −8
Original line number Diff line number Diff line
import graphene
from graphene_django import DjangoObjectType

from aai.access import user_from_context
from common_lib.schema.enums import (
    GroupEnum,
    QuestionnaireStateEnum,
    LogTypeEnum,
    QuestionTypeEnum,
)
from exercise.models import (
    Exercise,
@@ -34,6 +36,9 @@ from exercise_definition.models import (
    Question,
    LearningObjective,
    LearningActivity,
    RadioQuestion,
    FreeFormQuestion,
    QuestionTypes,
)
from running_exercise.models import (
    ActionLog,
@@ -222,11 +227,12 @@ class ControlsMapType(graphene.ObjectType):
    control = graphene.Field(ControlType, required=True)


class QuestionType(DjangoObjectType):
class RadioQuestionDetailsType(DjangoObjectType):
    class Meta:
        model = Question
        model = RadioQuestion

    controls = graphene.List(graphene.NonNull(ControlsMapType), required=True)
    definition = graphene.Field(ExerciseDefinitionType, required=True)

    def resolve_correct(self, info):
        user = info.context.user
@@ -252,6 +258,47 @@ class QuestionType(DjangoObjectType):
        ]


class MilestoneType(DjangoObjectType):
    class Meta:
        model = Milestone
        exclude = ["milestone_states", "definition"]


class FreeFormQuestionDetailsType(DjangoObjectType):
    class Meta:
        model = FreeFormQuestion

    related_milestones = graphene.List(
        graphene.NonNull(MilestoneType), required=True
    )
    definition = graphene.Field(ExerciseDefinitionType, required=True)

    def resolve_related_milestones(self, info):
        user = user_from_context(info.context)

        if user.group == User.AuthGroup.TRAINEE:
            return Milestone.objects.none()

        return Milestone.objects.filter(id__in=self.related_milestones)


class QuestionDetails(graphene.Union):
    class Meta:
        types = [
            RadioQuestionDetailsType,
            FreeFormQuestionDetailsType,
        ]


class QuestionType(DjangoObjectType):
    class Meta:
        model = Question
        exclude = ["details_id"]

    type = graphene.Field(QuestionTypeEnum, required=True)
    details = graphene.Field(QuestionDetails, required=True)


class QuestionnaireType(DjangoObjectType):
    class Meta:
        model = Questionnaire
@@ -277,12 +324,6 @@ class ActionLogType(DjangoObjectType):
    type = graphene.Field(LogTypeEnum, required=True)


class MilestoneType(DjangoObjectType):
    class Meta:
        model = Milestone
        exclude = ["milestone_states", "definition"]


class MilestoneStateType(DjangoObjectType):
    class Meta:
        model = MilestoneState
@@ -410,11 +451,45 @@ class DefinitionChannelType(DjangoObjectType):
        model = Channel


class QuestionRelatedMilestonesType(graphene.ObjectType):
    question_id = graphene.ID(required=True)
    milestones = graphene.List(
        graphene.NonNull(MilestoneStateType), required=True
    )


class TeamQuestionnaireStateType(DjangoObjectType):
    class Meta:
        model = TeamQuestionnaireState

    status = graphene.Field(QuestionnaireStateEnum, required=True)
    reviewed_by = graphene.Field(RestrictedUser)
    related_milestones = graphene.List(
        graphene.NonNull(QuestionRelatedMilestonesType), required=True
    )

    def resolve_related_milestones(self, info):
        user = user_from_context(info.context)

        if user.group == User.AuthGroup.TRAINEE:
            return []

        team_state_id = self.team.state_id
        result = []
        for question in self.questionnaire.questions.filter(
            type=QuestionTypes.FREE_FORM
        ):
            result.append(
                {
                    "question_id": question.id,
                    "milestones": MilestoneState.objects.filter(
                        team_state_id=team_state_id,
                        milestone__id__in=question.details.related_milestones,
                    ),
                }
            )

        return result


class QuestionnaireAnswerType(DjangoObjectType):
+10 −0
Original line number Diff line number Diff line
## 0.15.0

Allow specifying open-ended questions in questionnaires. inject/backend#340

### questionnaires.yml

- add field `type` to questions
- add field `related_milestones` to `free-form` type questions 
- add field `multiline` to `free-form` type questions

## 0.14.0

Allow multiple info channels in a definition. #336
+38 −21
Original line number Diff line number Diff line
@@ -279,8 +279,12 @@ Each questionnaire has the following fields:
  after which the questionnaire will be sent
- **control**: _[control](#control), default=empty_
- **overlay**: _[overlay](#overlay), default=empty_
- **questions**:
- **questions**: details specified bellow
  - **content**: _[content](#content), default=empty_ - should contain the text of the question
  - **type**: _string, default="radio"_ - type of the question, determines what fields can be specified,
  currently supported values are `radio` and `free-form`

#### Question: radio
- **max**: _int, required_ - the number of options for the question
- **labels**: _str, default=""_ - the labels of options specified as a comma-separated string (`,`),
  empty string means the labels will be numbers in range [1-max],
@@ -300,3 +304,16 @@ Each questionnaire has the following fields:
  control object will be modified accordingly.
  Choices may be omitted. 
  Valid numbers are in the range [1-max].

#### Question: free-form
- **related_milestones**: _list of strings, default=empty_ - list of related milestone names,
  these milestones will be shown to the instructor for evaluation,
  example:
  ```yaml
  related_milestones:
    - milestone_1
    - milestone_2
  # or
  related_milestones: [ milestone_1, milestone_2 ]
  ```
- **multiline**: _bool, default=False_ - changes whether the input text field should be multiline or single line
+1 −1
Original line number Diff line number Diff line
exercise_duration: 60
show_exercise_time: False
version: 0.14.0
version: 0.15.0
Loading