Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature:HelpQueue] Setting required contact info per queue #7128

Merged
merged 41 commits into from Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
43dd312
twig fixes
loadedskiffer Jul 29, 2021
2d07eca
Revert "twig fixes"
loadedskiffer Jul 29, 2021
eafe21b
Merge branch 'master' of https://github.com/Submitty/Submitty
loadedskiffer Jul 30, 2021
9647971
Merge branch 'master' of https://github.com/Submitty/Submitty
loadedskiffer Jul 30, 2021
20c7c1d
Merge branch 'master' of https://github.com/Submitty/Submitty
loadedskiffer Aug 2, 2021
2fc22b9
Merge branch 'master' of https://github.com/Submitty/Submitty
loadedskiffer Sep 1, 2021
aa4927b
Merge branch 'master' of https://github.com/Submitty/Submitty
loadedskiffer Sep 3, 2021
bdad325
Merge branch 'master' of https://github.com/Submitty/Submitty
loadedskiffer Sep 6, 2021
4483d4a
Merge branch 'master' of https://github.com/Submitty/Submitty
loadedskiffer Sep 8, 2021
9f214d9
add alert message
loadedskiffer Sep 8, 2021
d2931b9
lint
loadedskiffer Sep 8, 2021
b3a45b6
requested changes
loadedskiffer Sep 10, 2021
1872b16
add tooltip
loadedskiffer Sep 15, 2021
9276513
Merge branch 'master' into locked-gradeable-message
loadedskiffer Sep 20, 2021
eed421e
change tooltip
loadedskiffer Sep 21, 2021
3852134
new add queue modal
loadedskiffer Sep 21, 2021
38351c9
get post request
loadedskiffer Sep 21, 2021
3817eb2
database migration
loadedskiffer Sep 22, 2021
8c6d11b
new ui for joining queue
loadedskiffer Sep 23, 2021
344b622
allow students to join without info
loadedskiffer Sep 23, 2021
ef3c5d6
remove contact information from queue settings
loadedskiffer Sep 23, 2021
ada646b
allow editing of contact info field
loadedskiffer Sep 23, 2021
3d55ff6
enhance edit queue UI
loadedskiffer Sep 23, 2021
e0122bf
linter
loadedskiffer Sep 23, 2021
735255d
fix unit tests
loadedskiffer Sep 23, 2021
35171c4
remove tests for course config
loadedskiffer Sep 24, 2021
70fa5e7
fix e2e
loadedskiffer Sep 24, 2021
04b3fcf
fix accesibilty test
loadedskiffer Sep 25, 2021
81347fc
requested changes
loadedskiffer Sep 25, 2021
0a0789b
Merge branch 'master' into refactor-queue-contact-info
loadedskiffer Sep 25, 2021
f660efc
linter again
loadedskiffer Sep 25, 2021
87212cd
Merge branch 'refactor-queue-contact-info' of https://github.com/Subm…
loadedskiffer Sep 25, 2021
fd9cbb3
e2e again
loadedskiffer Sep 25, 2021
ea68544
hopefully fix accesibilty check
loadedskiffer Sep 25, 2021
252bff4
fix col length
loadedskiffer Sep 25, 2021
969b017
Merge branch 'master' into refactor-queue-contact-info
loadedskiffer Oct 1, 2021
061006c
requested changes
loadedskiffer Oct 1, 2021
c436d90
fix migration
loadedskiffer Oct 11, 2021
a9ca759
fix migration
loadedskiffer Oct 11, 2021
43f719c
add check for field in migration
loadedskiffer Oct 12, 2021
6d074ac
Update migration/migrator/migrations/course/20210921195244_add_queue_…
MasterOdin Oct 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion .setup/bin/setup_sample_courses.py
Expand Up @@ -1309,7 +1309,6 @@ def add_sample_queue_data(self):
with open(course_json_file, 'r+') as open_file:
course_json = json.load(open_file)
course_json['course_details']['queue_enabled'] = True
course_json['course_details']['queue_contact_info'] = True
course_json['course_details']['queue_message'] = queue_data["queue_message"]
course_json['course_details']['queue_announcement_message'] = queue_data["queue_announcement_message"]
open_file.seek(0)
Expand Down
3 changes: 2 additions & 1 deletion migration/migrator/data/course_tables.sql
Expand Up @@ -927,7 +927,8 @@ CREATE TABLE public.queue_settings (
open boolean NOT NULL,
code text NOT NULL,
token text NOT NULL,
regex_pattern character varying
regex_pattern character varying,
contact_information boolean DEFAULT true NOT NULL
);


