Skip to content

Commit 8a23597

Browse files
authored
Merge pull request #2232 from drgrice1/job-manager
Add a page to manage jobs in the job queue.
2 parents d8a32be + 4580b98 commit 8a23597

File tree

24 files changed

+885
-110
lines changed

24 files changed

+885
-110
lines changed

htdocs/js/JobManager/jobmanager.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(() => {
2+
// Show/hide the filter elements depending on if the field matching option is selected.
3+
const filter_select = document.getElementById('filter_select');
4+
const filter_elements = document.getElementById('filter_elements');
5+
if (filter_select && filter_elements) {
6+
const toggle_filter_elements = () => {
7+
if (filter_select.value === 'match_regex') filter_elements.style.display = 'block';
8+
else filter_elements.style.display = 'none';
9+
};
10+
filter_select.addEventListener('change', toggle_filter_elements);
11+
toggle_filter_elements();
12+
}
13+
})();

lib/Mojolicious/WeBWorK.pm

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use Env qw(WEBWORK_SERVER_ADMIN);
2626

2727
use WeBWorK;
2828
use WeBWorK::CourseEnvironment;
29-
use WeBWorK::Utils qw(x writeTimingLogEntry);
29+
use WeBWorK::Utils qw(writeTimingLogEntry);
3030
use WeBWorK::Utils::Routes qw(setup_content_generator_routes);
3131

