Skip to content

Commit

Permalink
Merge pull request openwebwork#2232 from drgrice1/job-manager
Browse files Browse the repository at this point in the history
Add a page to manage jobs in the job queue.
  • Loading branch information
pstaabp authored Jan 8, 2024
2 parents d8a32be + 4580b98 commit 8a23597
Show file tree
Hide file tree
Showing 24 changed files with 885 additions and 110 deletions.
13 changes: 13 additions & 0 deletions htdocs/js/JobManager/jobmanager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
(() => {
// Show/hide the filter elements depending on if the field matching option is selected.
const filter_select = document.getElementById('filter_select');
const filter_elements = document.getElementById('filter_elements');
if (filter_select && filter_elements) {
const toggle_filter_elements = () => {
if (filter_select.value === 'match_regex') filter_elements.style.display = 'block';
else filter_elements.style.display = 'none';
};
filter_select.addEventListener('change', toggle_filter_elements);
toggle_filter_elements();
}
})();
5 changes: 3 additions & 2 deletions lib/Mojolicious/WeBWorK.pm
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use Env qw(WEBWORK_SERVER_ADMIN);

use WeBWorK;
use WeBWorK::CourseEnvironment;
use WeBWorK::Utils qw(x writeTimingLogEntry);
use WeBWorK::Utils qw(writeTimingLogEntry);
use WeBWorK::Utils::Routes qw(setup_content_generator_routes);

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

# Setup the Minion job queue.
# Setup the Minion job queue. Make sure that any task added here is represented in the TASK_NAMES hash in
# WeBWorK::ContentGenerator::Instructor::JobManager.
$app->plugin(Minion => { $ce->{job_queue}{backend} => $ce->{job_queue}{database_dsn} });
$app->minion->add_task(lti_mass_update => 'Mojolicious::WeBWorK::Tasks::LTIMassUpdate');
$app->minion->add_task(send_instructor_email => 'Mojolicious::WeBWorK::Tasks::SendInstructorEmail');
Expand Down
21 changes: 11 additions & 10 deletions lib/Mojolicious/WeBWorK/Tasks/AchievementNotification.pm
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,31 @@ use WeBWorK::SafeTemplate;

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

my $ce = eval { WeBWorK::CourseEnvironment->new({ courseName => $courseID }); };
return $job->fail("Could not construct course environment for $courseID.")
unless $ce;

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

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

return $job->fail($job->maketext("Cannot notify student without an achievement."))
return $job->fail($job->maketext('Cannot notify student without an achievement.'))
unless $mail_data->{achievementID};
$mail_data->{achievement} = $db->getAchievement($mail_data->{achievementID});
return $job->fail($job->maketext("Could not find achievement [_1].", $mail_data->{achievementID}))
return $job->fail($job->maketext('Could not find achievement [_1].', $mail_data->{achievementID}))
unless $mail_data->{achievement};

my $result_message = eval { $job->send_achievement_notification($ce, $db, $mail_data) };
if ($@) {
$job->app->log->error($job->maketext("An error occurred while trying to send email: $@"));
return $job->fail($job->maketext("An error occurred while trying to send email: [_1]", $@));
$job->app->log->error("An error occurred while trying to send email: $@");
return $job->fail($job->maketext('An error occurred while trying to send email: [_1]', $@));
}
$job->app->log->info("Message sent to $mail_data->{recipient}");
return $job->finish($job->maketext("Message sent to [_1]", $mail_data->{recipient}));
return $job->finish($result_message);
}