Expand Down
@@ -0,0 +1,49 @@
"""Migration for a given Submitty course database."""
from pathlib import Path
import json

def up(config, database, semester, course):
"""
Run up migration.

:param config: Object holding configuration details about Submitty
:type config: migrator.config.Config
:param database: Object for interacting with given database for environment
:type database: migrator.db.Database
:param semester: Semester of the course being migrated
:type semester: str
:param course: Code of course being migrated
:type course: str
"""
sql = "ALTER TABLE queue_settings ADD COLUMN IF NOT EXISTS contact_information BOOLEAN NOT NULL DEFAULT TRUE;"
database.execute(sql)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have a follow-up query where we check the current value of course_config['course_details']['queue_contact_info'] and update all existing queue_settings in the DB to be true/false respectively.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed queue_contact_information from the course config, will the new migration still work when it gets installed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should. If you wanted to have extra confidence, add an existence check for the field before attempting to read it.

course_dir = Path(config.submitty['submitty_data_dir'], 'courses', semester, course)
# add boolean to course config
config_file = Path(course_dir, 'config', 'config.json')
if config_file.is_file():
with config_file.open() as in_file:
j = json.load(in_file)
if 'queue_contact_info' in j['course_details']:
contact_information_enabled = j['course_details']['queue_contact_info']
query = """
UPDATE queue_settings
SET contact_information = :contact_information_enabled;
"""
params = {'contact_information_enabled' : contact_information_enabled}
database.session.execute(query, params)


def down(config, database, semester, course):
"""
Run down migration (rollback).

:param config: Object holding configuration details about Submitty
:type config: migrator.config.Config
:param database: Object for interacting with given database for environment
:type database: migrator.db.Database
:param semester: Semester of the course being migrated
:type semester: str
:param course: Code of course being migrated
:type course: str
"""
pass
29 changes: 25 additions & 4 deletions site/app/controllers/OfficeHoursQueueController.php
Expand Up @@ -57,6 +57,8 @@ public function openQueue() {
);
}

$require_contact_info = isset($_POST['require_contact_information']);

//Replace whitespace with "_"
$queue_code = trim($_POST['code']);
$token = trim($_POST['token'] ?? '');
Expand All @@ -74,7 +76,7 @@ public function openQueue() {
);
}
$regex_pattern = isset($_POST['regex']) ? trim($_POST['regex']) : '';
if ($this->core->getQueries()->openQueue($queue_code, $token, $regex_pattern)) {
if ($this->core->getQueries()->openQueue($queue_code, $token, $regex_pattern, $require_contact_info)) {
$this->core->addSuccessMessage("New queue added");
Logger::logQueueActivity($this->core->getConfig()->getSemester(), $this->core->getDisplayedCourseName(), $queue_code, "CREATED");
}
Expand Down Expand Up @@ -107,9 +109,10 @@ public function addPerson($queue_code) {
);
}