3232
sub startup ($app) {
@@ -84,7 +84,8 @@ sub startup ($app) {
8484
# Add the themes directory to the template search paths.
8585
push(@{ $app->renderer->paths }, $ce->{webworkDirs}{themes});
8686

87-
# Setup the Minion job queue.
87+
# Setup the Minion job queue. Make sure that any task added here is represented in the TASK_NAMES hash in
88+
# WeBWorK::ContentGenerator::Instructor::JobManager.
8889
$app->plugin(Minion => { $ce->{job_queue}{backend} => $ce->{job_queue}{database_dsn} });
8990
$app->minion->add_task(lti_mass_update => 'Mojolicious::WeBWorK::Tasks::LTIMassUpdate');
9091
$app->minion->add_task(send_instructor_email => 'Mojolicious::WeBWorK::Tasks::SendInstructorEmail');

lib/Mojolicious/WeBWorK/Tasks/AchievementNotification.pm

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,30 +31,31 @@ use WeBWorK::SafeTemplate;
3131

3232
# send student notification that they have earned an achievement
3333
sub run ($job, $mail_data) {
34-
my $ce = eval { WeBWorK::CourseEnvironment->new({ courseName => $mail_data->{courseName} }); };
35-
return $job->fail("Could not construct course environment for $mail_data->{courseName}.")
34+
my $courseID = $job->info->{notes}{courseID};
35+
36+
my $ce = eval { WeBWorK::CourseEnvironment->new({ courseName => $courseID }); };
37+
return $job->fail("Could not construct course environment for $courseID.")
3638
unless $ce;
3739

38-
$job->{language_handle} =
39-
WeBWorK::Localize::getLoc($ce->{language} || 'en');
40+
$job->{language_handle} = WeBWorK::Localize::getLoc($ce->{language} || 'en');
4041

4142
my $db = WeBWorK::DB->new($ce->{dbLayout});
42-
return $job->fail($job->maketext("Could not obtain database connection for [_1].", $mail_data->{courseName}))
43+
return $job->fail($job->maketext('Could not obtain database connection for [_1].', $courseID))
4344
unless $db;
4445

45-
return $job->fail($job->maketext("Cannot notify student without an achievement."))
46+
return $job->fail($job->maketext('Cannot notify student without an achievement.'))
4647
unless $mail_data->{achievementID};
4748
$mail_data->{achievement} = $db->getAchievement($mail_data->{achievementID});
48-
return $job->fail($job->maketext("Could not find achievement [_1].", $mail_data->{achievementID}))
49+
return $job->fail($job->maketext('Could not find achievement [_1].', $mail_data->{achievementID}))
4950
unless $mail_data->{achievement};
5051

5152
my $result_message = eval { $job->send_achievement_notification($ce, $db, $mail_data) };
5253
if ($@) {
53-
$job->app->log->error($job->maketext("An error occurred while trying to send email: $@"));
54-
return $job->fail($job->maketext("An error occurred while trying to send email: [_1]", $@));
54+
$job->app->log->error("An error occurred while trying to send email: $@");
55+
return $job->fail($job->maketext('An error occurred while trying to send email: [_1]', $@));
5556
}
5657
$job->app->log->info("Message sent to $mail_data->{recipient}");
57-
return $job->finish($job->maketext("Message sent to [_1]", $mail_data->{recipient}));
58+
return $job->finish($result_message);
5859
}
5960

6061
sub send_achievement_notification ($job, $ce, $db, $mail_data) {

lib/Mojolicious/WeBWorK/Tasks/LTIMassUpdate.pm

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,29 @@ use WeBWorK::CourseEnvironment;
2222
use WeBWorK::DB;
2323

2424
# Perform a mass update of grades via LTI.
25-
sub run ($job, $courseID, $userID = '', $setID = '') {
26-
# Establish a lock guard that only allow 1 job at a time (technichally more than one could run at a time if a job
25+
sub run ($job, $userID = '', $setID = '') {
26+
# Establish a lock guard that only allows 1 job at a time (technically more than one could run at a time if a job
2727
# takes more than an hour to complete). As soon as a job completes (or fails) the lock is released and a new job
28-
# can start. New jobs retry every minute until they can aquire their own lock.
28+
# can start. New jobs retry every minute until they can acquire their own lock.
2929
return $job->retry({ delay => 60 }) unless my $guard = $job->minion->guard('lti_mass_update', 3600);
3030

31+
my $courseID = $job->info->{notes}{courseID};
32+
return $job->fail('The course id was not passed when this job was enqueued.') unless $courseID;
33+
3134
my $ce = eval { WeBWorK::CourseEnvironment->new({ courseName => $courseID }) };
32-
return $job->fail("Could not construct course environment for $courseID.") unless $ce;
35+
return $job->fail('Could not construct course environment.') unless $ce;
36+
37+
$job->{language_handle} = WeBWorK::Localize::getLoc($ce->{language} || 'en');
3338

3439
my $db = WeBWorK::DB->new($ce->{dbLayout});
35-
return $job->fail("Could not obtain database connection for $courseID.") unless $db;
40+
return $job->fail($job->maketext('Could not obtain database connection.')) unless $db;
3641

37-
if ($setID && $userID && $ce->{LTIGradeMode} eq 'homework') {
38-
$job->app->log->info("LTI Mass Update: Starting grade update for user $userID and set $setID.");
39-
} elsif ($setID && $ce->{LTIGradeMode} eq 'homework') {
40-
$job->app->log->info("LTI Mass Update: Starting grade update for all users assigned to set $setID.");
41-
} elsif ($userID) {
42-
$job->app->log->info("LTI Mass Update: Starting grade update of all sets assigned to user $userID.");
43-
} else {
44-
$job->app->log->info('LTI Mass Update: Starting grade update for all sets and users.');
45-
}
42+
my @messages;
43+
my $job_logger = sub {
44+
my ($log, $level, @lines) = @_;
45+
push @messages, $lines[-1];
46+
};
47+
$job->app->log->on(message => $job_logger);
4648

4749
# Pass a fake controller object that will work for the grader.
4850
my $grader =
@@ -76,8 +78,24 @@ sub run ($job, $courseID, $userID = '', $setID = '') {
7678
}
7779
}
7880

79-
$job->app->log->info("Updated grades via LTI for course $courseID.");
80-
return $job->finish("Updated grades via LTI for course $courseID.");
81+
if ($setID && $userID && $ce->{LTIGradeMode} eq 'homework') {
82+
unshift(@messages, $job->maketext('Updated grades via LTI for user [_1] and set [_2].', $userID, $setID));
83+
} elsif ($setID && $ce->{LTIGradeMode} eq 'homework') {
84+
unshift(@messages, $job->maketext('Updated grades via LTI all users assigned to set [_1].', $setID));
85+
} elsif ($userID) {
86+
unshift(@messages, $job->maketext('Updated grades via LTI of all sets assigned to user [_1].', $userID));
87+
} else {
88+
unshift(@messages, $job->maketext('Updated grades via LTI for all sets and users.'));
89+
}
90+
91+
$job->app->log->unsubscribe(message => $job_logger);
92+
93+
$job->app->log->info($messages[0]);
94+
return $job->finish(@messages > 1 ? \@messages : $messages[0]);
95+
}
96+
97+
sub maketext ($job, @args) {
98+
return &{ $job->{language_handle} }(@args);
8199
}
82100

83101
1;

lib/Mojolicious/WeBWorK/Tasks/SendInstructorEmail.pm

Lines changed: 45 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -28,46 +28,48 @@ use WeBWorK::Utils qw/processEmailMessage createEmailSenderTransportSMTP/;
2828
# Send instructor email messages to students.
2929
# FIXME: This job currently allows multiple jobs to run at once. Should it be limited?
3030
sub run ($job, $mail_data) {
31-
my $ce = eval { WeBWorK::CourseEnvironment->new({ courseName => $mail_data->{courseName} }) };
32-
return $job->fail("Could not construct course environment for $mail_data->{courseName}.") unless $ce;
31+
my $courseID = $job->info->{notes}{courseID};
32+
return $job->fail('The course id was not passed when this job was enqueued.') unless $courseID;
3333

34-
my $db = WeBWorK::DB->new($ce->{dbLayout});
35-
return $job->fail("Could not obtain database connection for $mail_data->{courseName}.") unless $db;
34+
my $ce = eval { WeBWorK::CourseEnvironment->new({ courseName => $courseID }) };
35+
return $job->fail('Could not construct course environment.') unless $ce;
3636

3737
$job->{language_handle} = WeBWorK::Localize::getLoc($ce->{language} || 'en');
3838

39-
my $result_message = eval { $job->mail_message_to_recipients($ce, $db, $mail_data) };
40-
if ($@) {
41-
$result_message .= "An error occurred while trying to send email.\n" . "The error message is:\n\n$@\n\n";
42-
$job->app->log->error("An error occurred while trying to send email: $@\n");
43-
}
39+
my $db = WeBWorK::DB->new($ce->{dbLayout});
40+
return $job->fail($job->maketext('Could not obtain database connection.')) unless $db;
4441

45-
eval { $job->email_notification($ce, $mail_data, $result_message) };
42+
my @result_messages = eval { $job->mail_message_to_recipients($ce, $db, $mail_data) };
4643
if ($@) {
47-
$job->app->log->error("An error occurred while trying to send the email notification: $@\n");
48-
return $job->fail("FAILURE: Unable to send email notifation to instructor.");
44+
push(@result_messages,
45+
$job->maketext('An error occurred while trying to send email.'),
46+
$job->maketext('The error message is: [_1]', ref($@) ? $@->message : $@),
47+
);
48+
$job->app->log->error($_) for @result_messages;
49+
return $job->fail(\@result_messages);
4950
}
5051

51-
return $job->finish("SUCCESS: Email messages sent.");
52+
$job->app->log->error($_) for @result_messages;
53+
return $job->finish(\@result_messages);
5254
}
5355

5456
sub mail_message_to_recipients ($job, $ce, $db, $mail_data) {
55-
my $result_message = '';
57+
my @result_messages;
5658
my $failed_messages = 0;
57-
my $error_messages = '';
59+
my @error_messages;
5860

5961
my @recipients = @{ $mail_data->{recipients} };
6062

6163
for my $recipient (@recipients) {
62-
$error_messages = '';
64+
@error_messages = ();
6365

6466
my $user_record = $db->getUser($recipient);
6567
unless ($user_record) {
66-
$error_messages .= "Record for user $recipient not found\n";
68+
push(@error_messages, $job->maketext('Record for user [_1] not found.', $recipient));
6769
next;
6870
}
6971
unless ($user_record->email_address =~ /\S/) {
70-
$error_messages .= "User $recipient does not have an email address -- skipping\n";
72+
push(@error_messages, $job->maketext('User [_1] does not have an email address.', $recipient));
7173
next;
7274
}
7375

@@ -86,52 +88,44 @@ sub mail_message_to_recipients ($job, $ce, $db, $mail_data) {
8688
transport => createEmailSenderTransportSMTP($ce),
8789
$ce->{mail}{set_return_path} ? (from => $ce->{mail}{set_return_path}) : ()
8890
});
89-
debug 'email sent successfully to ' . $user_record->email_address;
91+
debug 'Email successfully sent to ' . $user_record->email_address;
9092
};
9193
if ($@) {
92-
debug "Error sending email: $@";
93-
$error_messages .= "Error sending email: $@";
94+
my $exception_message = ref($@) ? $@->message : $@;
95+
debug 'Error sending email to ' . $user_record->email_address . ": $exception_message";
96+
push(
97+
@error_messages,
98+
$job->maketext(
99+
'Error sending email to [_1]: [_2]', $user_record->email_address, $exception_message
100+
)
101+
);
94102
next;
95103
}
96104

97-
$result_message .=
98-
$job->maketext('Message sent to [_1] at [_2].', $recipient, $user_record->email_address) . "\n"
99-
unless $error_messages;
105+
push(@result_messages, $job->maketext('Message sent to [_1] at [_2].', $recipient, $user_record->email_address))
106+
unless @error_messages;
100107
} continue {
101108
# Update failed messages before continuing loop.
102-
if ($error_messages) {
109+
if (@error_messages) {
103110
$failed_messages++;
104-
$result_message .= $error_messages;
111+
push(@result_messages, @error_messages);
105112
}
106113
}
107114

108115
my $number_of_recipients = @recipients - $failed_messages;
109-
return $job->maketext(
110-
'A message with the subject line "[_1]" has been sent to [quant,_2,recipient] in the class [_3]. '
111-
. 'There were [_4] message(s) that could not be sent.',
112-
$mail_data->{subject}, $number_of_recipients, $mail_data->{courseName},
116+
return (
117+
$job->maketext(
118+
'A message with the subject line "[_1]" has been sent to [quant,_2,recipient].',
119+
$mail_data->{subject}, $number_of_recipients
120+
),
113121
$failed_messages
114-
)
115-
. "\n\n"
116-
. $result_message;
117-
}
118-
119-
sub email_notification ($job, $ce, $mail_data, $result_message) {
120-
my $email =
121-
Email::Stuffer->to($mail_data->{defaultFrom})->from($mail_data->{defaultFrom})->subject('WeBWorK email sent')
122-
->text_body($result_message)->header('X-Remote-Host' => $mail_data->{remote_host});
123-
124-
eval {
125-
$email->send_or_die({
126-
transport => createEmailSenderTransportSMTP($ce),
127-
$ce->{mail}{set_return_path} ? (from => $ce->{mail}{set_return_path}) : ()
128-
});
129-
};
130-
$job->app->log->error("Error sending email: $@") if $@;
131-
132-
$job->app->log->info("WeBWorK::Tasks::SendInstructorEmail: Instructor message sent from $mail_data->{defaultFrom}");
133-
134-
return;
122+
? ($job->maketext(
123+
'There [plural,_1,was,were] [quant,_1,message] that could not be sent.',
124+
$failed_messages
125+
))
126+
: (),
127+
@result_messages
128+
);
135129
}
136130

137131
sub maketext ($job, @args) {

lib/WeBWorK/AchievementEvaluator.pm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,13 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
257257
send_achievement_email => [ {
258258
recipient => $user_id,
259259
subject => 'Congratulations on earning a new achievement!',
260-
courseName => $ce->{courseName},
261260
achievementID => $achievement_id,
262261
setID => $set_id,
263262
nextLevelPoints => $nextLevelPoints || 0,
264263
pointsEarned => $achievementPoints,
265264
remote_host => $c->tx->remote_address || "UNKNOWN",
266-
} ]
265+
} ],
266+
{ notes => { courseID => $ce->{courseName} } }
267267
) if ($ce->{mail}{achievementEmailFrom} && $achievement->email_template);
268268
}
269269

