Skip to content

Commit 37ee628

Browse files
committed
WIP: work on course settings
1 parent df7c214 commit 37ee628

File tree

15 files changed

+247
-175
lines changed

15 files changed

+247
-175
lines changed

lib/DB/Schema/Result/CourseSetting.pm

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ C<setting_id>: database id that the given setting is related to (foreign key)
2525
2626
=item *
2727
28-
C<value>: the value of the setting
28+
C<value>: the value of the setting as a JSON so different types of data can be stored.
2929
3030
=back
3131
3232
=cut
3333

3434
__PACKAGE__->table('course_setting');
3535

36+
__PACKAGE__->load_components('InflateColumn::Serializer', 'Core');
37+
3638
__PACKAGE__->add_columns(
3739
course_setting_id => {
3840
data_type => 'integer',
@@ -51,8 +53,11 @@ __PACKAGE__->add_columns(
5153
is_nullable => 0,
5254
},
5355
value => {
54-
data_type => 'text',
55-
is_nullable => 0,
56+
data_type => 'text',
57+
is_nullable => 0,
58+
default_value => '\'\'',
59+
serializer_class => 'JSON',
60+
serializer_options => { utf8 => 1 }
5661
},
5762
);
5863

lib/DB/Schema/Result/GlobalSetting.pm

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ __PACKAGE__->add_columns(
8080
default_value => '',
8181
},
8282
doc => {
83-
data_type => 'text',
84-
is_nullable => 1,
83+
data_type => 'text',
84+
is_nullable => 1,
8585
},
8686
type => {
8787
data_type => 'varchar',
@@ -102,9 +102,9 @@ __PACKAGE__->add_columns(
102102
default_value => ''
103103
},
104104
subcategory => {
105-
data_type => 'varchar',
106-
size => 64,
107-
is_nullable => 1
105+
data_type => 'varchar',
106+
size => 64,
107+
is_nullable => 1
108108
}
109109
);
110110

lib/DB/Schema/ResultSet/Course.pm

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -356,18 +356,19 @@ sub getCourseSettings ($self, %args) {
356356
my @settings_from_db = $course->course_settings;
357357

358358
return \@settings_from_db if $args{as_result_set};
359-
my @settings_to_return;
360-
if ($args{merged}) {
361-
@settings_to_return = map {
359+
my @settings_to_return = ($args{merged})
360+
? map {
362361
{ $_->get_inflated_columns, $_->global_setting->get_inflated_columns };
363-
} @settings_from_db;
364-
for my $setting (@settings_to_return) {
365-
$setting->{default_value} = $setting->{default_value}->{value};
366-
}
367-
} else {
368-
@settings_to_return = map {
362+
} @settings_from_db
363+
: map {
369364
{ $_->get_inflated_columns };
370365
} @settings_from_db;
366+
367+
for my $setting (@settings_to_return) {
368+
# value and default_value are decoded from JSON as a hash. Return to a value.
369+
for my $key (qw/default_value value/) {
370+
$setting->{$key} = $setting->{$key}->{value} if defined($setting->{$key});
371+
}
371372
}
372373
return \@settings_to_return;
373374
}
@@ -409,13 +410,16 @@ sub getCourseSetting ($self, %args) {
409410
my $setting = $course->course_settings->find({ setting_id => $global_setting->setting_id });
410411

411412
return $setting if $args{as_result_set};
412-
if ($args{merged}) {
413-
my $setting_to_return = { $setting->get_inflated_columns, $setting->global_setting->get_inflated_columns };
414-
$setting_to_return->{default_value} = $setting_to_return->{default_value}->{value};
415-
return $setting_to_return;
416-
} else {
417-
return { $setting->get_inflated_columns };
413+
my $setting_to_return =
414+
$args{merged}
415+
? { $setting->get_inflated_columns, $setting->global_setting->get_inflated_columns }
416+
: { $setting->get_inflated_columns };
417+
418+
# value and default_value are decoded from JSON as a hash. Return to a value.
419+
for my $key (qw/default_value value/) {
420+
$setting_to_return->{$key} = $setting_to_return->{$key}->{value} if defined($setting_to_return->{$key});
418421
}
422+
return $setting_to_return;
419423
}
420424

421425
=pod
@@ -445,7 +449,9 @@ global setting.
445449
A single course setting as either a hashref or a C<DBIx::Class::ResultSet::CourseSetting> object.
446450
447451
=cut
452+
448453
use Data::Dumper;
454+
449455
sub updateCourseSetting ($self, %args) {
450456
my $course = $self->getCourse(info => getCourseInfo($args{info}), as_result_set => 1);
451457

@@ -460,32 +466,32 @@ sub updateCourseSetting ($self, %args) {
460466
my $params = {
461467
course_id => $course->course_id,
462468
setting_id => $global_setting->{setting_id},
463-
value => $args{params}->{value}
469+
value => { value => $args{params}->{value} }
464470
};
465471

466472
# remove the following fields before checking for valid settings:
467473
for (qw/setting_id course_id/) { delete $global_setting->{$_}; }
468474

469-
isValidSetting($global_setting, $params->{value});
475+
isValidSetting($global_setting, $params->{value}->{value});
470476

471477
# The course_id must be deleted to ensure it is written to the database correctly.
472478
delete $params->{course_id} if defined($params->{course_id});
473479

474-
my $updated_course_setting = defined($course_setting) ? $course_setting->update($params) : $course->add_to_course_settings($params);
475-
476-
if ($args{merged}) {
477-
my $setting_to_return = {
478-
$updated_course_setting->get_inflated_columns,
479-
$updated_course_setting->global_setting->get_inflated_columns
480-
};
481-
$setting_to_return->{default_value} = $setting_to_return->{default_value}->{value};
482-
return $setting_to_return;
483-
} else {
484-
return { $updated_course_setting->get_inflated_columns };
485-
}
480+
my $updated_course_setting =
481+
defined($course_setting) ? $course_setting->update($params) : $course->add_to_course_settings($params);
486482

483+
return $updated_course_setting if $args{as_result_set};
484+
my $setting_to_return =
485+
($args{merged})
486+
? { $updated_course_setting->get_inflated_columns,
487+
$updated_course_setting->global_setting->get_inflated_columns }
488+
: { $updated_course_setting->get_inflated_columns };
487489

488-
return $args{as_result_set} ? $updated_course_setting : { $updated_course_setting->get_inflated_columns };
490+
# value and default_value are decoded from JSON as a hash. Return to a value.
491+
for my $key (qw/default_value value/) {
492+
$setting_to_return->{$key} = $setting_to_return->{$key}->{value} if defined($setting_to_return->{$key});
493+
}
494+
return $setting_to_return;
489495
}
490496

491497
=pod
@@ -516,7 +522,17 @@ sub deleteCourseSetting ($self, %args) {
516522
my $deleted_setting = $setting->delete;
517523

518524
return $deleted_setting if $args{as_result_set};
519-
return { $deleted_setting->get_inflated_columns };
525+
526+
my $setting_to_return =
527+
($args{merged})
528+
? { $deleted_setting->get_inflated_columns, $deleted_setting->global_setting->get_inflated_columns }
529+
: { $deleted_setting->get_inflated_columns };
530+
531+
# value and default_value are decoded from JSON as a hash. Return to a value.
532+
for my $key (qw/default_value value/) {
533+
$setting_to_return->{$key} = $setting_to_return->{$key}->{value} if defined($setting_to_return->{$key});
534+
}
535+
return $setting_to_return;
520536
}
521537

522538
1;

lib/WeBWorK3.pm

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,7 @@ sub problemRoutes ($app, $course_routes) {
217217
}
218218

219219
sub settingsRoutes ($self) {
220-
$self->routes->get('/webwork3/api/global-settings')->requires(authenticated => 1)
221-
->to('Settings#getGlobalSettings');
220+
$self->routes->get('/webwork3/api/global-settings')->requires(authenticated => 1)->to('Settings#getGlobalSettings');
222221
$self->routes->get('/webwork3/api/global-setting/:setting_id')->requires(authenticated => 1)
223222
->to('Settings#getGlobalSetting');
224223
$self->routes->get('/webwork3/api/courses/:course_id/settings')->requires(authenticated => 1)

lib/WeBWorK3/Controller/Settings.pm

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ sub getGlobalSettings ($c) {
1818
}
1919

2020
sub getGlobalSetting ($c) {
21-
my $setting = $c->schema->resultset('Course')->getGlobalSetting(info => {
22-
setting_id => int($c->param('setting_id'))
23-
});
21+
my $setting = $c->schema->resultset('Course')->getGlobalSetting(
22+
info => {
23+
setting_id => int($c->param('setting_id'))
24+
}
25+
);
2426
$c->render(json => $setting);
2527
return;
2628
}
@@ -38,8 +40,8 @@ sub getCourseSettings ($c) {
3840

3941
sub updateCourseSetting ($c) {
4042
my $course_setting = $c->schema->resultset('Course')->updateCourseSetting(
41-
info => {
42-
course_id => $c->param('course_id'),
43+
info => {
44+
course_id => $c->param('course_id'),
4345
setting_id => $c->param('setting_id')
4446
},
4547
params => $c->req->json
@@ -50,13 +52,13 @@ sub updateCourseSetting ($c) {
5052

5153
sub deleteCourseSetting ($c) {
5254
my $course_setting = $c->schema->resultset('Course')->deleteCourseSetting(
53-
info => {
54-
course_id => $c->param('course_id'),
55+
info => {
56+
course_id => $c->param('course_id'),
5557
setting_id => $c->param('setting_id')
56-
});
58+
}
59+
);
5760
$c->render(json => $course_setting);
5861
return;
5962
}
6063

61-
6264
1;

lib/WeBWorK3/Utils/Settings.pm

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ sub getDefaultCourseSettings () {
3737
}
3838

3939
my @course_setting_categories = qw/email optional general permissions problem problem_set/;
40-
my @valid_types = qw/text list multilist boolean int decimal time date_time time_duration timezone/;
40+
my @valid_types = qw/text list multilist boolean int decimal time date_time time_duration timezone/;
4141

4242
=pod
4343
@@ -90,8 +90,8 @@ sub isValidSetting ($setting, $value = undef) {
9090
} elsif ($setting->{type} eq 'multilist') {
9191
validateMultilist($setting, $val);
9292
} elsif ($setting->{type} eq 'time') {
93-
DB::Exception::InvalidCourseFieldType->throw(
94-
message => qq/The variable $setting->{setting_name} has value $val and must be a time in the form XX:XX/)
93+
DB::Exception::InvalidCourseFieldType->throw(message =>
94+
qq/The variable $setting->{setting_name} has value $val and must be a time in the form XX:XX/)
9595
unless isTimeString($val);
9696
} elsif ($setting->{type} eq 'int') {
9797
DB::Exception::InvalidCourseFieldType->throw(
@@ -130,7 +130,8 @@ sub validateList ($setting, $value) {
130130
DB::Exception::InvalidCourseFieldType->throw(
131131
message => "The options field for the type list in $setting->{setting_name} is missing ")
132132
unless defined($setting->{options});
133-
DB::Exception::InvalidCourseFieldType->throw(message => "The options field for $setting->{setting_name} is not an ARRAYREF")
133+
DB::Exception::InvalidCourseFieldType->throw(
134+
message => "The options field for $setting->{setting_name} is not an ARRAYREF")
134135
unless ref($setting->{options}) eq 'ARRAY';
135136

136137
# See if the $setting->{options} is an arrayref of strings or hashrefs.
@@ -158,7 +159,8 @@ sub validateMultilist ($setting, $value) {
158159
DB::Exception::InvalidCourseFieldType->throw(
159160
message => "The options field for the type multilist in $setting->{setting_name} is missing ")
160161
unless defined($setting->{options});
161-
DB::Exception::InvalidCourseFieldType->throw(message => "The options field for $setting->{setting_name} is not an ARRAYREF")
162+
DB::Exception::InvalidCourseFieldType->throw(
163+
message => "The options field for $setting->{setting_name} is not an ARRAYREF")
162164
unless ref($setting->{options}) eq 'ARRAY';
163165

164166
my @diff = array_minus(@{ $setting->{options} }, @$value);
Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,76 @@
11
<template>
22
<tr>
3-
<td width="60%">{{ setting?.doc }}
4-
<q-icon v-if="setting?.doc2" name="help" class="q-ml-md">
3+
<td width="60%">{{ setting.description }}
4+
<q-icon v-if="setting.doc" name="help" class="q-ml-md">
55
<q-tooltip class="text-body2">
6-
{{ setting.doc2 }}
6+
{{ setting.doc }}
77
</q-tooltip>
88
</q-icon>
99
</td>
1010
<td width="40%">
1111
<input-with-blur
1212
outlined dense
1313
v-if="setting?.type === 'text' || setting?.type === 'timezone'"
14-
v-model="val"
14+
v-model="course_setting.value"
1515
/>
16-
<q-select v-if="setting?.type === 'list'" v-model="option" :options="options" />
17-
<q-input v-if="setting?.type === 'time_duration'" v-model="val" :rules="check_time_dur" />
18-
<q-toggle v-if="setting?.type === 'boolean'" v-model="val" />
19-
<q-input v-if="setting?.type === 'integer'" v-model="val" :rules="check_int" />
16+
<q-select v-if="setting.type === 'list'" v-model="option" :options="options" />
17+
<input-with-blur
18+
v-if="setting.type === 'time_duration'"
19+
v-model="course_setting.value"
20+
:rules="[checkTimeDuration]"
21+
/>
22+
<q-toggle v-if="setting.type === 'boolean'" v-model="course_setting.value" />
23+
<q-input v-if="setting.type === 'integer'" v-model="course_setting.value" :rules="[checkInt]" />
2024
</td>
2125
</tr>
2226
</template>
2327

2428
<script setup lang="ts">
25-
import { defineProps, PropType, ref, watch } from 'vue';
26-
import { CourseSetting, CourseSettingInfo, OptionType } from 'src/common/models/settings';
29+
import { defineProps, ref, watch } from 'vue';
30+
import { useQuasar } from 'quasar';
31+
import { CourseSetting, OptionType } from 'src/common/models/settings';
32+
2733
import InputWithBlur from 'src/components/common/InputWithBlur.vue';
34+
import { logger } from 'src/boot/logger';
35+
2836
import { useSettingsStore } from 'src/stores/settings';
37+
import { isTimeDuration } from 'src/common/models/parsers';
2938
30-
const props = defineProps({
31-
setting: Object as PropType<CourseSettingInfo>,
32-
value: [String, Number, Boolean, Array]
33-
});
39+
const props = defineProps<{
40+
setting: CourseSetting
41+
}>();
3442
43+
const $q = useQuasar();
3544
const settings = useSettingsStore();
36-
// The 'as string[]` is a hack since the array prop type cannot specify the
37-
// the type of array.
38-
const val = ref<string | number | boolean | string[]>(props.value);
45+
46+
const course_setting = ref(props.setting.clone());
47+
3948
const option = ref<OptionType>({ value: '', label: '' });
4049
const options = ref<Array<OptionType>>([]);
4150
42-
if (props.setting?.options) {
43-
options.value = props.setting.options.map((opt: string | OptionType) =>
51+
const checkInt = (val: string) => Number.isInteger(val) ? true : 'This must be an integer.';
52+
const checkTimeDuration = (val: string) => isTimeDuration(val) ? true : 'This must be a time duration.';
53+
54+
if (course_setting.value.options) {
55+
options.value = course_setting.value.options.map((opt: string | OptionType) =>
4456
typeof opt === 'string' ? { label: opt, value: opt } : opt
4557
);
46-
const v = options.value.find((opt: OptionType) => opt.value === val.value);
58+
const v = options.value.find((opt: OptionType) => opt.value === course_setting.value.value);
4759
option.value = v || { value: '', label: '' };
4860
}
4961
50-
watch(() => val.value, () => {
51-
void settings.updateCourseSetting(new CourseSetting({
52-
var: props.setting?.var,
53-
value: val.value
54-
}));
62+
watch(() => course_setting.value.value, async () => {
63+
try {
64+
await settings.updateCourseSetting(course_setting.value as CourseSetting);
65+
const msg = `The setting '${course_setting.value.setting_name}' was updated successfully`;
66+
$q.notify({
67+
message: msg,
68+
color: 'green'
69+
});
70+
logger.debug(`[CourseSettings/updateCourseSetting]: ${msg}`);
71+
} catch (err) {
72+
$q.notify({ message: err as string, color: 'red' });
73+
logger.error(`[CourseSettings/updateCourseSetting]: ${err as string}`);
74+
}
5575
});
5676
</script>

src/layouts/MenuBar.vue

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ import { endSession } from 'src/common/api-requests/session';
6868
import { useI18n } from 'vue-i18n';
6969
import { setI18nLanguage } from 'boot/i18n';
7070
import { useSessionStore } from 'src/stores/session';
71-
import type { CourseSettingInfo } from 'src/common/models/settings';
7271
import { useSettingsStore } from 'src/stores/settings';
7372
import { logger } from 'src/boot/logger';
7473
@@ -99,9 +98,7 @@ const changeCourse = (course_id: number, course_name: string) => {
9998
}
10099
};
101100
102-
const availableLocales = computed(() =>
103-
settings.default_settings.find((setting: CourseSettingInfo) => setting.var === 'language')?.options
104-
);
101+
const availableLocales = computed(() => settings.getCourseSetting('language')?.options);
105102
106103
const logout = async () => {
107104
await endSession();

0 commit comments

Comments
 (0)