sub send_achievement_notification ($job, $ce, $db, $mail_data) {
Expand Down
50 changes: 34 additions & 16 deletions lib/Mojolicious/WeBWorK/Tasks/LTIMassUpdate.pm
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,29 @@ use WeBWorK::CourseEnvironment;
use WeBWorK::DB;

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

my $courseID = $job->info->{notes}{courseID};
return $job->fail('The course id was not passed when this job was enqueued.') unless $courseID;

my $ce = eval { WeBWorK::CourseEnvironment->new({ courseName => $courseID }) };
return $job->fail("Could not construct course environment for $courseID.") unless $ce;
return $job->fail('Could not construct course environment.') unless $ce;

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

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

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

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

$job->app->log->info("Updated grades via LTI for course $courseID.");
return $job->finish("Updated grades via LTI for course $courseID.");
if ($setID && $userID && $ce->{LTIGradeMode} eq 'homework') {
unshift(@messages, $job->maketext('Updated grades via LTI for user [_1] and set [_2].', $userID, $setID));
} elsif ($setID && $ce->{LTIGradeMode} eq 'homework') {
unshift(@messages, $job->maketext('Updated grades via LTI all users assigned to set [_1].', $setID));
} elsif ($userID) {
unshift(@messages, $job->maketext('Updated grades via LTI of all sets assigned to user [_1].', $userID));
} else {
unshift(@messages, $job->maketext('Updated grades via LTI for all sets and users.'));
}

$job->app->log->unsubscribe(message => $job_logger);

$job->app->log->info($messages[0]);
return $job->finish(@messages > 1 ? \@messages : $messages[0]);
}

sub maketext ($job, @args) {
return &{ $job->{language_handle} }(@args);
}

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

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

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

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

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

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

sub mail_message_to_recipients ($job, $ce, $db, $mail_data) {
my $result_message = '';
my @result_messages;
my $failed_messages = 0;
my $error_messages = '';
my @error_messages;

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

for my $recipient (@recipients) {
$error_messages = '';
@error_messages = ();

my $user_record = $db->getUser($recipient);
unless ($user_record) {
$error_messages .= "Record for user $recipient not found\n";
push(@error_messages, $job->maketext('Record for user [_1] not found.', $recipient));
next;
}
unless ($user_record->email_address =~ /\S/) {
$error_messages .= "User $recipient does not have an email address -- skipping\n";
push(@error_messages, $job->maketext('User [_1] does not have an email address.', $recipient));
next;
}

Expand All @@ -86,52 +88,44 @@ sub mail_message_to_recipients ($job, $ce, $db, $mail_data) {
transport => createEmailSenderTransportSMTP($ce),
$ce->{mail}{set_return_path} ? (from => $ce->{mail}{set_return_path}) : ()
});
debug 'email sent successfully to ' . $user_record->email_address;
debug 'Email successfully sent to ' . $user_record->email_address;
};
if ($@) {
debug "Error sending email: $@";
$error_messages .= "Error sending email: $@";
my $exception_message = ref($@) ? $@->message : $@;
debug 'Error sending email to ' . $user_record->email_address . ": $exception_message";
push(
@error_messages,
$job->maketext(
'Error sending email to [_1]: [_2]', $user_record->email_address, $exception_message
)
);
next;
}

$result_message .=
$job->maketext('Message sent to [_1] at [_2].', $recipient, $user_record->email_address) . "\n"
unless $error_messages;
push(@result_messages, $job->maketext('Message sent to [_1] at [_2].', $recipient, $user_record->email_address))
unless @error_messages;
} continue {
# Update failed messages before continuing loop.
if ($error_messages) {
if (@error_messages) {
$failed_messages++;
$result_message .= $error_messages;
push(@result_messages, @error_messages);
}
}

my $number_of_recipients = @recipients - $failed_messages;
return $job->maketext(
'A message with the subject line "[_1]" has been sent to [quant,_2,recipient] in the class [_3]. '
. 'There were [_4] message(s) that could not be sent.',
$mail_data->{subject}, $number_of_recipients, $mail_data->{courseName},
return (
$job->maketext(
'A message with the subject line "[_1]" has been sent to [quant,_2,recipient].',
$mail_data->{subject}, $number_of_recipients
),
$failed_messages
)
. "\n\n"
. $result_message;
}

sub email_notification ($job, $ce, $mail_data, $result_message) {
my $email =
Email::Stuffer->to($mail_data->{defaultFrom})->from($mail_data->{defaultFrom})->subject('WeBWorK email sent')
->text_body($result_message)->header('X-Remote-Host' => $mail_data->{remote_host});

eval {
$email->send_or_die({
transport => createEmailSenderTransportSMTP($ce),
$ce->{mail}{set_return_path} ? (from => $ce->{mail}{set_return_path}) : ()
});
};
$job->app->log->error("Error sending email: $@") if $@;

$job->app->log->info("WeBWorK::Tasks::SendInstructorEmail: Instructor message sent from $mail_data->{defaultFrom}");

return;
? ($job->maketext(
'There [plural,_1,was,were] [quant,_1,message] that could not be sent.',
$failed_messages
))
: (),
@result_messages
);
}