lib/WeBWorK/Authen/LTI/MassUpdate.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ sub mass_update ($c, $manual_update = 0, $userID = undef, $setID = undef) {
6363
}
6464
}
6565

66-
$c->minion->enqueue(lti_mass_update => [ $ce->{courseName}, $userID, $setID ]);
66+
$c->minion->enqueue(lti_mass_update => [ $userID, $setID ], { notes => { courseID => $ce->{courseName} } });
6767

6868
return;
6969
}

lib/WeBWorK/Authen/LTIAdvanced/SubmitGrade.pm

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ async sub submit_course_grade ($self, $userID) {
122122
my $user = $db->getUser($userID);
123123
return 0 unless $user;
124124

125-
$self->warning("submitting all grades for user: $userID") if $ce->{debug_lti_grade_passback};
125+
$self->warning("submitting all grades for user: $userID")
126+
if $ce->{debug_lti_grade_passback} || $self->{post_processing_mode};
126127
$self->warning("lis_source_did is not available for user: $userID")
127-
if !$user->lis_source_did && $ce->{debug_lti_grade_passback};
128+
if !$user->lis_source_did && ($ce->{debug_lti_grade_passback} || $self->{post_processing_mode});
128129

129130
return await $self->submit_grade($user->lis_source_did, scalar(grade_all_sets($db, $userID)));
130131
}
@@ -140,9 +141,10 @@ async sub submit_set_grade ($self, $userID, $setID) {
140141

141142
my $userSet = $db->getMergedSet($userID, $setID);
142143

143-
$self->warning("Submitting grade for user $userID and set $setID.") if $ce->{debug_lti_grade_passback};
144+
$self->warning("Submitting grade for user $userID and set $setID.")
145+
if $ce->{debug_lti_grade_passback} || $self->{post_processing_mode};
144146
$self->warning('lis_source_did is not available for this set.')
145-
if !$userSet->lis_source_did && $ce->{debug_lti_grade_passback};
147+
if !$userSet->lis_source_did && ($ce->{debug_lti_grade_passback} || $self->{post_processing_mode});
146148

147149
return await $self->submit_grade(
148150
$userSet->lis_source_did,
@@ -229,7 +231,8 @@ EOS
229231
$bodyhash .= '=';
230232
}
231233

232-
$self->warning("Retrieving prior grade using sourcedid: $sourcedid") if $ce->{debug_lti_parameters};
234+
$self->warning("Retrieving prior grade using sourcedid: $sourcedid")
235+
if $ce->{debug_lti_parameters} || $self->{post_processing_mode};
233236

234237
my $requestGen = Net::OAuth->request('consumer');
235238

@@ -303,7 +306,7 @@ EOS
303306
if $ce->{debug_lti_grade_passback};
304307
$self->warning('LMS grade will NOT be updated - grade unchanged. '
305308
. "Old score: $oldScore; New score: $score")
306-
if ($ce->{debug_lti_grade_passback});
309+
if $ce->{debug_lti_grade_passback} || $self->{post_processing_mode};
307310
return 1;
308311
} else {
309312
debug("LMS grade will be updated. sourcedid: $sourcedid; Old score: $oldScore; New score: $score")
@@ -314,7 +317,7 @@ EOS
314317
$self->warning('Unable to retrieve prior grade from LMS. Note that if your server time is not correct, '
315318
. 'this may fail for reasons which are less than obvious from the error messages. Error: '
316319
. $response->message)
317-
if ($ce->{debug_lti_grade_passback});
320+
if $ce->{debug_lti_grade_passback} || $self->{post_processing_mode};
318321
debug('Unable to retrieve prior grade from LMS. Note that if your server time is not correct, '
319322
. 'this may fail for reasons which are less than obvious from the error messages. Error: '
320323
. $response->message);
@@ -412,17 +415,23 @@ EOS
412415
my $message = $1;
413416
$self->warning("result is: $message") if $ce->{debug_lti_grade_passback};
414417
if ($message ne 'success') {
415-
debug("Unable to update LMS grade $sourcedid . LMS responded with message: $message");
418+
$self->warning("Unable to update LMS grade $sourcedid. LMS responded with message: $message")
419+
if $self->{post_processing_mode};
420+
debug("Unable to update LMS grade $sourcedid. LMS responded with message: $message");
416421
return 0;
417422
} else {
418423
# If we got here, we got successes from both the post and the lms.
419424
debug("Successfully updated LMS grade $sourcedid. LMS responded with message: $message");
420425
}
421426
} else {
427+
$self->warning("Unable to update LMS grade $sourcedid. Error: " . $response->message)
428+
if $self->{post_processing_mode};
422429
debug("Unable to update LMS grade $sourcedid. Error: " . $response->message);
423430
debug($response->body);
424431
return 0;
425432
}
433+
$self->warning("Success submitting grade using sourcedid: $sourcedid and score: $score")
434+
if $self->{post_processing_mode};
426435
debug("Success submitting grade using sourcedid: $sourcedid and score: $score");
427436

428437
return 1;

0 commit comments

Comments
 (0)