$contact_info = null;
if ($this->core->getConfig()->getQueueContactInfo()) {
if (empty($_POST['contact_info'])) {
if ($this->core->getQueries()->getQueueHasContactInformation($queue_code)) {
if (!isset($_POST['contact_info'])) {
$this->core->addErrorMessage("Missing contact info");
return MultiResponse::RedirectOnlyResponse(
new RedirectResponse($this->core->buildCourseUrl(['office_hours_queue']))
Expand All @@ -130,7 +133,6 @@ public function addPerson($queue_code) {
}
}
}

$queue_code = trim($queue_code);
$token = trim($_POST['token'] ?? '');

Expand Down Expand Up @@ -441,6 +443,25 @@ public function changeRegex($queue_code) {
);
}

/**
* @Route("/courses/{_semester}/{_course}/office_hours_queue/{queue_code}/change_contact_information", methods={"POST"})
* @AccessControl(role="LIMITED_ACCESS_GRADER")
* @return RedirectResponse
*/
public function changeContactInformation($queue_code) {
if (!isset($queue_code)) {
$this->core->addErrorMessage("Missing queue name");
shailpatels marked this conversation as resolved.
Show resolved Hide resolved
return new RedirectResponse($this->core->buildCourseUrl(['office_hours_queue']));
}

$contact_information = $_POST['contact_information'] === "true";

$queue_code = trim($_POST['code']);
$this->core->getQueries()->changeQueueContactInformation($contact_information, $queue_code);
$this->core->addSuccessMessage("Queue Contact Information Changed");
return new RedirectResponse($this->core->buildCourseUrl(['office_hours_queue']));
}

/**
* @Route("/courses/{_semester}/{_course}/office_hours_queue/current_queue", methods={"GET"})
* @return MultiResponse
Expand Down
2 changes: 0 additions & 2 deletions site/app/controllers/admin/ConfigurationController.php
Expand Up @@ -53,7 +53,6 @@ public function viewConfiguration(): MultiResponse {
'seating_only_for_instructor' => $this->core->getConfig()->isSeatingOnlyForInstructor(),
'auto_rainbow_grades' => $this->core->getConfig()->getAutoRainbowGrades(),
'queue_enabled' => $this->core->getConfig()->isQueueEnabled(),
'queue_contact_info' => $this->core->getConfig()->getQueueContactInfo(),
'queue_message' => $this->core->getConfig()->getQueueMessage(),
'seek_message_enabled' => $this->core->getConfig()->isSeekMessageEnabled(),
'seek_message_instructions' => $this->core->getConfig()->getSeekMessageInstructions(),
Expand Down Expand Up @@ -148,7 +147,6 @@ public function updateConfiguration(): MultiResponse {
'regrade_enabled',
'seating_only_for_instructor',
'queue_enabled',
'queue_contact_info',
'seek_message_enabled',
'polls_enabled'
]
Expand Down
12 changes: 10 additions & 2 deletions site/app/libraries/database/DatabaseQueries.php
Expand Up @@ -6188,15 +6188,15 @@ public function isAnyQueueOpen() {
}


public function openQueue($queue_code, $token, $regex_pattern) {
public function openQueue($queue_code, $token, $regex_pattern, $require_contact_info) {
$this->course_db->query("SELECT * FROM queue_settings WHERE UPPER(TRIM(code)) = UPPER(TRIM(?))", [$queue_code]);

//cannot have more than one queue with the same code
if (0 < count($this->course_db->rows())) {
return false;
}

$this->course_db->query("INSERT INTO queue_settings (open,code,token,regex_pattern) VALUES (TRUE, TRIM(?), TRIM(?), ?)", [$queue_code,$token,$regex_pattern]);
$this->course_db->query("INSERT INTO queue_settings (open,code,token,regex_pattern, contact_information) VALUES (TRUE, TRIM(?), TRIM(?), ?, ?)", [$queue_code,$token,$regex_pattern,$require_contact_info]);
return true;
}

Expand Down Expand Up @@ -6251,6 +6251,11 @@ public function getQueueId($queue_code) {
return $this->course_db->rows()[0]['id'];
}

public function getQueueHasContactInformation(string $queue_code) {
$this->course_db->query("select * from queue_settings where code = ?;", [$queue_code]);
shailpatels marked this conversation as resolved.
Show resolved Hide resolved
return $this->course_db->rows()[0]['contact_information'];
}

public function addToQueue($queue_code, $user_id, $name, $contact_info) {
$last_time_in_queue = $this->getLastTimeInQueue($user_id, $queue_code);
$this->course_db->query("INSERT INTO queue
Expand Down Expand Up @@ -6433,6 +6438,9 @@ public function changeQueueRegex($regex_pattern, $queue_code) {
$this->course_db->query("UPDATE queue_settings SET regex_pattern = ? WHERE code = ?", [$regex_pattern, $queue_code]);
}

public function changeQueueContactInformation(bool $contact_information, string $queue_code) {
$this->course_db->query("UPDATE queue_settings SET contact_information = ? WHERE code = ?", [$contact_information, $queue_code]);
}

public function getNumberAheadInQueueThisWeek($queue_code, $time_in) {
$day_threshold = $this->core->getDateTimeNow()->modify('-4 day')->format('Y-m-d 00:00:00O');
Expand Down
6 changes: 1 addition & 5 deletions site/app/models/Config.php
Expand Up @@ -64,7 +64,6 @@
* @method string getAutoRainbowGrades()
* @method string|null getVerifiedSubmittyAdminUser()
* @method bool isQueueEnabled()
* @method bool getQueueContactInfo()
* @method bool isSeekMessageEnabled()
* @method bool isPollsEnabled()
* @method void setSemester(string $semester)
Expand Down Expand Up @@ -251,8 +250,6 @@ class Config extends AbstractModel {
protected $queue_enabled;
/** @prop @var bool */
protected $seek_message_enabled;
/** @prop @var bool */
protected $queue_contact_info;
/** @prop @var string */
protected $queue_message;
/** @prop @var string */
Expand Down Expand Up @@ -470,7 +467,7 @@ public function loadCourseJson($semester, $course, $course_json_path) {
'zero_rubric_grades', 'upload_message', 'display_rainbow_grades_summary',
'display_custom_message', 'room_seating_gradeable_id', 'course_email', 'vcs_base_url', 'vcs_type',
'private_repository', 'forum_enabled', 'forum_create_thread_message', 'regrade_enabled', 'seating_only_for_instructor',
'regrade_message', 'auto_rainbow_grades', 'queue_enabled', 'queue_contact_info', 'queue_message', 'polls_enabled', 'queue_announcement_message', 'seek_message_enabled', 'seek_message_instructions'
'regrade_message', 'auto_rainbow_grades', 'queue_enabled', 'queue_message', 'polls_enabled', 'queue_announcement_message', 'seek_message_enabled', 'seek_message_instructions'
];
$this->setConfigValues($this->course_json, 'course_details', $array);

Expand Down Expand Up @@ -499,7 +496,6 @@ public function loadCourseJson($semester, $course, $course_json_path) {
'regrade_enabled',
'seating_only_for_instructor',
'queue_enabled',
'queue_contact_info',
'polls_enabled',
'seek_message_enabled',
];
Expand Down
4 changes: 0 additions & 4 deletions site/app/models/OfficeHoursQueueModel.php
Expand Up @@ -238,10 +238,6 @@ public function getColorFromCode($code) {
return $this->colors[$this->getIndexFromCode($code)];
}

public function isContactInfoEnabled() {
return $this->core->getConfig()->getQueueContactInfo();
}

public function getQueueMessage() {
return $this->core->getConfig()->getQueueMessage();
}
Expand Down
8 changes: 0 additions & 8 deletions site/app/templates/admin/Configuration.twig
Expand Up @@ -54,14 +54,6 @@
<span class="option-alt">Choose whether to enable an Office Hours Queue for this course.</span>
</label>
</div>
<div id="queue-contact-info-wrapper" class="config-row checkbox-row">
<input type="checkbox" name="queue_contact_info" id="queue-contact-info" value="true" {{ fields['queue_contact_info'] ? 'checked': '' }} />
<label for="queue-contact-info">
<span class="option-title">Contact Info for Office Hours Queue</span>
<br>
<span class="option-alt">Check this box to require that students specify their contact information for office hours help (e.g., email address, video chat, virtual meeting URL, phone number). This setting should only be enabled if the queue is used for remote or online instruction and tutoring.</san>
</label>
</div>

<label for="queue-message" class="config-row">
<span class="option-title">Office Hours Queue Welcome Message (Optional)</span>
Expand Down
8 changes: 4 additions & 4 deletions site/app/templates/officeHoursQueue/CurrentQueue.twig
Expand Up @@ -88,10 +88,10 @@
</form>
</td>
</tr>
{% if viewer.isContactInfoEnabled() %}
<tr class="contact_info row_color_{{viewer.getIndexFromCode(entry['queue_code'])}} queue_current_{{viewer.cleanForId(entry['queue_code'])}} current_queue_row" style="display:none;border-top:0px">
<td colspan="8" style="border-top:0px; word-wrap:anywhere;">{{entry['contact_info']}}</td>
</tr>
{% if entry['contact_info'] != '' %}
<tr class="contact_info row_color_{{viewer.getIndexFromCode(entry['queue_code'])}} queue_current_{{viewer.cleanForId(entry['queue_code'])}} current_queue_row" style="display:none;border-top:0px">
<td colspan="7" style="border-top:0px; word-wrap:anywhere;">{{entry['contact_info']}}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
Expand Down
25 changes: 15 additions & 10 deletions site/app/templates/officeHoursQueue/NewQueue.twig
Expand Up @@ -13,21 +13,22 @@
</a><br>
<p>NOTE: Access Codes are optional</p>
</div>
{% if viewer.isContactInfoEnabled() %}
<br>
Require contact information
<input type="checkbox" name="require_contact_information" onclick="toggleQueueRegex()"><br>
<div class="contact-information-regex" style="display: none">
<input type="text" id="regex" name="regex" placeholder="Contact Info Regex Pattern" aria-label="Regex" style="min-width: 45%; margin-top: 1rem;">

<div class="regex-help">
<br>You may optionally specify a regular expression to ensure your students enter the requested
format for the contact information. For example: <br>
<b>Gmail:&nbsp;&nbsp;</b> <tt>.+@gmail.com</tt><br>
<b>Email:&nbsp;&nbsp;</b> <tt>.+@.+\..+</tt><br>
<b>Webex Personal Room:&nbsp;&nbsp;</b> <tt>^https:\/\/.+\.webex\.com\/meet\/.*</tt><br>
<b>Webex Meeting:&nbsp;&nbsp;</b> <tt>^https:\/\/.+\.webex\.com\/.*</tt><br>
<b>Skype link:&nbsp;&nbsp;</b> <tt>.*join\.skype\.com.*</tt><br>
<b>Zoom link:&nbsp;&nbsp;</b> <tt>.*zoom\.us.*</tt><br><br>
<b>Gmail:&nbsp;&nbsp;</b> .+@gmail.com<br>
<b>Email:&nbsp;&nbsp;</b> .+@.+\..+<br>
<b>Webex Personal Room:&nbsp;&nbsp;</b> ^https:\/\/.+\.webex\.com\/meet\/.*<br>
<b>Webex Meeting:&nbsp;&nbsp;</b> ^https:\/\/.+\.webex\.com\/.*<br>
<b>Skype link:&nbsp;&nbsp;</b> .*join\.skype\.com.*<br>
<b>Zoom link:&nbsp;&nbsp;</b> .*zoom\.us.* <br><br>
</div>
{% endif %}
<br>
</div>
<button id="open_new_queue_btn" type="submit" class="btn btn-primary" >Open New Queue</button>
</form>
<script>
Expand All @@ -38,6 +39,10 @@
}).mouseup(function(ev) {
queueDraggable.draggable('enable');
});

