# Definitions

This directory contains the example definitions which showcase various functionality.

## Versioning

Definition use a modified [semantic versioning](https://semver.org/):

- **Patch**: adding fields and files to the definition, this allows for older versions of definitions to run on newer backends.
  Sensible defaults should be selected when adding fields to allow for backwards compatibility.
- **Minor**: modifying existing fields, such as renaming, deleting or changing their meaning.
  Such changes would either break the definition validation or it would cause unintended/incorrect behavior.
- **Major**: no set of rules are defined, can be used for extreme changes to the definition format.

The current definition version the backend supports is the topmost version in the changelog.
For example, backend version [`2.0.0`](https://gitlab.fi.muni.cz/inject/backend/-/tags/v2.0.0)
supports definitions with version [`0.12.1`](https://gitlab.fi.muni.cz/inject/backend/-/blob/v2.0.0/definitions/CHANGELOG.md?ref_type=tags).

## Definition file structure

```
content/
files/
config.yml
channels.yml
injects.yml
email.yml
milestones.yml
tools.yml
roles.yml
questionnaires.yml
objectives.yml
```

The `content/` and `files/` directories are optional.
They do not need to be present if your definition does not use the relevant features.

Furthermore, it is possible to use a directory instead of a single YAML file.
The files inside the directory must follow the same structure as the original YAML file.
For example, if you decide that `injects.yml` file is too large
you can create a directory `injects` inside of which you create 4 different files
which when combined will contain the original content of the `injects.yml` file.
These files can be named in any way you like, however they must be in the YAML format.

The above currently holds true for all YAML files except `config.yml`.
These approaches can be mixed:
e.g. if you have many injects but not that many tools
you can put the injects into a directory and still use a single `tools.yml` file.

_Note_: If the definition contains both a YAML file and a directory for the same structure,
the YAML file will be prioritized and the directory ignored.
E.g. if you have `injects.yml` and `injects` directory, the directory will be skipped.

## Current file formats

Below are schema definitions for each file.
Every field is required unless a default value is specified or it is stated otherwise.

### Quick list

- [common definitions](#common-definitions)
- [config.yml](#configyml)
- [channels.yml](#channelsyml)
- [injects.yml](#injectsyml)
- [tools.yml](#toolsyml)
- [milestones.yml](#milestonesyml)
- [email.yml](#emailyml)
- [roles.yml](#rolesyml)
- [questionnaires.yml](#questionnairesyml)
- [objectives.yml](#objectivesyml)

### Common definitions

This section contains definitions of common fields used by multiple definition files.
Restrictions on some uses might apply, these restrictions will be listed on the exact location.

#### Content

Only one of **content** or **content_path** can be specified on a single content field.
These fields can be formatted as **Markdown** with [these](https://python-markdown.github.io/#differences) restrictions.
The Markdown syntax can be found [here](https://daringfireball.net/projects/markdown/syntax).

**Warning**: The frontend application sanitizes the HTML generated by Markdown using
the default options of [sanitize-html](https://www.npmjs.com/package/sanitize-html).
Some content may therefore not be shown as intended. Mainly, `<script>`, `<style>`, `<iframe>`,
`<img>`, `<video>`, and `<form>` tags, along with attributes like `id`,
`class`, and event handlers (e.g., `onclick`, `onload`) are not allowed. Furthermore, the only URL schemes
allowed (in `href`, `src`, and `cite`) are `http`, `https`, `ftp`, `mailto`, and `tel`.

If you want to embed media (images, videos, or audio recordings), use files from the definition.
To embed a file, simply surround the path to the file in `{{}}`.
For example:

```yaml
content:
  content: "This is an embedded image {{files/image.png}}"
```

**Warning**: If the embedding is done directly in YAML (like the example above),
you have to surround the content in `"`, just like the example.

The same syntax can also be used in markdown files,
without the requirement of surrounding the text in `"`.
It is recommended to do embeddings in markdown files instead of specifying it directly in YAML.

All most commonly used [image](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#supported_image_formats), [audio, and video](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers#index_of_media_container_formats_file_types) file formats are supported.

- **content**: _str, default=""_ - content displayed to users, mutually exclusive with `content_path`
- **content_path**: _str, default=""_ - name of a markdown file that contains the content,
  mutually exclusive with `content`
- **file_name**: _str, default=""_ - optional file attachment,
  has to be present in the `files` directory in the definition

#### Control

- **milestone_condition**: _str, default=""_ - condition which must be met before an object can be sent/processed.
  Supports simple logical expressions in Python format, where the allowed operators are `and`, `or`, `not` and `()`.
  These operators can be applied to names of milestones specified in the definition.
- **activate_milestone**: _str, default=""_ - a comma-separated list of milestone _names_ (not display names),
  which will be activated after an object is sent/processed
- **deactivate_milestone**: _str, default=""_ - a comma-separated list of milestone _names_ (not display names),
  which will be deactivated after an object is sent/processed
- **roles**: _str, default=""_ - usable only if roles are enabled,
  a space-separated list of roles, a team must have at least one of the roles to be eligible for this object

#### Overlay

- **duration**: _int_ - number of minutes the overlay should stay active

### config.yml

This file contains various settings for an exercise.
All advanced features are disabled by default:

- **exercise_duration**: _int_ - Duration of the exercise in minutes.
- **email_between_teams**: _bool, default=False_ - Allow emails between teams.
  Can only be enabled if emails are enabled.
- **custom_email_suffix**: _string, default="mail.com"_ - Changes the domain of email addresses of teams,
  e.g. if set to `gmail.com` team email addresses will have the form `team_name@gmail.com`.
- **show_exercise_time**: _bool, default=False_ - Show the remaining duration of the ongoing exercise to trainees on the frontend.
- **enable_roles**: _bool, default=False_ - Enables use of team roles.
  Requires `roles.yml` file to be included in the definition.
- **description**: _string, default=""_ - description of the definition
- **target_audience**: _string, default=""_ - target audience of the definition
- **prerequisites**: _list of strings, default=empty_ - list of prerequisites for the definition
- **version**: _string_ - semver version string of the format this definition uses, see [versioning](#versioning) for details.

### objectives.yml

This file contains all learning objectives.
Each learning objective has the following fields:

- **name**: _string, unique_ - name of the learning objective
- **tags**: _string, default=""_ - a comma-separated list of tags corresponding to this objective
- **description**: _string, default=""_ - description of the objective
- **order**: _int, default=0_ - order of the objective,
  defines the order in which objectives will be displayed for instructors,
- **activities**: - list of learning activities
    - **name**: _string_ - name of the learning activity
    - **description**: _string, default=""_ - description of the activity
    - **tags**: _string, default=""_ - a comma-separated list of tags corresponding to this activity
    - **milestones**: _list of strings, default=empty_ - list of milestones related to this activity,
      a milestone can only be linked to _one_ activity

### channels.yml

This file contains the definitions of _all_ channels used in the exercise.

- **name**: _string, unique_ - the name that will be displayed to users
- **display_name**: _string, default=name_ - display name of the channel
- **description**: _string, default=""_ - description of the channel
- **type**: _string_ - the type of messages that will be sent to this channel, see [injects.yml](#injectsyml) for details.

Currently supported channel types:

- `info` - the most basic type of message,
  A definition _can_ include one or more channels with the `info` type.
- `tool` - tool usage by trainees will be sent to the channel with this type.
  Injects _cannot_ be specified with this type and there can be at most _one channel_ with this type in a definition
- `email` - injects meant to represent email communication
- `form` - questionnaires will be sent to this channel

_At most one channel_ of a specific type can exist (except the `info` channels).
In other words, there cannot be multiple channels with type `email`.
If a definition does not contain a channel with the `tool` type, _no tools_ can be specified.
Same rule applies to emails and questionnaires.
When a channel of some type is specified, at least one related object must be specified.
E.g. if an `email` channel exists, at least one email address must be specified in `emails.yml`.

**Note**: Defining a channel for tools or emails is required to enable these features. Without such a channel, these functionalities will be turned off.

### injects.yml

This file contains a list of injects.
Each inject has the following fields:

- **name**: _string, unique_ - name of the inject
- **time**: _int, default=0_ - time since the beginning of the exercise in minutes,
  the inject will not be processed until this time has been reached
- **delay**: _int, default=0_ - time in minutes, whenever this inject is about to be sent,
  it is delayed by this number of minutes.
  The condition is checked throughout the duration and if it becomes false, the inject is cancelled
  and the process can start again.
- **organization**: _string, default=""_ - name of the organization this inject was sent from
- **type**: _string, default="info"_ - type of this inject, the `alternatives` field depends on this value
- **alternatives**: options specified bellow
- **target**: _string, default=""_ - a mandatory field (only for `info` injects) that specifies to which info channel should be the inject sent

When selecting alternatives to send,
they are ordered alphabetically based on their `name`.
The first one that fulfills the condition is selected.
In other words, when multiple alternatives can be sent,
their priority is determined by their `name` attribute.

#### Alternatives: _info_

- **name**: _string_ - name of the alternative
- **content**: _[content](#content), default=empty_ -
  if empty, no ActionLog will be created when this alternative is sent.
  Can be used for hidden state management of the exercise.
- **control**: _[control](#control), default=empty_ - milestone conditions are checked when the alternative is being selected,
  milestone modifications happen _after_ the alternative has been selected
- **overlay**: _[overlay](#overlay), default=empty_
- **confirmation**: _default=empty_ - adds a confirmation button to this alternative
    - **text**: _string_ - the button text
    - **control**: _[control](#control)_ - milestone modifications happen _after_ a team presses the button,
      milestone condition and roles cannot be specified

#### Alternatives: _email_

- **name**: _string_ - name of the alternative
- **sender**: _string_ - an email address from which this alternative is sent, must be an address
  specified in `email.yml`.
- **subject**: _str_ - subject of the email
- **content**: _[content](#content), default=empty_
- **control**: _[control](#control), default=empty_ - milestone conditions are checked when the alternative is being selected,
  milestone modifications happen _after_ the alternative has been selected
- **extra_copies**: _int, default=0_ - number of extra copies to be sent, useful for specifying spam email.
- **overlay**: _[overlay](#overlay), default=empty_

### tools.yml

This file contains a list of tools.
A tool can be thought of as a simple function,
which based on its input and current state of the exercise returns some output.
Each tool has the following fields:

- **name**: _string, unique_ - Name of the tool
- **category**: _string, default=""_ - category of the tool, used to group multiple tools together visually
- **tooltip_description**: _string, default=""_ - Description of a tool displayed to trainees.
- **hint**: _string, default=""_ - Argument hint
- **default_response**: _[content](#content), default=Empty_ - Response that the tool returns if no other response gets matched.
- **roles**: _string, default=""_ - Usable only if roles are enabled.
  Defines which roles will be able to use the tool.
  By default, all roles can access the tool.
  If tool is meant for multiple roles, it can be achieved by writing the role names separated by spaces:
  ```yml
  - roles: role_name_1 role_name_2 role_name_3
  ```
- **requires_input**: _bool, default=True_ - if set to False, this tool will not require an input
  and will be showcased as a simple button, will cause the `param` fields in responses to be ignored when selecting a response
- **responses**: - A list of responses.
  A response is matched when its conditions are met.
  When trainees use a tool, the list of responses is examined from top to bottom and
  only the **first** matched response is sent back.
    - **param**: _string_ - A parameter that is matched **exactly** to the input of trainees.
      Can be written as [Python regular expression](https://docs.python.org/3/library/re.html) if parameter `regex` is set to true.
    - **regex**: _bool, default=False_ - Determines whether parameter `param` is matched as a string or a regex.
    - **time**: _int, default=0_ - Time since the beginning of the exercise in minutes after which this response can be matched.
      If the trainees use correct param but this time was not reached yet, it will not match.
    - **content**: _[content](#content), default=empty_
    - **control**: _[control](#control), default=empty_ - milestone condition is checked when a response is being selected,
      milestone modifications are triggered _after_ this response has been selected

### milestones.yml

This file contains all milestones in an exercise.
Any `milestone_condition` or `(de)activate_milestone` field in the whole definition can only contain milestones defined in here.
Each milestone has the following fields:

- **name**: _string, unique_ - Name of the milestone.
  Rules for the naming of the milestones:
    - A name must start with a letter or the underscore character
    - A name cannot start with a number
    - A name can only contain alphanumeric characters and underscores (A-z, 0-9, and \_ )
    - Variable names are case-sensitive (age, Age and AGE are three different variables)
    - A name must be continuous string -> a name can not contain spaces
- **display_name**: _string, default=name_ - display name of the milestone
- **description**: _string, default=""_ - description of the milestone
- **tags**: _list of strings, default=empty_ - optional list of tags related to the milestone
- **roles**: _string, default=""_ - Usable only if roles are enabled. Works only if the milestone is marked as visible.
  Defines, which roles will see the milestone. By default, all roles will see the milestone.
- **file_names**: _string, default=""_ - Defines the name of a file from the definition. Multiple file names
  can be specified as a space separated list. If at least one of these files is downloaded, this milestone activates.
- **final**: _bool, default=False_ - sets this milestone as a final milestone. If a team reaches this
  milestone, their exercise is considered to be finished.
- **initial_state**: _bool, default=False_ - the initial state of the milestone,
  if this is set to `True`, `final` _cannot_ be set to `True`

### email.yml

This file contains all email addresses where each address has the following fields:

- **address**: _string, unique_ - Address of the simulated correspondent.
- **description**: _string_ - Description of the correspondent and related context for the instructor.
- **control**: _[control](#control), default=empty_ - milestone condition and roles cannot be specified,
  milestone modifications are triggered _after_ a team sends an email to this address
- **team_visible**: _bool, default=False_ - Specifies if this email address should be visible to the teams.
- **organization**: _string, default=""_ - Name of the organization this email address belongs to.
- **templates**: _not required_ - A list of template emails that the instructor can choose from when writing an email.
    - **context**: _string_ - Description of the context of this email answer for the instructor.
    - **subject**: _string_ - subject of the email thread when sending this template
    - **content**: _[content](#content), default=empty_
    - **control**: _[control](#control), default=empty_ - milestone condition and roles cannot be specified,
      milestone modifications are triggered once the template is sent

### roles.yml

Required if roles are enabled.
This file contains all role names:

- **name**: _string, unique_ - Name of the role.
- **display_name**: _string, default=name_ - display name of the role
- **description**: _string, default=""_ - description of the role

### questionnaires.yml

This file contains all questionnaires.
Each questionnaire has the following fields:

- **title**: _string_ - title of the questionnaire
- **content**: _content, default=empty_ - description of the questionnaire
- **time**: _int, default=0_ - time since the beginning of the exercise in minutes,
  after which the questionnaire will be sent
- **control**: _[control](#control), default=empty_ - the milestone condition is checked
  when the questionnaire should be sent (similar to injects),
  the milestone modifications are triggered once _after_ the questionnaire has been answered fully (all questions answered)
- **overlay**: _[overlay](#overlay), default=empty_
- **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`
    - **note**: _string, default=""_ - add a stylized note _after_ the question

#### 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],
  if specified, the number of labels _must_ be equal to `max`
- **correct**: _int, default=0_ - position which represents the correct choice, for example,
  if we have 4 labels `yes, no, maybe, absolutely`, the `correct` field should have the number `3`
  if the correct choice is `maybe`, `0` means the question has no correct choice
- **controls**: _int -> [control](#control), default=empty_ - a mapping of choice numbers to
  control objects in the format
  ```yaml
    controls:
      1: { activate_milestone: ... }
      2: { deactivate_milestone: ... }
    ...
  ```
  When a user selects a choice which has a corresponding number, the milestones specified in the
  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
