diff --git a/htdocs/js/PGProblemEditor/pgproblemeditor.js b/htdocs/js/PGProblemEditor/pgproblemeditor.js index f48214574f..8e9fa78a54 100644 --- a/htdocs/js/PGProblemEditor/pgproblemeditor.js +++ b/htdocs/js/PGProblemEditor/pgproblemeditor.js @@ -231,6 +231,30 @@ .catch((err) => showMessage(`Error: ${err?.message ?? err}`)); }; + // Send a request to the server to run the PG critic in the CodeMirror editor. + const runPGCritic = () => { + const request_object = { courseID: document.getElementsByName('courseID')[0]?.value }; + + const user = document.getElementsByName('user')[0]; + if (user) request_object.user = user.value; + const sessionKey = document.getElementsByName('key')[0]; + if (sessionKey) request_object.key = sessionKey.value; + + request_object.rpc_command = 'runPGCritic'; + request_object.pgCode = + webworkConfig?.pgCodeMirror?.source ?? document.getElementById('problemContents')?.value ?? ''; + + fetch(webserviceURL, { method: 'post', mode: 'same-origin', body: new URLSearchParams(request_object) }) + .then((response) => response.json()) + .then((data) => { + renderArea.innerHTML = data.result_data.html; + }) + .catch((err) => { + console.log(err); + showMessage(`Error: ${err?.message ?? err}`); + }); + }; + document.getElementById('take_action')?.addEventListener('click', async (e) => { if (document.getElementById('current_action')?.value === 'format_code') { e.preventDefault(); @@ -240,6 +264,8 @@ document.querySelector('input[name="action.format_code"]:checked').value == 'convertCodeToPGML' ) { convertCodeToPGML(); + } else if (document.querySelector('input[name="action.format_code"]:checked').value == 'runPGCritic') { + runPGCritic(); } return; } diff --git a/lib/WebworkWebservice.pm b/lib/WebworkWebservice.pm index 1bd113b619..f44e4e5e5c 100644 --- a/lib/WebworkWebservice.pm +++ b/lib/WebworkWebservice.pm @@ -270,6 +270,7 @@ sub command_permission { putPastAnswer => 'problem_grader', tidyPGCode => 'access_instructor_tools', convertCodeToPGML => 'access_instructor_tools', + runPGCritic => 'access_instructor_tools', # WebworkWebservice::RenderProblem renderProblem => 'proctor_quiz_login', diff --git a/lib/WebworkWebservice/ProblemActions.pm b/lib/WebworkWebservice/ProblemActions.pm index 7684872367..9ec13c57ec 100644 --- a/lib/WebworkWebservice/ProblemActions.pm +++ b/lib/WebworkWebservice/ProblemActions.pm @@ -21,8 +21,9 @@ use warnings; use Data::Structure::Util qw(unbless); -use WeBWorK::PG::Tidy qw(pgtidy); -use WeBWorK::PG::ConvertToPGML qw(convertToPGML); +use WeBWorK::PG::Tidy qw(pgtidy); +use WeBWorK::PG::ConvertToPGML qw(convertToPGML); +use WeBWorK::PG::PGProblemCritic qw(analyzePGcode); sub getUserProblem { my ($invocant, $self, $params) = @_; @@ -180,4 +181,21 @@ sub convertCodeToPGML { } +sub runPGCritic { + my ($invocant, $self, $params) = @_; + my $pg_critic_results = analyzePGcode($params->{pgCode}); + + my $html_output = $self->c->render_to_string( + template => 'ContentGenerator/Instructor/PGProblemEditor/pg_critic', + results => $pg_critic_results + ); + + return { + ra_out => { + html => $html_output + }, + text => 'The script pg-critic has been run successfully.' + }; +} + 1; diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor/format_code_form.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor/format_code_form.html.ep index 69da3d5a95..0ddab6d09f 100644 --- a/templates/ContentGenerator/Instructor/PGProblemEditor/format_code_form.html.ep +++ b/templates/ContentGenerator/Instructor/PGProblemEditor/format_code_form.html.ep @@ -30,4 +30,18 @@ <%= maketext('PGML Conversion Help') %> +
+ <%= radio_button 'action.format_code' => 'runPGCritic', + id => 'action_format_code_run_pgcritic', class => 'form-check-input'=%> + <%= label_for 'action_format_code_run_pgcritic', class => 'form-check-label', begin =%> + <%== maketext('Run the PG Critic Analyzer') =%> + <% end =%> + + + <%= maketext('PG Critic Help') %> + +
diff --git a/templates/ContentGenerator/Instructor/PGProblemEditor/pg_critic.html.ep b/templates/ContentGenerator/Instructor/PGProblemEditor/pg_critic.html.ep new file mode 100644 index 0000000000..d5a4e77f1d --- /dev/null +++ b/templates/ContentGenerator/Instructor/PGProblemEditor/pg_critic.html.ep @@ -0,0 +1,147 @@ +
+

