From 14e48edb4d3cf936e2e4c6c3218f532af313b39a Mon Sep 17 00:00:00 2001
From: Maija Y
Date: Thu, 6 Jul 2023 14:55:06 +0300
Subject: [PATCH 01/13] research consent form translations
---
shared-module/src/locales/en/main-frontend.json | 9 +++++++++
shared-module/src/locales/fi/main-frontend.json | 9 +++++++++
2 files changed, 18 insertions(+)
diff --git a/shared-module/src/locales/en/main-frontend.json b/shared-module/src/locales/en/main-frontend.json
index 4586247faac9..e05d3e29593f 100644
--- a/shared-module/src/locales/en/main-frontend.json
+++ b/shared-module/src/locales/en/main-frontend.json
@@ -460,6 +460,15 @@
"reprocess-module-completions": "Reprocess module completions",
"required-field": "This field is required",
"requires-a-finnish-social-security-number": "(Requires a Finnish social security number)",
+ "research-consent-data-from-learning-process-is-used": "Data from the learning process, and data you give us through questionnaires, is used in this research. The data includes information about learning material usage, course assignment progress and completion, and exam performance. Individual students cannot be identified from any published results. Participation is voluntary, and if you do not wish to participate in the research, there are no consequences to you.",
+ "research-consent-director-info": "Petri Ihantola, the director of the MOOC center at the University of Helsinki is responsible for the research. You can request the deletion of data related to you at any time by sending an email to mooc@cs.helsinki.fi",
+ "research-consent-educational-research-is-conducted-on-the-courses": "Educational research is conducted on the courses. This research has multiple goals:",
+ "research-consent-goals-advance-knowledge": "advance knowledge and understanding about learning in online learning environments, and",
+ "research-consent-goals-develop-learning": "to develop learning materials so that they take individual differences in learning into account and can individualize content in the material based on the learner,",
+ "research-consent-goals-provide-research-based-support": "provide research-based support to other learning material developers and educational researchers, which will lead into a better learning experience for students.",
+ "research-consent-i-do-not-want-participate-in-educational-research": "I do not want to participate in the educational research.",
+ "research-consent-i-want-to-participate-in-educational-research": "I want to participate in the educational research. By choosing this, you help both current and future students.",
+ "research-consent-title": "Regarding research done on courses",
"role-admin": "Admin",
"role-assistant": "Assistant",
"role-course-or-exam-creator": "Course or exam creator",
diff --git a/shared-module/src/locales/fi/main-frontend.json b/shared-module/src/locales/fi/main-frontend.json
index 28991b7dd890..57074ca40bc2 100644
--- a/shared-module/src/locales/fi/main-frontend.json
+++ b/shared-module/src/locales/fi/main-frontend.json
@@ -457,6 +457,15 @@
"reprocess-module-completions": "Uudelleenkäsittele moduulien suoritukset",
"required-field": "Tämä on pakollinen kenttä",
"requires-a-finnish-social-security-number": "(Edellyttää suomalaista henkilötunnusta)",
+ "research-consent-data-from-learning-process-is-used": "Luovuttamaasi ja työskentelystäsi kertyvää tietoa käytetään tutkimuksessa. Kerätty tieto sisältää tietoa oppimateriaalien käytöstä, kurssitehtävien tekemisestä sekä kokeissa pärjäämisestä. Julkaistuista tutkimustuloksista ei pystytä tunnistamaan yksittäisiä opiskelijoita. Osallistuminen on vapaaehtoista, ja jos et osallistu tutkimukseen, siitä ei tule minkäänlaisia seuraamuksia.",
+ "research-consent-director-info": "Tutkimuksesta vastaa Helsingin yliopiston MOOC-keskuksen johtaja Petri Ihantola. Voit pyytää milloin tahansa sinusta kerätyn datan poistamista lähettämällä sähköpostin osoitteeseen mooc@cs.helsinki.fi",
+ "research-consent-educational-research-is-conducted-on-the-courses": "Kursseilla tehdään oppimiseen liittyvää tutkimusta. Tällä tutkimuksella on useampia tavoitteita:",
+ "research-consent-goals-advance-knowledge": "edistää digitaalisissa ympäristöissä tapahtuvaan oppimiseen liittyvää ymmärrystä ja tietoa, sekä",
+ "research-consent-goals-develop-learning": "kehittää kurssien oppimateriaaleja niin, että ne ottaisivat yksilölliset erot huomioon ja reagoisivat tarvittaessa tarjoten kohdennetumpaa oppisisältöä",
+ "research-consent-goals-provide-research-based-support": "tukea tutkimustiedon kautta muita oppimateriaalien kehittäjiä ja oppimisen tutkijoita. Tämä johtaa luonnollisesti myös parempaan oppimiskokemukseen opiskelijoille.",
+ "research-consent-i-do-not-want-participate-in-educational-research": "En osallistu oppimiseen liittyvään tutkimukseen.",
+ "research-consent-i-want-to-participate-in-educational-research": "Osallistun oppimiseen liittyvään tutkimukseen. Valitsemalla tämän autat sekä nykyisiä että tulevia opiskelijoita.",
+ "research-consent-title": "Kursseilla tehtävästä tutkimuksesta",
"role-admin": "Ylläpitäjä",
"role-assistant": "Avustaja",
"role-course-or-exam-creator": "Kurssien tai kokeiden luoja",
From e3b4574da12ef38a8a71e23495bd6e83fe16500a Mon Sep 17 00:00:00 2001
From: Maija Y
Date: Fri, 7 Jul 2023 13:07:43 +0300
Subject: [PATCH 02/13] Consent form for research on courses
---
.../forms/ResearchOnCoursesForm.tsx | 174 ++++++++++++++++++
shared-module/src/img/clipboard-icon.svg | 9 +
.../src/locales/en/main-frontend.json | 2 +-
.../src/locales/fi/main-frontend.json | 2 +-
4 files changed, 185 insertions(+), 2 deletions(-)
create mode 100644 services/main-frontend/src/components/forms/ResearchOnCoursesForm.tsx
create mode 100644 shared-module/src/img/clipboard-icon.svg
diff --git a/services/main-frontend/src/components/forms/ResearchOnCoursesForm.tsx b/services/main-frontend/src/components/forms/ResearchOnCoursesForm.tsx
new file mode 100644
index 000000000000..09c941a5ed99
--- /dev/null
+++ b/services/main-frontend/src/components/forms/ResearchOnCoursesForm.tsx
@@ -0,0 +1,174 @@
+/* eslint-disable i18next/no-literal-string */
+import { css } from "@emotion/css"
+import React, { useState } from "react"
+import { useForm } from "react-hook-form"
+import { useTranslation } from "react-i18next"
+
+import Button from "../../shared-module/components/Button"
+import Dialog from "../../shared-module/components/Dialog"
+import RadioButton from "../../shared-module/components/InputFields/RadioButton"
+import ClipboardIcon from "../../shared-module/img/clipboard-icon.svg"
+import { baseTheme, fontWeights, headingFont } from "../../shared-module/styles"
+
+interface ResearchOnCoursesFields {
+ consent: boolean
+ user_id: string
+}
+
+interface ResearchOnCoursesFormProps {
+ user_id?: string
+}
+
+const ResearchOnCoursesForm: React.FC> = ({
+ user_id,
+}) => {
+ const {
+ handleSubmit,
+ formState: { errors },
+ } = useForm()
+ const { t } = useTranslation()
+ const [researchConsentFormOpen, setResearchConsentFormOpen] = useState(true)
+ const [consent, setConsent] = useState(false)
+
+ const handleOnSubmit = handleSubmit(() => {
+ setResearchConsentFormOpen(false)
+ console.log("Closed: ", consent, user_id, errors)
+ setConsent(false)
+ })
+
+ const handleOnCancel = () => {
+ setResearchConsentFormOpen(false)
+ setConsent(false)
+ }
+
+ const handleConsentSelection = (value: boolean) => {
+ setConsent(value)
+ }
+
+ return (
+
+
+
+ )
+}
+
+export default ResearchOnCoursesForm
diff --git a/shared-module/src/img/clipboard-icon.svg b/shared-module/src/img/clipboard-icon.svg
new file mode 100644
index 000000000000..9fa4ae22751a
--- /dev/null
+++ b/shared-module/src/img/clipboard-icon.svg
@@ -0,0 +1,9 @@
+
diff --git a/shared-module/src/locales/en/main-frontend.json b/shared-module/src/locales/en/main-frontend.json
index e05d3e29593f..24e643b0956c 100644
--- a/shared-module/src/locales/en/main-frontend.json
+++ b/shared-module/src/locales/en/main-frontend.json
@@ -461,7 +461,7 @@
"required-field": "This field is required",
"requires-a-finnish-social-security-number": "(Requires a Finnish social security number)",
"research-consent-data-from-learning-process-is-used": "Data from the learning process, and data you give us through questionnaires, is used in this research. The data includes information about learning material usage, course assignment progress and completion, and exam performance. Individual students cannot be identified from any published results. Participation is voluntary, and if you do not wish to participate in the research, there are no consequences to you.",
- "research-consent-director-info": "Petri Ihantola, the director of the MOOC center at the University of Helsinki is responsible for the research. You can request the deletion of data related to you at any time by sending an email to mooc@cs.helsinki.fi",
+ "research-consent-director-info": "{{director-name}}, the director of the MOOC center at the University of Helsinki is responsible for the research. You can request the deletion of data related to you at any time by sending an email to ",
"research-consent-educational-research-is-conducted-on-the-courses": "Educational research is conducted on the courses. This research has multiple goals:",
"research-consent-goals-advance-knowledge": "advance knowledge and understanding about learning in online learning environments, and",
"research-consent-goals-develop-learning": "to develop learning materials so that they take individual differences in learning into account and can individualize content in the material based on the learner,",
diff --git a/shared-module/src/locales/fi/main-frontend.json b/shared-module/src/locales/fi/main-frontend.json
index 57074ca40bc2..93d294169e55 100644
--- a/shared-module/src/locales/fi/main-frontend.json
+++ b/shared-module/src/locales/fi/main-frontend.json
@@ -458,7 +458,7 @@
"required-field": "Tämä on pakollinen kenttä",
"requires-a-finnish-social-security-number": "(Edellyttää suomalaista henkilötunnusta)",
"research-consent-data-from-learning-process-is-used": "Luovuttamaasi ja työskentelystäsi kertyvää tietoa käytetään tutkimuksessa. Kerätty tieto sisältää tietoa oppimateriaalien käytöstä, kurssitehtävien tekemisestä sekä kokeissa pärjäämisestä. Julkaistuista tutkimustuloksista ei pystytä tunnistamaan yksittäisiä opiskelijoita. Osallistuminen on vapaaehtoista, ja jos et osallistu tutkimukseen, siitä ei tule minkäänlaisia seuraamuksia.",
- "research-consent-director-info": "Tutkimuksesta vastaa Helsingin yliopiston MOOC-keskuksen johtaja Petri Ihantola. Voit pyytää milloin tahansa sinusta kerätyn datan poistamista lähettämällä sähköpostin osoitteeseen mooc@cs.helsinki.fi",
+ "research-consent-director-info": "Tutkimuksesta vastaa Helsingin yliopiston MOOC-keskuksen johtaja {{director-name}}. Voit pyytää milloin tahansa sinusta kerätyn datan poistamista lähettämällä sähköpostin osoitteeseen ",
"research-consent-educational-research-is-conducted-on-the-courses": "Kursseilla tehdään oppimiseen liittyvää tutkimusta. Tällä tutkimuksella on useampia tavoitteita:",
"research-consent-goals-advance-knowledge": "edistää digitaalisissa ympäristöissä tapahtuvaan oppimiseen liittyvää ymmärrystä ja tietoa, sekä",
"research-consent-goals-develop-learning": "kehittää kurssien oppimateriaaleja niin, että ne ottaisivat yksilölliset erot huomioon ja reagoisivat tarvittaessa tarjoten kohdennetumpaa oppisisältöä",
From 79bd5295181f1b294fce262f07dfed9b659cb993 Mon Sep 17 00:00:00 2001
From: Maija Y
Date: Fri, 7 Jul 2023 15:32:00 +0300
Subject: [PATCH 03/13] new user research consent table
---
...8_add_user_reasearch_consent_table.down.sql | 2 ++
...238_add_user_reasearch_consent_table.up.sql | 18 ++++++++++++++++++
2 files changed, 20 insertions(+)
create mode 100644 services/headless-lms/migrations/20230707103238_add_user_reasearch_consent_table.down.sql
create mode 100644 services/headless-lms/migrations/20230707103238_add_user_reasearch_consent_table.up.sql
diff --git a/services/headless-lms/migrations/20230707103238_add_user_reasearch_consent_table.down.sql b/services/headless-lms/migrations/20230707103238_add_user_reasearch_consent_table.down.sql
new file mode 100644
index 000000000000..567c7ce8c607
--- /dev/null
+++ b/services/headless-lms/migrations/20230707103238_add_user_reasearch_consent_table.down.sql
@@ -0,0 +1,2 @@
+-- Add down migration script here
+DROP TABLE user_research_consents;
diff --git a/services/headless-lms/migrations/20230707103238_add_user_reasearch_consent_table.up.sql b/services/headless-lms/migrations/20230707103238_add_user_reasearch_consent_table.up.sql
new file mode 100644
index 000000000000..daf66dbd0c9d
--- /dev/null
+++ b/services/headless-lms/migrations/20230707103238_add_user_reasearch_consent_table.up.sql
@@ -0,0 +1,18 @@
+-- Add up migration script here
+CREATE TABLE user_research_consents (
+ id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
+ user_id UUID NOT NULL REFERENCES users,
+ research_consent BOOLEAN NOT NULL DEFAULT FALSE,
+ created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
+ updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
+ deleted_at TIMESTAMP WITH TIME ZONE,
+ CONSTRAINT user_id_when_not_deleted UNIQUE NULLS NOT DISTINCT(user_id, deleted_at)
+);
+CREATE TRIGGER set_timestamp BEFORE
+UPDATE ON user_research_consents FOR EACH ROW EXECUTE PROCEDURE trigger_set_timestamp();
+COMMENT ON TABLE user_research_consents IS 'Stores information whether a student has consented to participate on research done on courses';
+COMMENT ON COLUMN user_research_consents.user_id IS 'The user for which the consent belongs to';
+COMMENT ON COLUMN user_research_consents.research_consent IS 'Whether or not the student has given a consent to research';
+COMMENT ON COLUMN user_research_consents.created_at IS 'Timestamp when the record was created.';
+COMMENT ON COLUMN user_research_consents.updated_at IS 'Timestamp when the record was last updated. The field is updated automatically by the set_timestamp trigger.';
+COMMENT ON COLUMN user_research_consents.deleted_at IS 'Timestamp when the record was deleted. If null, the record is not deleted.';
From c473ff2d40aaab4e8c652816dfb1b9e7b8c50068 Mon Sep 17 00:00:00 2001
From: Maija Y
Date: Mon, 10 Jul 2023 20:31:33 +0300
Subject: [PATCH 04/13] endpoint for users research consent
---
services/headless-lms/models/sqlx-data.json | 41 +++++++++++++++++
services/headless-lms/models/src/lib.rs | 1 +
.../models/src/user_research_consents.rs | 39 ++++++++++++++++
.../src/controllers/main_frontend/users.rs | 44 ++++++++++++++++---
.../server/src/ts_binding_generator.rs | 1 +
.../src/services/backend/users.ts | 17 ++++++-
shared-module/src/bindings.guard.ts | 14 ++++++
shared-module/src/bindings.ts | 9 ++++
8 files changed, 158 insertions(+), 8 deletions(-)
create mode 100644 services/headless-lms/models/src/user_research_consents.rs
diff --git a/services/headless-lms/models/sqlx-data.json b/services/headless-lms/models/sqlx-data.json
index 07a8b0e7ee7f..cc724720e27b 100644
--- a/services/headless-lms/models/sqlx-data.json
+++ b/services/headless-lms/models/sqlx-data.json
@@ -3200,6 +3200,47 @@
},
"query": "\nUPDATE course_modules\nSET name = COALESCE($2, name),\n order_number = $3,\n uh_course_code = $4,\n ects_credits = $5,\n automatic_completion = $6,\n automatic_completion_number_of_exercises_attempted_treshold = $7,\n automatic_completion_number_of_points_treshold = $8,\n automatic_completion_requires_exam = $9,\n completion_registration_link_override = $10,\n enable_registering_completion_to_uh_open_university = $11\nWHERE id = $1\n "
},
+ "2ff803033f9f7a9eca35e97ebeada5b85cc247236b7a693fd496d448ca36efec": {
+ "describe": {
+ "columns": [
+ {
+ "name": "id",
+ "ordinal": 0,
+ "type_info": "Uuid"
+ },
+ {
+ "name": "user_id",
+ "ordinal": 1,
+ "type_info": "Uuid"
+ },
+ {
+ "name": "research_consent",
+ "ordinal": 2,
+ "type_info": "Bool"
+ },
+ {
+ "name": "created_at",
+ "ordinal": 3,
+ "type_info": "Timestamptz"
+ },
+ {
+ "name": "updated_at",
+ "ordinal": 4,
+ "type_info": "Timestamptz"
+ },
+ {
+ "name": "deleted_at",
+ "ordinal": 5,
+ "type_info": "Timestamptz"
+ }
+ ],
+ "nullable": [false, false, false, false, false, true],
+ "parameters": {
+ "Left": ["Uuid", "Uuid", "Bool"]
+ }
+ },
+ "query": "\nINSERT INTO user_research_consents (\n id,\n user_id,\n research_consent\n)\nVALUES ($1, $2, $3) ON CONFLICT (user_id, deleted_at) \nDO UPDATE SET research_consent = $3\nRETURNING *;\n "
+ },
"306820247b9533af5d464aa15a58f9fcde6a59b1666a3709b32bc1823ad2e970": {
"describe": {
"columns": [
diff --git a/services/headless-lms/models/src/lib.rs b/services/headless-lms/models/src/lib.rs
index e2ba28460bac..cb1a2ae29ffa 100644
--- a/services/headless-lms/models/src/lib.rs
+++ b/services/headless-lms/models/src/lib.rs
@@ -75,6 +75,7 @@ pub mod user_details;
pub mod user_exercise_slide_states;
pub mod user_exercise_states;
pub mod user_exercise_task_states;
+pub mod user_research_consents;
pub mod users;
pub mod error;
diff --git a/services/headless-lms/models/src/user_research_consents.rs b/services/headless-lms/models/src/user_research_consents.rs
new file mode 100644
index 000000000000..dcac9e5ed0cd
--- /dev/null
+++ b/services/headless-lms/models/src/user_research_consents.rs
@@ -0,0 +1,39 @@
+use crate::prelude::*;
+
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
+#[cfg_attr(feature = "ts_rs", derive(TS))]
+pub struct UserResearchConsent {
+ pub id: Uuid,
+ pub user_id: Uuid,
+ pub research_consent: bool,
+ pub created_at: DateTime,
+ pub updated_at: DateTime,
+ pub deleted_at: Option>,
+}
+
+pub async fn insert(
+ conn: &mut PgConnection,
+ pkey_policy: PKeyPolicy,
+ user_id: Uuid,
+ research_consent: bool,
+) -> ModelResult {
+ let res = sqlx::query_as!(
+ UserResearchConsent,
+ "
+INSERT INTO user_research_consents (
+ id,
+ user_id,
+ research_consent
+)
+VALUES ($1, $2, $3) ON CONFLICT (user_id, deleted_at)
+DO UPDATE SET research_consent = $3
+RETURNING *;
+ ",
+ pkey_policy.into_uuid(),
+ user_id,
+ research_consent,
+ )
+ .fetch_one(conn)
+ .await?;
+ Ok(res)
+}
diff --git a/services/headless-lms/server/src/controllers/main_frontend/users.rs b/services/headless-lms/server/src/controllers/main_frontend/users.rs
index e97c1024d474..08e56438f6da 100644
--- a/services/headless-lms/server/src/controllers/main_frontend/users.rs
+++ b/services/headless-lms/server/src/controllers/main_frontend/users.rs
@@ -1,6 +1,8 @@
-use models::{course_instance_enrollments::CourseInstanceEnrollmentsInfo, users::User};
-
use crate::prelude::*;
+use models::{
+ course_instance_enrollments::CourseInstanceEnrollmentsInfo,
+ user_research_consents::UserResearchConsent, users::User,
+};
/**
GET `/api/v0/main-frontend/users/:id`
@@ -48,9 +50,39 @@ pub async fn get_course_instance_enrollments_for_user(
token.authorized_ok(web::Json(res))
}
+/**
+POST `/api/v0/main-frontend/users/:id/user-research-consents` - Adds a research consent for a student.
+*/
+#[generated_doc]
+#[instrument(skip(pool))]
+pub async fn post_user_consents(
+ user_id: web::Path,
+ payload: web::Json,
+ pool: web::Data,
+ user: AuthUser,
+) -> ControllerResult> {
+ let mut conn = pool.acquire().await?;
+ let research_consent = payload.0;
+ let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::User).await?;
+
+ let res = models::user_research_consents::insert(
+ &mut conn,
+ PKeyPolicy::Generate,
+ user.id,
+ research_consent,
+ )
+ .await?;
+ token.authorized_ok(web::Json(res))
+}
+
pub fn _add_routes(cfg: &mut ServiceConfig) {
- cfg.route("/{user_id}", web::get().to(get_user)).route(
- "/{user_id}/course-instance-enrollments",
- web::get().to(get_course_instance_enrollments_for_user),
- );
+ cfg.route("/{user_id}", web::get().to(get_user))
+ .route(
+ "/{user_id}/course-instance-enrollments",
+ web::get().to(get_course_instance_enrollments_for_user),
+ )
+ .route(
+ "/{user_id}/user-research-consents",
+ web::post().to(post_user_consents),
+ );
}
diff --git a/services/headless-lms/server/src/ts_binding_generator.rs b/services/headless-lms/server/src/ts_binding_generator.rs
index 1b3d0cda7cef..7fd09422fd12 100644
--- a/services/headless-lms/server/src/ts_binding_generator.rs
+++ b/services/headless-lms/server/src/ts_binding_generator.rs
@@ -231,6 +231,7 @@ fn models(target: &mut File) {
user_exercise_states::UserCourseInstanceChapterExerciseProgress,
user_exercise_states::UserCourseInstanceProgress,
user_exercise_states::UserExerciseState,
+ user_research_consents::UserResearchConsent,
users::User,
page_visit_datum_summary_by_courses::PageVisitDatumSummaryByCourse,
diff --git a/services/main-frontend/src/services/backend/users.ts b/services/main-frontend/src/services/backend/users.ts
index 452698ba9a7b..8fb09a02e30e 100644
--- a/services/main-frontend/src/services/backend/users.ts
+++ b/services/main-frontend/src/services/backend/users.ts
@@ -1,5 +1,8 @@
-import { CourseInstanceEnrollmentsInfo } from "../../shared-module/bindings"
-import { isCourseInstanceEnrollmentsInfo } from "../../shared-module/bindings.guard"
+import { CourseInstanceEnrollmentsInfo, UserResearchConsent } from "../../shared-module/bindings"
+import {
+ isCourseInstanceEnrollmentsInfo,
+ isUserResearchConsent,
+} from "../../shared-module/bindings.guard"
import { validateResponse } from "../../shared-module/utils/fetching"
import { mainFrontendClient } from "../mainFrontendClient"
@@ -9,3 +12,13 @@ export async function getCourseInstanceEnrollmentsInfo(
const response = await mainFrontendClient.get(`/users/${userId}/course-instance-enrollments`)
return validateResponse(response, isCourseInstanceEnrollmentsInfo)
}
+
+export async function postUserReseachConsent(
+ userId: string,
+ consent: boolean,
+): Promise {
+ const res = await mainFrontendClient.post(`/users/${userId}/user-research-consents`, consent, {
+ responseType: "json",
+ })
+ return validateResponse(res, isUserResearchConsent)
+}
diff --git a/shared-module/src/bindings.guard.ts b/shared-module/src/bindings.guard.ts
index 639dd5b18705..766b7914bc9f 100644
--- a/shared-module/src/bindings.guard.ts
+++ b/shared-module/src/bindings.guard.ts
@@ -213,6 +213,7 @@ import {
UserInfo,
UserModuleCompletionStatus,
UserPointsUpdateStrategy,
+ UserResearchConsent,
UserRole,
UserWithModuleCompletions,
} from "./bindings"
@@ -2837,6 +2838,19 @@ export function isUserExerciseState(obj: unknown): obj is UserExerciseState {
)
}
+export function isUserResearchConsent(obj: unknown): obj is UserResearchConsent {
+ const typedObj = obj as UserResearchConsent
+ return (
+ ((typedObj !== null && typeof typedObj === "object") || typeof typedObj === "function") &&
+ typeof typedObj["id"] === "string" &&
+ typeof typedObj["user_id"] === "string" &&
+ typeof typedObj["research_consent"] === "boolean" &&
+ typedObj["created_at"] instanceof Date &&
+ typedObj["updated_at"] instanceof Date &&
+ (typedObj["deleted_at"] === null || typedObj["deleted_at"] instanceof Date)
+ )
+}
+
export function isUser(obj: unknown): obj is User {
const typedObj = obj as User
return (
diff --git a/shared-module/src/bindings.ts b/shared-module/src/bindings.ts
index f7090a69feea..a85971964ce4 100644
--- a/shared-module/src/bindings.ts
+++ b/shared-module/src/bindings.ts
@@ -1597,6 +1597,15 @@ export interface UserExerciseState {
selected_exercise_slide_id: string | null
}
+export interface UserResearchConsent {
+ id: string
+ user_id: string
+ research_consent: boolean
+ created_at: Date
+ updated_at: Date
+ deleted_at: Date | null
+}
+
export interface User {
id: string
created_at: Date
From 259b72a8a64d1830eb27dba7f8b67adde0583459 Mon Sep 17 00:00:00 2001
From: Maija Y
Date: Thu, 13 Jul 2023 17:58:44 +0300
Subject: [PATCH 05/13] research consent form on login and signup
---
services/headless-lms/models/sqlx-data.json | 123 ++++++++++++------
.../models/src/user_research_consents.rs | 20 ++-
.../src/controllers/main_frontend/users.rs | 37 +++++-
.../forms/ResearchOnCoursesForm.tsx | 41 +++---
.../CourseLanguageVersionsList.tsx | 1 -
services/main-frontend/src/pages/login.tsx | 39 +++++-
services/main-frontend/src/pages/signup.tsx | 19 ++-
.../src/services/backend/users.ts | 19 ++-
8 files changed, 222 insertions(+), 77 deletions(-)
diff --git a/services/headless-lms/models/sqlx-data.json b/services/headless-lms/models/sqlx-data.json
index cc724720e27b..7810e084bb28 100644
--- a/services/headless-lms/models/sqlx-data.json
+++ b/services/headless-lms/models/sqlx-data.json
@@ -3200,47 +3200,6 @@
},
"query": "\nUPDATE course_modules\nSET name = COALESCE($2, name),\n order_number = $3,\n uh_course_code = $4,\n ects_credits = $5,\n automatic_completion = $6,\n automatic_completion_number_of_exercises_attempted_treshold = $7,\n automatic_completion_number_of_points_treshold = $8,\n automatic_completion_requires_exam = $9,\n completion_registration_link_override = $10,\n enable_registering_completion_to_uh_open_university = $11\nWHERE id = $1\n "
},
- "2ff803033f9f7a9eca35e97ebeada5b85cc247236b7a693fd496d448ca36efec": {
- "describe": {
- "columns": [
- {
- "name": "id",
- "ordinal": 0,
- "type_info": "Uuid"
- },
- {
- "name": "user_id",
- "ordinal": 1,
- "type_info": "Uuid"
- },
- {
- "name": "research_consent",
- "ordinal": 2,
- "type_info": "Bool"
- },
- {
- "name": "created_at",
- "ordinal": 3,
- "type_info": "Timestamptz"
- },
- {
- "name": "updated_at",
- "ordinal": 4,
- "type_info": "Timestamptz"
- },
- {
- "name": "deleted_at",
- "ordinal": 5,
- "type_info": "Timestamptz"
- }
- ],
- "nullable": [false, false, false, false, false, true],
- "parameters": {
- "Left": ["Uuid", "Uuid", "Bool"]
- }
- },
- "query": "\nINSERT INTO user_research_consents (\n id,\n user_id,\n research_consent\n)\nVALUES ($1, $2, $3) ON CONFLICT (user_id, deleted_at) \nDO UPDATE SET research_consent = $3\nRETURNING *;\n "
- },
"306820247b9533af5d464aa15a58f9fcde6a59b1666a3709b32bc1823ad2e970": {
"describe": {
"columns": [
@@ -15052,6 +15011,47 @@
},
"query": "\nSELECT id,\ncreated_at,\nupdated_at,\nexercise_task_submission_id,\ncourse_id,\nexam_id,\nexercise_id,\nexercise_task_id,\ngrading_priority,\nscore_given,\ngrading_progress as \"grading_progress: _\",\nunscaled_score_given,\nunscaled_score_maximum,\ngrading_started_at,\ngrading_completed_at,\nfeedback_json,\nfeedback_text,\ndeleted_at\nFROM exercise_task_gradings\nWHERE deleted_at IS NULL\n AND exercise_task_submission_id IN (\n SELECT id\n FROM exercise_task_submissions\n WHERE exercise_slide_submission_id = $1\n )\n"
},
+ "a8ea36034543bcc580413790b2bf159402da39f943469c0fe24efc63597b07e1": {
+ "describe": {
+ "columns": [
+ {
+ "name": "id",
+ "ordinal": 0,
+ "type_info": "Uuid"
+ },
+ {
+ "name": "user_id",
+ "ordinal": 1,
+ "type_info": "Uuid"
+ },
+ {
+ "name": "research_consent",
+ "ordinal": 2,
+ "type_info": "Bool"
+ },
+ {
+ "name": "created_at",
+ "ordinal": 3,
+ "type_info": "Timestamptz"
+ },
+ {
+ "name": "updated_at",
+ "ordinal": 4,
+ "type_info": "Timestamptz"
+ },
+ {
+ "name": "deleted_at",
+ "ordinal": 5,
+ "type_info": "Timestamptz"
+ }
+ ],
+ "nullable": [false, false, false, false, false, true],
+ "parameters": {
+ "Left": ["Uuid", "Uuid", "Bool"]
+ }
+ },
+ "query": "\nINSERT INTO user_research_consents (\n id,\n user_id,\n research_consent\n)\nVALUES ($1, $2, $3) ON CONFLICT (user_id, deleted_at)\nDO UPDATE SET research_consent = $3\nRETURNING *;\n "
+ },
"a963c734bc1f9c48be192fa5c8537d461c9ded7412268b17b64085e4f44b174a": {
"describe": {
"columns": [
@@ -19031,6 +19031,47 @@
},
"query": "\nSELECT users.id,\n user_details.first_name,\n user_details.last_name,\n user_details.email,\n role AS \"role: UserRole\"\nFROM users\n JOIN roles ON users.id = roles.user_id\n JOIN user_details ON users.id = user_details.user_id\nWHERE roles.organization_id = $1\nAND roles.deleted_at IS NULL\n"
},
+ "d43f160bfad3d375a88a2fe2226a163ecf5e6b4b809434ec06d5d3554461af2c": {
+ "describe": {
+ "columns": [
+ {
+ "name": "id",
+ "ordinal": 0,
+ "type_info": "Uuid"
+ },
+ {
+ "name": "user_id",
+ "ordinal": 1,
+ "type_info": "Uuid"
+ },
+ {
+ "name": "research_consent",
+ "ordinal": 2,
+ "type_info": "Bool"
+ },
+ {
+ "name": "created_at",
+ "ordinal": 3,
+ "type_info": "Timestamptz"
+ },
+ {
+ "name": "updated_at",
+ "ordinal": 4,
+ "type_info": "Timestamptz"
+ },
+ {
+ "name": "deleted_at",
+ "ordinal": 5,
+ "type_info": "Timestamptz"
+ }
+ ],
+ "nullable": [false, false, false, false, false, true],
+ "parameters": {
+ "Left": ["Uuid"]
+ }
+ },
+ "query": "\nSELECT *\nFROM user_research_consents\nWHERE user_id = $1\n "
+ },
"d565b3ca168a0beb24ee0a58c078fd031a52cd26a807c348bdb3fac1849bd456": {
"describe": {
"columns": [
diff --git a/services/headless-lms/models/src/user_research_consents.rs b/services/headless-lms/models/src/user_research_consents.rs
index dcac9e5ed0cd..cea8eee51094 100644
--- a/services/headless-lms/models/src/user_research_consents.rs
+++ b/services/headless-lms/models/src/user_research_consents.rs
@@ -25,7 +25,7 @@ INSERT INTO user_research_consents (
user_id,
research_consent
)
-VALUES ($1, $2, $3) ON CONFLICT (user_id, deleted_at)
+VALUES ($1, $2, $3) ON CONFLICT (user_id, deleted_at)
DO UPDATE SET research_consent = $3
RETURNING *;
",
@@ -37,3 +37,21 @@ RETURNING *;
.await?;
Ok(res)
}
+
+pub async fn get_research_consent_by_user_id(
+ conn: &mut PgConnection,
+ user_id: Uuid,
+) -> ModelResult {
+ let res = sqlx::query_as!(
+ UserResearchConsent,
+ "
+SELECT *
+FROM user_research_consents
+WHERE user_id = $1
+ ",
+ user_id,
+ )
+ .fetch_one(conn)
+ .await?;
+ Ok(res)
+}
diff --git a/services/headless-lms/server/src/controllers/main_frontend/users.rs b/services/headless-lms/server/src/controllers/main_frontend/users.rs
index 08e56438f6da..c118bb73f0b2 100644
--- a/services/headless-lms/server/src/controllers/main_frontend/users.rs
+++ b/services/headless-lms/server/src/controllers/main_frontend/users.rs
@@ -50,6 +50,12 @@ pub async fn get_course_instance_enrollments_for_user(
token.authorized_ok(web::Json(res))
}
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
+#[cfg_attr(feature = "ts_rs", derive(TS))]
+pub struct ConsentData {
+ pub consent: bool,
+}
+
/**
POST `/api/v0/main-frontend/users/:id/user-research-consents` - Adds a research consent for a student.
*/
@@ -57,24 +63,39 @@ POST `/api/v0/main-frontend/users/:id/user-research-consents` - Adds a research
#[instrument(skip(pool))]
pub async fn post_user_consents(
user_id: web::Path,
- payload: web::Json,
+ payload: web::Json,
pool: web::Data,
- user: AuthUser,
) -> ControllerResult> {
let mut conn = pool.acquire().await?;
- let research_consent = payload.0;
- let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::User).await?;
+ let token = skip_authorize();
let res = models::user_research_consents::insert(
&mut conn,
PKeyPolicy::Generate,
- user.id,
- research_consent,
+ *user_id,
+ payload.consent,
)
.await?;
token.authorized_ok(web::Json(res))
}
+/**
+GET `/api/v0/main-frontend/users/:id/get-user-research-consent` - Gets users research consent.
+*/
+#[generated_doc]
+#[instrument(skip(pool))]
+pub async fn get_research_consent_by_user_id(
+ user_id: web::Path,
+ pool: web::Data,
+) -> ControllerResult> {
+ let mut conn = pool.acquire().await?;
+ let token = skip_authorize();
+
+ let res = models::user_research_consents::get_research_consent_by_user_id(&mut conn, *user_id)
+ .await?;
+ token.authorized_ok(web::Json(res))
+}
+
pub fn _add_routes(cfg: &mut ServiceConfig) {
cfg.route("/{user_id}", web::get().to(get_user))
.route(
@@ -84,5 +105,9 @@ pub fn _add_routes(cfg: &mut ServiceConfig) {
.route(
"/{user_id}/user-research-consents",
web::post().to(post_user_consents),
+ )
+ .route(
+ "/{user_id}/get-user-research-consent",
+ web::get().to(get_research_consent_by_user_id),
);
}
diff --git a/services/main-frontend/src/components/forms/ResearchOnCoursesForm.tsx b/services/main-frontend/src/components/forms/ResearchOnCoursesForm.tsx
index 09c941a5ed99..7e2cc00a8bc2 100644
--- a/services/main-frontend/src/components/forms/ResearchOnCoursesForm.tsx
+++ b/services/main-frontend/src/components/forms/ResearchOnCoursesForm.tsx
@@ -1,44 +1,49 @@
/* eslint-disable i18next/no-literal-string */
import { css } from "@emotion/css"
+import { useQuery } from "@tanstack/react-query"
import React, { useState } from "react"
-import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
+import { postUserResearchConsent } from "../../services/backend/users"
import Button from "../../shared-module/components/Button"
import Dialog from "../../shared-module/components/Dialog"
import RadioButton from "../../shared-module/components/InputFields/RadioButton"
import ClipboardIcon from "../../shared-module/img/clipboard-icon.svg"
import { baseTheme, fontWeights, headingFont } from "../../shared-module/styles"
-interface ResearchOnCoursesFields {
- consent: boolean
- user_id: string
-}
-
interface ResearchOnCoursesFormProps {
- user_id?: string
+ afterSubmit?: () => void
+ userId: string
}
const ResearchOnCoursesForm: React.FC> = ({
- user_id,
+ afterSubmit,
+ userId,
}) => {
- const {
- handleSubmit,
- formState: { errors },
- } = useForm()
const { t } = useTranslation()
const [researchConsentFormOpen, setResearchConsentFormOpen] = useState(true)
- const [consent, setConsent] = useState(false)
+ const [consent, setConsent] = useState(false)
+
+ const consentQuery = useQuery({
+ queryKey: [`users-${userId}-user-research-consents`],
+ queryFn: () => postUserResearchConsent(userId, consent),
+ enabled: false,
+ })
- const handleOnSubmit = handleSubmit(() => {
+ const handleOnSubmit = () => {
setResearchConsentFormOpen(false)
- console.log("Closed: ", consent, user_id, errors)
+ consentQuery.refetch()
+ if (afterSubmit != undefined) {
+ afterSubmit()
+ }
setConsent(false)
- })
+ }
const handleOnCancel = () => {
setResearchConsentFormOpen(false)
- setConsent(false)
+ if (afterSubmit != undefined) {
+ afterSubmit()
+ }
}
const handleConsentSelection = (value: boolean) => {
@@ -47,7 +52,7 @@ const ResearchOnCoursesForm: React.FC
-