Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 125 additions & 1 deletion docs/DataStructure.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
- SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
- SPDX-FileCopyrightText: 2021-2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-only
-->

Expand Down Expand Up @@ -270,6 +270,7 @@ Optional extra settings for some [Question Types](#question-types)
| `columns` | `grid` | Array | - | Array of column identifiers / labels for grid questions |
| `rows` | `grid` | Array | - | Array of row identifiers / labels for grid questions |
| `questionType` | `grid` | String | `checkbox`, `number`, `radio` | Type of cell for grid questions (checkbox, numeric input, or radio) |
| `branches` | `dropdown`, `multiple_unique` | Array | Array of [Branch objects](#branch-object) | Branches with follow-up questions, see [Follow-up Questions](#follow-up-questions) |

### Option Types

Expand All @@ -282,3 +283,126 @@ Supported option types and their intended usage.
| `column` | Represents a column header when rendering options in a grid-style question. |

`row` and `column` option types are primarily used together with `grid` question types to build a two-dimensional selection matrix. `choice` is the default for normal option lists.

## Follow-up Questions

Some question types can have branches of follow-up questions in `extraSettings.branches`. A branch is shown when the answer of its question matches one of the branch's conditions, and its follow-up questions are shown right below that question. Follow-up questions can not have branches themselves.

### Question Properties for Subquestions

Follow-up questions (subquestions) are questions of their own with additional properties:

| Property | Type | Description |
| ---------------- | ------- | --------------------------------------------------------------------- |
| parentQuestionId | Integer | The ID of the question the branch belongs to (null for other questions) |
| branchId | String | The ID of the branch this subquestion belongs to |

### Question Types With Branches

| Question Type | Condition Types | Description |
| ----------------- | ----------------------------------- | -------------------------------------------------- |
| `multiple_unique` | `option_selected`, `other_selected` | Radio buttons - the selected option, or "Other" |
| `dropdown` | `option_selected` | Dropdown - the selected option |

A branch is shown when any of its conditions matches. An answer leads to at most one branch: an option, or the answer "Other", is part of the conditions of one branch only. A branch without conditions is never shown; it keeps its follow-up questions.

When an option is deleted, its conditions are removed from the branches. When `allowOtherAnswer` is turned off, the `other_selected` conditions are removed.

### Branch Object

A branch defines conditions and the subquestions that appear when those conditions are met. The subquestions are only part of a branch when reading a question, they are created, changed and deleted like other questions.

| Property | Type | Description |
| ------------ | ---------------------------------------- | ---------------------------------------------- |
| id | String | Unique identifier for the branch, at most 64 characters |
| conditions | Array of [Conditions](#condition-object) | Conditions that must be met to show the branch |
| subQuestions | Array of [Questions](#question) | Questions shown when conditions are met |

```json
{
"id": "branch-1705587600000",
"conditions": [{ "type": "option_selected", "optionId": 42 }],
"subQuestions": [
{
"id": 101,
"formId": 3,
"order": 1,
"type": "short",
"text": "Please provide details",
"parentQuestionId": 100,
"branchId": "branch-1705587600000"
}
]
}
```

### Condition Object

Conditions determine when a branch is activated. Every condition has a `type`, which must be one of the condition types of the question type.

#### option_selected (for dropdown, multiple_unique)

Matches when the option is selected. The option must belong to the question.

```json
{ "type": "option_selected", "optionId": 42 }
```

#### other_selected (for multiple_unique)

Matches when the answer "Other" is given with a text. Requires `allowOtherAnswer`.

```json
{ "type": "other_selected" }
```

### Example

A dropdown question with a branch of follow-up questions:

```json
{
"id": 100,
"formId": 3,
"order": 1,
"type": "dropdown",
"isRequired": true,
"text": "Do you have any dietary restrictions?",
"options": [
{ "id": 42, "questionId": 100, "order": 1, "text": "Yes" },
{ "id": 43, "questionId": 100, "order": 2, "text": "No" }
],
"extraSettings": {
"branches": [
{
"id": "branch-yes",
"conditions": [{ "type": "option_selected", "optionId": 42 }],
"subQuestions": [
{
"id": 101,
"formId": 3,
"order": 1,
"type": "long",
"text": "Please describe your dietary restrictions",
"parentQuestionId": 100,
"branchId": "branch-yes"
}
]
}
]
}
}
```

### Answers

Follow-up questions are answered like any other question, keyed by their own id:

```json
{
"100": ["42"],
"101": ["Vegetarian, no nuts"]
}
```

Only the follow-up questions of the branches the answers open can be answered, and only those are required.
3 changes: 1 addition & 2 deletions lib/Analytics/AnalyticsDatasource.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ public function readData($option): array {

$formId = $option['formId'];

$questions = $this->formsService->getQuestions($formId);
foreach ($questions as $question) {
foreach (FormsService::flattenQuestions($this->formsService->getQuestions($formId)) as $question) {
$questionMap[$question['id']] = $question['text'];
}

Expand Down
20 changes: 19 additions & 1 deletion lib/Constants.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2021-2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

Expand Down Expand Up @@ -239,6 +239,24 @@ class Constants {
self::ANSWER_GRID_TYPE_RADIO,
];

/**
* Condition types of branches
*/
public const CONDITION_TYPE_OPTION_SELECTED = 'option_selected';
public const CONDITION_TYPE_OTHER_SELECTED = 'other_selected';

/**
* Question types that can have branches with follow-up questions in `extraSettings.branches`,
* mapped to the condition types their branches accept
*
* Each branch has a unique `id` and `conditions`, e.g. `[{type: 'option_selected', optionId: 1}]`.
* The conditions of a branch are OR'ed. An answer leads to at most one branch.
*/
public const QUESTION_TYPES_WITH_BRANCHES = [
self::ANSWER_TYPE_MULTIPLEUNIQUE => [self::CONDITION_TYPE_OPTION_SELECTED, self::CONDITION_TYPE_OTHER_SELECTED],
self::ANSWER_TYPE_DROPDOWN => [self::CONDITION_TYPE_OPTION_SELECTED],
];

/**
* !! Keep in sync with src/mixins/ShareTypes.js !!
*/
Expand Down
Loading
Loading