From 2cd3bc605af9e5d32930a569d7e4d743cf750b12 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Wed, 31 Aug 2022 20:47:25 +0200 Subject: [PATCH] Feature: Allow shuffling of answer options This introduces an question option to randomize the order of the answer options. Shuffling the order of the answer choices reduces bias in responses. Implements #1067 If no shuffling is enabled the options are sorted in the order they were created, as currently they are shown as returned by the database which is not necessarily sorted. Fixes #1007 Signed-off-by: Ferdinand Thiessen --- lib/Controller/ApiController.php | 1 + lib/Db/Question.php | 13 +++++ .../Version030000Date20220831195000.php | 57 +++++++++++++++++++ src/components/Questions/Question.vue | 14 +++++ src/components/Questions/QuestionDropdown.vue | 4 +- src/components/Questions/QuestionMultiple.vue | 4 +- src/mixins/QuestionMixin.js | 37 ++++++++++++ tests/Unit/Service/FormsServiceTest.php | 6 ++ 8 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 lib/Migration/Version030000Date20220831195000.php diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index cbb143f3f..b3de3d095 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -482,6 +482,7 @@ public function newQuestion(int $formId, string $type, string $text = ''): DataR $question->setText($text); $question->setDescription(''); $question->setIsRequired(false); + $question->setExtraSettings([]); $question = $this->questionMapper->insert($question); diff --git a/lib/Db/Question.php b/lib/Db/Question.php index 7d02dbd58..1bcc4e0a1 100644 --- a/lib/Db/Question.php +++ b/lib/Db/Question.php @@ -44,6 +44,8 @@ * @method void setText(string $value) * @method string getDescription() * @method void setDescription(string $value) + * @method array getExtraSettings() + * @method void setExtraSettings(array $value) */ class Question extends Entity { protected $formId; @@ -52,6 +54,7 @@ class Question extends Entity { protected $isRequired; protected $text; protected $description; + protected $extraSettingsJson; public function __construct() { $this->addType('formId', 'integer'); @@ -62,6 +65,15 @@ public function __construct() { $this->addType('description', 'string'); } + public function getExtraSettings(): array { + return json_decode($this->getExtraSettingsJson(), true); + } + + public function setExtraSettings(array $extraSettings) { + // Make sure to be an object (empty assoc. array) + $this->setExtraSettingsJson(json_encode($extraSettings, JSON_FORCE_OBJECT)); + } + public function read(): array { return [ 'id' => $this->getId(), @@ -71,6 +83,7 @@ public function read(): array { 'isRequired' => $this->getIsRequired(), 'text' => htmlspecialchars_decode($this->getText()), 'description' => htmlspecialchars_decode($this->getDescription()), + 'extraSettings' => $this->getExtraSettings(), ]; } } diff --git a/lib/Migration/Version030000Date20220831195000.php b/lib/Migration/Version030000Date20220831195000.php new file mode 100644 index 000000000..088d12f86 --- /dev/null +++ b/lib/Migration/Version030000Date20220831195000.php @@ -0,0 +1,57 @@ + + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ +namespace OCA\Forms\Migration; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +class Version030000Date20220831195000 extends SimpleMigrationStep { + + // Types::JSON is only available starting with NC24, we still support NC22 + private const TYPE_JSON = 'json'; + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + $table = $schema->getTable('forms_v2_questions'); + + if (!$table->hasColumn('extra_settings_json')) { + $table->addColumn('extra_settings_json', self::TYPE_JSON, [ + 'notnull' => false, + ]); + + return $schema; + } + + return null; + } +} diff --git a/src/components/Questions/Question.vue b/src/components/Questions/Question.vue index 079eabbe1..8df3216a1 100644 --- a/src/components/Questions/Question.vue +++ b/src/components/Questions/Question.vue @@ -61,6 +61,11 @@ {{ t('forms', 'Required') }} + + {{ t('forms', 'Shuffle options') }} +