From 332f5ef297815502adbc79df6aa4962688a9aebc Mon Sep 17 00:00:00 2001 From: Viktoria Wiebe <vwiebe@uni-osnabrueck.de> Date: Tue, 1 Oct 2024 14:07:24 +0000 Subject: [PATCH] Tic 2807 - add toggling task visibility functionality to student task dashboard Closes #2807 Merge request studip/studip!3096 --- ...6.0.19_tic_2807_add_cw_task_visibility.php | 26 +++++++ .../JsonApi/Routes/Courseware/Authority.php | 2 +- .../JsonApi/Routes/Courseware/TasksUpdate.php | 4 ++ .../JsonApi/Schemas/Courseware/Task.php | 1 + lib/models/Courseware/StructuralElement.php | 18 +++++ lib/models/Courseware/Task.php | 22 ++++++ .../tasks/CoursewareDashboardTasks.vue | 67 ++++++++++++++++++- 7 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 db/migrations/6.0.19_tic_2807_add_cw_task_visibility.php diff --git a/db/migrations/6.0.19_tic_2807_add_cw_task_visibility.php b/db/migrations/6.0.19_tic_2807_add_cw_task_visibility.php new file mode 100644 index 00000000000..4e06c33ebcb --- /dev/null +++ b/db/migrations/6.0.19_tic_2807_add_cw_task_visibility.php @@ -0,0 +1,26 @@ +<?php + + +class Tic2807AddCWTaskVisibility extends Migration +{ + public function description() + { + return 'Adds a visibility column to courseware tasks.'; + } + + protected function up() + { + DBManager::get()->exec( + "ALTER TABLE `cw_tasks` + ADD COLUMN `visible` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `renewal_date`" + ); + } + + protected function down() + { + DBManager::get()->exec( + "ALTER TABLE `cw_tasks` + DROP COLUMN `visible`" + ); + } +} diff --git a/lib/classes/JsonApi/Routes/Courseware/Authority.php b/lib/classes/JsonApi/Routes/Courseware/Authority.php index 2acf83e3a6b..7ed609fc65d 100644 --- a/lib/classes/JsonApi/Routes/Courseware/Authority.php +++ b/lib/classes/JsonApi/Routes/Courseware/Authority.php @@ -324,7 +324,7 @@ class Authority public static function canShowTask(User $user, Task $resource): bool { - return self::canUpdateTask($user, $resource); + return self::canUpdateTask($user, $resource) || $resource->visible; } public static function canIndexTasks(User $user): bool diff --git a/lib/classes/JsonApi/Routes/Courseware/TasksUpdate.php b/lib/classes/JsonApi/Routes/Courseware/TasksUpdate.php index 33b51ad1ae8..dd9f648f295 100644 --- a/lib/classes/JsonApi/Routes/Courseware/TasksUpdate.php +++ b/lib/classes/JsonApi/Routes/Courseware/TasksUpdate.php @@ -81,6 +81,10 @@ class TasksUpdate extends JsonApiController $resource->requestRenewal(); } + if (self::arrayHas($json, 'data.attributes.visible')) { + $resource->setVisibility(self::arrayGet($json, 'data.attributes.visible')); + } + return $resource; } diff --git a/lib/classes/JsonApi/Schemas/Courseware/Task.php b/lib/classes/JsonApi/Schemas/Courseware/Task.php index 81c7a0d18a5..c612333886c 100644 --- a/lib/classes/JsonApi/Schemas/Courseware/Task.php +++ b/lib/classes/JsonApi/Schemas/Courseware/Task.php @@ -36,6 +36,7 @@ class Task extends SchemaProvider 'submitted' => (bool) $resource['submitted'], 'renewal' => empty($resource['renewal']) ? null : (string) $resource['renewal'], 'renewal-date' => date('c', $resource['renewal_date']), + 'visible' => (bool) $resource['visible'], 'mkdate' => date('c', $resource['mkdate']), 'chdate' => date('c', $resource['chdate']), ]; diff --git a/lib/models/Courseware/StructuralElement.php b/lib/models/Courseware/StructuralElement.php index e72cc4f34ef..8e06a396af9 100644 --- a/lib/models/Courseware/StructuralElement.php +++ b/lib/models/Courseware/StructuralElement.php @@ -391,6 +391,19 @@ class StructuralElement extends \SimpleORMap implements \PrivacyObject, \Feedbac return true; } + if ($task->isSubmitted()) { + if ($task->visible) { + return true; + } + $solvers = $task->getTaskGroup()->getSolvers(); + foreach ($solvers as $solver) { + if ($solver->id === $user->id) { + return true; + } + } + return false; + } + return $task->userIsASolver($user); } @@ -1240,4 +1253,9 @@ SQL; [$this->id, self::class] ); } + + public function isTaskVisible(): bool + { + return $this->payload['task-visibility']; + } } diff --git a/lib/models/Courseware/Task.php b/lib/models/Courseware/Task.php index d409676ca3c..2a1a28d2b00 100644 --- a/lib/models/Courseware/Task.php +++ b/lib/models/Courseware/Task.php @@ -21,6 +21,7 @@ use User; * @property int $submitted database column * @property string|null $renewal database column * @property int $renewal_date database column + * @property int $visible database column * @property int|null $feedback_id database column * @property int $mkdate database column * @property int $chdate database column @@ -90,6 +91,11 @@ class Task extends \SimpleORMap parent::configure($config); } + public function getTaskGroup(): TaskGroup + { + return $this->task_group; + } + /** * Returns the structural element of this task. * This structural element and all its children are part of the task. @@ -130,6 +136,16 @@ class Task extends \SimpleORMap if ($this->solver_id === $user->id) { return true; } + + if ($this->visible) { + $solvers = $this->getTaskGroup()->getSolvers(); + foreach ($solvers as $solver) { + if ($solver->id === $user->id) { + return true; + } + } + } + break; case 'group': @@ -235,6 +251,12 @@ class Task extends \SimpleORMap $this->store(); } + public function setVisibility(bool $visibility): void + { + $this->visible = (int) $visibility; + $this->store(); + } + private function getStructuralElementProgress(StructuralElement $structural_element): float { $containers = Container::findBySQL('structural_element_id = ?', [intval($structural_element->id)]); diff --git a/resources/vue/components/courseware/tasks/CoursewareDashboardTasks.vue b/resources/vue/components/courseware/tasks/CoursewareDashboardTasks.vue index 35384fce41f..cb3577ad563 100644 --- a/resources/vue/components/courseware/tasks/CoursewareDashboardTasks.vue +++ b/resources/vue/components/courseware/tasks/CoursewareDashboardTasks.vue @@ -2,7 +2,15 @@ <div class="cw-dashboard-tasks-wrapper"> <table v-if="tasks.length > 0" class="default"> <colgroup> - <col /> + <col style="width: 5%" /> + <col style="width: 20%" /> + <col style="width: 10%" /> + <col style="width: 10%" /> + <col style="width: 5%" /> + <col style="width: 15%" /> + <col style="width: 15%" /> + <col style="width: 15%" /> + <col style="width: 5%" /> </colgroup> <thead> <tr> @@ -12,6 +20,7 @@ <th>{{ $gettext('Abgabefrist') }}</th> <th>{{ $gettext('Abgabe') }}</th> <th class="responsive-hidden">{{ $gettext('Verlängerungsanfrage') }}</th> + <th class="responsive-hidden">{{ $gettext('Für Teilnehmende freigeben') }}</th> <th class="responsive-hidden">{{ $gettext('Anmerkung') }}</th> <th class="actions">{{ $gettext('Aktionen') }}</th> </tr> @@ -54,6 +63,23 @@ {{ $gettext('verlängert bis') }}: {{ getReadableDate(task.attributes['renewal-date']) }} </span> </td> + <td class="responsive-hidden"> + <span v-if="task.attributes.submitted"> + <button + class="button" + v-if="!task.attributes.visible" + @click="toggleVisibilityOn(task)" + > + {{ $gettext('Freigeben') }} + </button> + <button + class="button" + v-if="task.attributes.visible" + @click="toggleVisibilityOff(task)"> + {{ $gettext('Freigabe widerrufen') }} + </button> + </span> + </td> <td class="responsive-hidden"> <studip-icon v-if="feedback" @@ -166,6 +192,13 @@ export default { return result; }); }, + taskVisibilities() { + let visibilities = []; + for (const task of this.tasks) { + visibilities[`${task.task.id}`] = task.element.attributes.payload['task-visibility']; + } + return visibilities; + } }, methods: { ...mapActions({ @@ -175,6 +208,7 @@ export default { companionSuccess: 'companionSuccess', companionError: 'companionError', createCoursewareUnit: 'courseware-units/create', + loadStructuralElement: 'courseware-structural-elements/loadById' }), getTaskMenuItems(task, status, element) { let menuItems = []; @@ -277,6 +311,37 @@ export default { this.showFeedbackDialog = true; this.currentTaskFeedback = feedback.attributes.content; }, + toggleVisibilityOn(task) { + let attributes = task.attributes; + attributes['visible'] = true; + this.toggleVisibility(task, attributes); + }, + toggleVisibilityOff(task) { + let attributes = task.attributes; + attributes['visible'] = false; + this.toggleVisibility(task, attributes); + }, + async toggleVisibility(task, attributes) { + await this.updateTask({ + attributes: attributes, + taskId: task.id, + }); + + const taskGroup = this.getTaskGroupById({ id: task.relationships['task-group'].data.id }); + const taskTitle = taskGroup.attributes.title; + + if (attributes.visible) { + this.companionSuccess({ + info: this.$gettextInterpolate(this.$gettext('"%{ title }" wurde freigegeben.'), + { title: taskTitle }), + }); + } else { + this.companionSuccess({ + info: this.$gettextInterpolate(this.$gettext('Die Freigabe für %{ "title }" wurde zurückgenommen.'), + { title: taskTitle }), + }); + } + } }, }; </script> -- GitLab