Skip to content

Commit 4ca6bb5

Browse files
committed
Move achievement item use form to ProblemSet page.
Move the forms to use achievement items to a modal on the problem set page. If any achievement item is usable on the current set, a button to "Use Achievement Reward" will be available, and open a modal which lists all achievement items that can be applied. The Achievements page still lists all rewards the student has earned, and provides instructions to use them from the problem assignment they wish to use the achievement on.
1 parent ada1b90 commit 4ca6bb5

27 files changed

+840
-1293
lines changed

htdocs/js/AchievementItems/achievementitems.js

Lines changed: 0 additions & 33 deletions
This file was deleted.

lib/WeBWorK/AchievementItems.pm

Lines changed: 95 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package WeBWorK::AchievementItems;
1717
use Mojo::Base -signatures;
1818

19-
use WeBWorK::Utils qw(thaw_base64);
19+
use WeBWorK::Utils qw(nfreeze_base64 thaw_base64);
2020

2121
# List of available achievement items. Make sure to add any new items to this list. Furthermore, the elements in this
2222
# list have to match the class name of the achievement item classes loaded below.
@@ -44,65 +44,125 @@ use constant ITEMS => [ qw(
4444

4545
=head2 NAME
4646
47-
This is the base class for achievement times. This defines an interface for all of the achievement items. Each
48-
achievement item will have a name, a description, a method for creating an html form to get its inputs called print_form
49-
and a method for applying those inputs called use_item.
47+
This is the base class for achievement times. This defines an interface for all of the achievement items.
48+
Each achievement item will have an id, a name, a description, and the three methods can_use (checks if the
49+
item can be used on the given set), print_form (prints the form to use the item), and use_item.
5050
5151
Note: the ID has to match the name of the class.
5252
53+
The global method UserItems returns an array of all achievement items available to the given user. If no
54+
set is included, a list of all earned achievement items is return. If provided a set and corresponding problem
55+
or test version records, a list of items usable on the current set and records paired with an input form to
56+
use the item is returned. This method will also process any posts to use the achievement item.
57+
5358
=cut
5459

55-
sub id ($c) { return $c->{id}; }
56-
sub name ($c) { return $c->{name}; }
57-
sub description ($c) { return $c->{description}; }
60+
sub id ($self) { return $self->{id}; }
61+
sub name ($self) { return $self->{name}; }
62+
sub count ($self) { return $self->{count}; }
63+
sub description ($self) { return $self->{description}; }
64+
65+
# Method to find all achievement items available to the given user.
66+
# If $set is undefined return an array reference of all earned items.
67+
# If $set is defined, return an array reference of the usable items
68+
# for the given $set and problem or test versions records. Each item
69+
# is paired with its input form to use the item.
70+
sub UserItems ($c, $userName, $set, $records) {
71+
my $db = $c->db;
5872

59-
# This is a global method that returns all of the provided users items.
60-
sub UserItems ($userName, $db, $ce) {
61-
# return unless the user has global achievement data
62-
my $globalUserAchievement = $db->getGlobalUserAchievement($userName);
73+
# When acting as another user, achievement items can be listed but not used.
74+
return if $set && $userName ne $c->param('user');
6375

64-
return unless ($globalUserAchievement->frozen_hash);
76+
# Return unless the user has global achievement data.
77+
my $globalUserAchievement = $c->{globalData} // $db->getGlobalUserAchievement($userName);
78+
return unless $globalUserAchievement && $globalUserAchievement->frozen_hash;
6579

66-
my $globalData = thaw_base64($globalUserAchievement->frozen_hash);
80+
my $globalData = thaw_base64($globalUserAchievement->frozen_hash);
81+
my $use_item_id = $c->param('use_achievement_item_id') // '';
6782
my @items;
6883

69-
# Get a new item object for each type of item.
7084
for my $item (@{ +ITEMS }) {
71-
push(@items, [ "WeBWorK::AchievementItems::$item"->new, $globalData->{$item} ])
72-
if ($globalData->{$item});
85+
next unless $globalData->{$item};
86+
my $achievementItem = "WeBWorK::AchievementItems::$item"->new;
87+
$achievementItem->{count} = $globalData->{$item};
88+
89+
# Return list of achievements items if $set is not defined.
90+
unless ($set) {
91+
push(@items, $achievementItem);
92+
next;
93+
}
94+
next unless $achievementItem->can_use($set, $records);
95+
96+
# Use the achievement item.
97+
if ($use_item_id eq $item) {
98+
my $message = $achievementItem->use_item($set, $records, $c);
99+
if ($message) {
100+
$globalData->{$item}--;
101+
$achievementItem->{count}--;
102+
$globalUserAchievement->frozen_hash(nfreeze_base64($globalData));
103+
$db->putGlobalUserAchievement($globalUserAchievement);
104+
$c->addgoodmessage($c->maketext('[_1] succesffuly used. [_2]', $achievementItem->name, $message));
105+
}
106+
}
107+
108+
push(@items, [ $achievementItem, $use_item_id ? '' : $achievementItem->print_form($set, $records, $c) ]);
73109
}
74110

111+
# If an achievement item has been used, double check if the achievement items can still be used
112+
# since the item count could now be zero or an achievement item has altered the set/records.
113+
# Input forms are also built here to account for any possible change.
114+
if ($set && $use_item_id) {
115+
my @new_items;
116+
for (@items) {
117+
my $item = $_->[0];
118+
next unless $item->{count} && $item->can_use($set, $records);
119+
push(@new_items, [ $item, $item->print_form($set, $records, $c) ]);
120+
}
121+
return \@new_items;
122+
}
75123
return \@items;
76124
}
77125

126+
# Method that returns a string with the achievement name and number of remaining items.
127+
sub remaining_title ($self, $c) {
128+
if ($self->count > 1) {
129+
return $c->maketext('[_1] ([_2] remaining)', $c->maketext($self->name), $self->count);
130+
} elsif ($self->count < 0) {
131+
return $c->maketext('[_1] (unlimited reusability)', $c->maketext($self->name));
132+
} else {
133+
return $c->maketext('[_1] (1 remains)', $c->maketext($self->name));
134+
}
135+
}
136+
78137
# Utility method for outputing a form row with a label and popup menu.
79138
# The id, label_text, and values are required parameters.
80139
sub form_popup_menu_row ($c, %options) {
81140
my %params = (
82-
id => '',
83-
label_text => '',
84-
label_attr => {},
85-
values => [],
86-
menu_attr => {},
87-
menu_container_attr => {},
88-
add_container => 1,
141+
id => '',
142+
first_item => '',
143+
label_text => '',
144+
label_attr => {},
145+
values => [],
146+
menu_attr => {},
147+
add_container => 1,
89148
%options
90149
);
91150

92-
$params{label_attr}{class} //= 'col-4 col-form-label';
93-
$params{menu_attr}{class} //= 'form-select';
94-
$params{menu_container_attr}{class} //= 'col-8';
151+
$params{label_attr}{class} //= 'col-form-label';
152+
$params{menu_attr}{class} //= 'form-select';
95153

96-
my $row_contents = $c->c(
97-
$c->label_for($params{id} => $params{label_text}, %{ $params{label_attr} }),
98-
$c->tag(
99-
'div',
100-
%{ $params{menu_container_attr} },
101-
$c->select_field($params{id} => $params{values}, id => $params{id}, %{ $params{menu_attr} })
102-
)
103-
)->join('');
154+
unshift(@{ $params{values} }, [ $params{first_item} => '' ]) if $params{first_item};
155+
156+
my $row_contents = $c->tag(
157+
'div',
158+
class => 'form-floating',
159+
$c->c(
160+
$c->select_field($params{id} => $params{values}, %{ $params{menu_attr} }),
161+
$c->label_for($params{id} => $params{label_text}, %{ $params{label_attr} })
162+
)->join('')
163+
);
104164

105-
return $params{add_container} ? $c->tag('div', class => 'row mb-3', $row_contents) : $row_contents;
165+
return $params{add_container} ? $c->tag('div', class => 'my-3', $row_contents) : $row_contents;
106166
}
107167

108168
END {

lib/WeBWorK/AchievementItems/AddNewTestGW.pm

Lines changed: 24 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ use Mojo::Base 'WeBWorK::AchievementItems', -signatures;
1818

1919
# Item to allow students to take an additional version of a test within its test version interval
2020

21-
use WeBWorK::Utils qw(x nfreeze_base64 thaw_base64);
22-
use WeBWorK::Utils::DateTime qw(before between);
23-
use WeBWorK::Utils::Sets qw(format_set_name_display);
21+
use WeBWorK::Utils qw(x);
22+
use WeBWorK::Utils::DateTime qw(between);
2423

2524
sub new ($class) {
2625
return bless {
@@ -33,60 +32,34 @@ sub new ($class) {
3332
}, $class;
3433
}
3534

36-
sub print_form ($self, $sets, $setProblemIds, $c) {
37-
my $db = $c->db;
38-
39-
my @openGateways;
40-
41-
# Find the template sets of open gateway quizzes.
42-
for my $set (@$sets) {
43-
push(@openGateways, [ format_set_name_display($set->set_id) => $set->set_id ])
44-
if $set->assignment_type =~ /gateway/
45-
&& $set->set_id !~ /,v\d+$/
46-
&& between($set->open_date, $set->due_date);
47-
}
48-
49-
return unless @openGateways;
35+
sub can_use ($self, $set, $records) {
36+
return
37+
$set->assignment_type =~ /gateway/
38+
&& $set->set_id !~ /,v\d+$/
39+
&& between($set->open_date, $set->due_date)
40+
&& $set->versions_per_interval > 0;
41+
}
5042

51-
return $c->c(
52-
$c->tag('p', $c->maketext('Add a new version for which test?')),
53-
WeBWorK::AchievementItems::form_popup_menu_row(
54-
$c,
55-
id => 'adtgw_gw_id',
56-
label_text => $c->maketext('Test Name'),
57-
values => \@openGateways,
58-
menu_attr => { dir => 'ltr' }
43+
sub print_form ($self, $set, $records, $c) {
44+
return $c->tag(
45+
'p',
46+
$c->maketext(
47+
'Increase the number of versions from [_1] to [_2] for this test.',
48+
$set->versions_per_interval,
49+
$set->versions_per_interval + 1
5950
)
60-
)->join('');
51+
);
6152
}
6253

63-
sub use_item ($self, $userName, $c) {
64-
my $db = $c->db;
65-
my $ce = $c->ce;
66-
67-
# Validate data
68-
my $globalUserAchievement = $db->getGlobalUserAchievement($userName);
69-
return 'No achievement data?!?!?!' unless $globalUserAchievement->frozen_hash;
70-
71-
my $globalData = thaw_base64($globalUserAchievement->frozen_hash);
72-
return "You are $self->{id} trying to use an item you don't have" unless $globalData->{ $self->{id} };
73-
74-
my $setID = $c->param('adtgw_gw_id');
75-
return 'You need to input a Test Name' unless defined $setID;
76-
77-
my $set = $db->getMergedSet($userName, $setID);
78-
my $userSet = $db->getUserSet($userName, $setID);
79-
return q{Couldn't find that set!} unless $set && $userSet;
80-
81-
# Add an additional version per interval to the set.
82-
$userSet->versions_per_interval($set->versions_per_interval + 1) unless $set->versions_per_interval == 0;
54+
sub use_item ($self, $set, $records, $c) {
55+
# Increase the number of versions per interval by 1.
56+
my $db = $c->db;
57+
my $userSet = $db->getUserSet($set->user_id, $set->set_id);
58+
$set->versions_per_interval($set->versions_per_interval + 1);
59+
$userSet->versions_per_interval($set->versions_per_interval);
8360
$db->putUserSet($userSet);
8461

85-
$globalData->{ $self->{id} }--;
86-
$globalUserAchievement->frozen_hash(nfreeze_base64($globalData));
87-
$db->putGlobalUserAchievement($globalUserAchievement);
88-
89-
return;
62+
return $c->maketext('One additional test version added to this test.');
9063
}
9164

9265
1;

0 commit comments

Comments
 (0)