PG Critic Results

+ +

Metadata

+ +

The following lists required metadata. If any is missing, the given tag must be filled in. + However, make sure that the categories are correct, especially if the problem has been + copied.

+ +% sub showIcon { my $show = shift; +% return $show ? q! +% +% +% ! : q! +% +% +% !; +%} + + + + + + + +
DBsubject <%== showIcon($results->{metadata}{DBsection}) %>
DBchapter <%== showIcon($results->{metadata}{DBchapter}) %>
DBsection <%== showIcon($results->{metadata}{DBsection}) %>
Keywords <%== showIcon($results->{metadata}{KEYWORDS}) %>
+ +% my $pos = $results->{positive}; +% if ($pos->{PGML} || $pos->{solution} || $pos->{hint}) { +

Good aspects of this problems are the following

+% } + + +% if ($pos->{PGML}) { + +% } +% if ($pos->{solution}) { + +% } +% if ($pos->{hint}) { + +% } +% # list of the positive contexts: +% my @good_contexts = grep { $pos->{contexts}{$_} } keys %{$pos->{parsers}}; +% if (@good_contexts) { + +% } +% my @good_parsers = grep { $pos->{parsers}->{$_} } keys %{$pos->{parsers}}; +% if (@good_parsers) { + +% } +% my @good_macros = grep { $pos->{macros}->{$_} } keys %{$pos->{macros}}; +% if (@good_macros) { + +% } + +
PGMLThis problem uses PGML, the current preferred way to write problem (text), solution and hint + blocks.
SolutionsThis problem has a solution block. Every problem should have solutions that the + student can view after the answer data.
HintsThis problem has a hint. This can be helpful for students after attempting the problem + a few times (this can be set by the instructor). +% } +% if ($pos->{randomness}) { +
RandomnessThis problem uses randomness. This is desired to give to a class of students, each + of whom may have a different problem.
Modern ContextsThis problem uses the following modern contexts: + <%= join(', ', @good_contexts) %>
Modern ParsersThis problem uses features of the following modern parsers: + <%= join(', ', @good_parsers) %>
Modern MacrosThis problem uses functionality from the following modern macros: + <%= join(', ', @good_macros) %>
+ + +% if( scalar(@{$results->{deprecated_macros}}) > 0) { +

Deprecated Macros

+

This problem has the following deprecated macros: <%= join(', ',@{$results->{deprecated_macros}} ) %>

+ +

These should be removed from the problem in that these macros will be deleted from PG in a future + version. The functions from these macros may be listed below to help aid in transitioning away from + these macros.

+% } + +% my $has_bad_features = 0; +% $has_bad_features += $results->{negative}{$_} for (keys %{$results->{negative}}); + +% if ($has_bad_features || !$pos->{solution}) { +

You can improve on the following:

+

There are features in this problem that contain old or deprecated features. The following + list gives feedback of how the problem can be improved.

+%} + + + + +