function toggleQueueRegex(){
$('.contact-information-regex').toggle();
}
</script>
{% endblock %}
{% block form %}
Expand Down
53 changes: 36 additions & 17 deletions site/app/templates/officeHoursQueue/QueueFilter.twig
Expand Up @@ -19,22 +19,38 @@
<button id="change_code_btn" type="submit" class="btn btn-primary">Change Queue Access Code</button>
</form>
<br>
{% if viewer.isContactInfoEnabled() %}
<hr>
<span class="option-title">Modify regex pattern</span>
<form method="post" id="change_queue_regex" action="{{base_url}}/no_code_added/change_regex" style="height:auto;">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<select id="old_queue_code_2" class="form-control" name="code" aria-label="Access Code" required="required">
<option value="">Queue Name</option>
{% for queue in viewer.getAllQueues() %}
<option value="{{queue['code']}}">{{queue['code']}}</option>
{% endfor %}
</select>
<input type="text" id="old_queue_regex" name="regex" placeholder="Contact Info Regex Pattern" aria-label="New Access Code" required="required" style="min-width: 40%;">
<button id="change_regex_btn" type="submit" class="btn btn-primary">Change Queue Regex Pattern </button>
</form>
<br>
{% endif %}
<hr>
<span class="option-title">Modify Contact Information</span>
<form method="post" id="change_queue_contact_information" action="{{base_url}}/no_code_added/change_contact_information" style="height:auto;">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<select id="old_queue_code_1" class="form-control" name="code" aria-label="Access Code" required="required">
<option value="">Queue Name</option>
{% for queue in viewer.getAllQueues() %}
<option value="{{queue['code']}}">{{queue['code']}}</option>
{% endfor %}
</select>
<select id="old_queue_contact_information" name="contact_information" aria-label="New Access Code" required="required">
<option value="">choose option</option>
<option value="true"> On </option>
<option value="false"> Off </option>
</select>
<button id="change_contact_information_btn" type="submit" class="btn btn-primary">Change Queue Contact Information</button>
</form>
<br>
<form method="post" id="change_queue_regex" action="{{base_url}}/no_code_added/change_regex" style="height:auto;">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}"/>
<select id="old_queue_code_2" class="form-control" name="code" aria-label="Access Code" required="required">
<option value="">Queue Name</option>
{% for queue in viewer.getAllQueues() %}
<option value="{{queue['code']}}">{{queue['code']}}</option>
{% endfor %}
</select>
<input type="text" id="old_queue_regex" name="regex" placeholder="Contact Info Regex Pattern" aria-label="New Access Code" required="required" style="min-width: 40%;">
<button id="change_regex_btn" type="submit" class="btn btn-primary">Change Queue Regex Pattern </button>
</form>
<hr>

<br>
<span class="option-title"> Modify queue status </span>
<table class="table table-striped" style="width:100%;">
<thead>
Expand All @@ -51,9 +67,12 @@
<td style="text-align: left;">
<b>Name: </b>{{queue['code']}}<br>
<b>Access Code: </b>{{queue['token']}}<br>
{% if queue['regex_pattern'] is defined and queue['regex_pattern'] != '' %}
<b>Contact Information: </b>{{ queue['contact_information'] ? "On" : "Off" }}<br>
{% if queue['regex_pattern'] is defined and queue['regex_pattern'] != '' and queue['contact_information'] %}
<b>Regex Pattern: </b>{{queue['regex_pattern']}}</td>
{% endif %}


<td>
<input type="checkbox" class="toggle-queue-checkbox" aria-label="Toggle open/close for: {{queue['code']|upper}}" id="toggle-queue-{{loop.index}}" onchange="toggleQueue({{loop.index}}, '{{queue['code']}}')" {% if queue['open'] %} checked {% endif %} />
</td>
Expand Down