sub maketext ($job, @args) {
Expand Down
4 changes: 2 additions & 2 deletions lib/WeBWorK/AchievementEvaluator.pm
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,13 @@ sub checkForAchievements ($problem_in, $pg, $c, %options) {
send_achievement_email => [ {
recipient => $user_id,
subject => 'Congratulations on earning a new achievement!',
courseName => $ce->{courseName},
achievementID => $achievement_id,
setID => $set_id,
nextLevelPoints => $nextLevelPoints || 0,
pointsEarned => $achievementPoints,
remote_host => $c->tx->remote_address || "UNKNOWN",
} ]
} ],
{ notes => { courseID => $ce->{courseName} } }
) if ($ce->{mail}{achievementEmailFrom} && $achievement->email_template);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/WeBWorK/Authen/LTI/MassUpdate.pm
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ sub mass_update ($c, $manual_update = 0, $userID = undef, $setID = undef) {
}
}

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

return;
}
Expand Down
25 changes: 17 additions & 8 deletions lib/WeBWorK/Authen/LTIAdvanced/SubmitGrade.pm
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,10 @@ async sub submit_course_grade ($self, $userID) {
my $user = $db->getUser($userID);
return 0 unless $user;

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

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

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

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

return await $self->submit_grade(
$userSet->lis_source_did,
Expand Down Expand Up @@ -229,7 +231,8 @@ EOS
$bodyhash .= '=';
}

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

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

Expand Down Expand Up @@ -303,7 +306,7 @@ EOS
if $ce->{debug_lti_grade_passback};
$self->warning('LMS grade will NOT be updated - grade unchanged. '
. "Old score: $oldScore; New score: $score")
if ($ce->{debug_lti_grade_passback});
if $ce->{debug_lti_grade_passback} || $self->{post_processing_mode};
return 1;
} else {
debug("LMS grade will be updated. sourcedid: $sourcedid; Old score: $oldScore; New score: $score")
Expand All @@ -314,7 +317,7 @@ EOS
$self->warning('Unable to retrieve prior grade from LMS. Note that if your server time is not correct, '
. 'this may fail for reasons which are less than obvious from the error messages. Error: '
. $response->message)
if ($ce->{debug_lti_grade_passback});
if $ce->{debug_lti_grade_passback} || $self->{post_processing_mode};
debug('Unable to retrieve prior grade from LMS. Note that if your server time is not correct, '
. 'this may fail for reasons which are less than obvious from the error messages. Error: '
. $response->message);
Expand Down Expand Up @@ -412,17 +415,23 @@ EOS
my $message = $1;
$self->warning("result is: $message") if $ce->{debug_lti_grade_passback};
if ($message ne 'success') {
debug("Unable to update LMS grade $sourcedid . LMS responded with message: $message");
$self->warning("Unable to update LMS grade $sourcedid. LMS responded with message: $message")
if $self->{post_processing_mode};
debug("Unable to update LMS grade $sourcedid. LMS responded with message: $message");
return 0;
} else {
# If we got here, we got successes from both the post and the lms.
debug("Successfully updated LMS grade $sourcedid. LMS responded with message: $message");
}
} else {
$self->warning("Unable to update LMS grade $sourcedid. Error: " . $response->message)
if $self->{post_processing_mode};
debug("Unable to update LMS grade $sourcedid. Error: " . $response->message);
debug($response->body);
return 0;
}
$self->warning("Success submitting grade using sourcedid: $sourcedid and score: $score")
if $self->{post_processing_mode};
debug("Success submitting grade using sourcedid: $sourcedid and score: $score");

return 1;
Expand Down
Loading

0 comments on commit 8a23597

Please sign in to comment.