Skip to content

Commit 85d80eb

Browse files
authored
Merge pull request #3831 from learningequality/hotfixes
Patch release v2022.11.22
2 parents d66b065 + 4acc580 commit 85d80eb

File tree

5 files changed

+47
-57
lines changed

5 files changed

+47
-57
lines changed

contentcuration/contentcuration/frontend/channelEdit/views/ImportFromChannels/ChannelList.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@
124124
[this.channelFilter]: true,
125125
page: this.$route.query.page || 1,
126126
exclude: this.currentChannelId,
127-
ordering: this.channelFilter === 'public' ? 'name' : '-modified',
128127
}).then(page => {
129128
this.pageCount = page.total_pages;
130129
this.channels = page.results;

contentcuration/contentcuration/frontend/shared/client.js

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -32,61 +32,45 @@ const client = axios.create({
3232
client.interceptors.response.use(
3333
response => response,
3434
error => {
35-
let message;
36-
let url;
37-
let config;
35+
const url = error.config.url;
36+
let message = error.message;
37+
let status = 0;
3838
if (error.response) {
39-
config = error.response.config;
40-
url = config.url;
39+
status = error.response.status;
4140
message = error.response.statusText;
4241
// Don't send a Sentry report for permissions errors
4342
// Many 404s are in fact also unauthorized requests so
4443
// we should silence those on the front end and try
4544
// to catch legitimate request issues in the backend.
4645
//
4746
// Allow 412 too as that's specific to out of storage checks
48-
if (
49-
error.response.status === 403 ||
50-
error.response.status === 404 ||
51-
error.response.status === 405 ||
52-
error.response.status === 412
53-
) {
47+
if (status === 403 || status === 404 || status === 405 || status === 412) {
5448
return Promise.reject(error);
5549
}
56-
57-
if (error.response.status === 0) {
58-
message = 'Network Error: ' + url;
59-
}
60-
61-
// Put the URL in the main message for timeouts
62-
// so we can see which timeouts are most frequent.
63-
if (error.response.status === 504) {
64-
message = 'Request Timed Out: ' + url;
65-
}
66-
} else if (error.request && error.request.config) {
67-
// Request was sent but no response received
68-
config = error.request.config;
69-
url = config.url;
70-
message = 'Network Error: ' + url;
71-
} else {
72-
message = error.message;
7350
}
7451

75-
const extraData = {
76-
url,
77-
type: config ? config.responseType : null,
78-
data: config ? config.data : null,
79-
status: error.response ? error.response.status : null,
80-
error: message,
81-
response: error.response ? error.response.data : null,
82-
};
52+
message = message ? `${message}: ${url}` : `Network Error: ${url}`;
53+
8354
if (process.env.NODE_ENV !== 'production') {
8455
// In dev build log warnings to console for developer use
8556
console.warn('AJAX Request Error: ' + message); // eslint-disable-line no-console
86-
console.warn('Error data: ' + JSON.stringify(extraData)); // eslint-disable-line no-console
57+
console.warn('Error data: ', error); // eslint-disable-line no-console
8758
} else {
88-
Sentry.captureMessage(message, {
89-
extra: extraData,
59+
Sentry.withScope(function(scope) {
60+
scope.addAttachment({
61+
filename: 'error.json',
62+
data: JSON.stringify(error),
63+
contentType: 'application/json',
64+
});
65+
Sentry.captureException(new Error(message), {
66+
extra: {
67+
Request: {
68+
headers: error.config.headers,
69+
method: error.config.method,
70+
url,
71+
},
72+
},
73+
});
9074
});
9175
}
9276
return Promise.reject(error);

contentcuration/contentcuration/models.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,12 +1756,15 @@ def mark_complete(self): # noqa C901
17561756
if self.kind_id == content_kinds.EXERCISE:
17571757
# Check to see if the exercise has at least one assessment item that has:
17581758
if not self.assessment_items.filter(
1759-
# A non-blank question
1760-
~Q(question='')
1761-
# Non-blank answers
1762-
& ~Q(answers='[]')
1763-
# With either an input question or one answer marked as correct
1764-
& (Q(type=exercises.INPUT_QUESTION) | Q(answers__iregex=r'"correct":\s*true'))
1759+
# Item with non-blank raw data
1760+
~Q(raw_data="") | (
1761+
# A non-blank question
1762+
~Q(question='')
1763+
# Non-blank answers
1764+
& ~Q(answers='[]')
1765+
# With either an input question or one answer marked as correct
1766+
& (Q(type=exercises.INPUT_QUESTION) | Q(answers__iregex=r'"correct":\s*true'))
1767+
)
17651768
).exists():
17661769
errors.append("No questions with question text and complete answers")
17671770
# Check that it has a mastery model set

contentcuration/contentcuration/tests/test_contentnodes.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,15 @@ def test_create_exercise_valid_assessment_items(self):
10611061
new_obj.mark_complete()
10621062
self.assertTrue(new_obj.complete)
10631063

1064+
def test_create_exercise_valid_assessment_items_raw_data(self):
1065+
licenses = list(License.objects.filter(copyright_holder_required=False, is_custom=False).values_list("pk", flat=True))
1066+
channel = testdata.channel()
1067+
new_obj = ContentNode(title="yes", kind_id=content_kinds.EXERCISE, parent=channel.main_tree, license_id=licenses[0], extra_fields=self.new_extra_fields)
1068+
new_obj.save()
1069+
AssessmentItem.objects.create(contentnode=new_obj, raw_data="{\"question\": {}}")
1070+
new_obj.mark_complete()
1071+
self.assertTrue(new_obj.complete)
1072+
10641073
def test_create_exercise_no_extra_fields(self):
10651074
licenses = list(License.objects.filter(copyright_holder_required=False, is_custom=False).values_list("pk", flat=True))
10661075
channel = testdata.channel()

contentcuration/contentcuration/viewsets/channel.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,6 @@ class ChannelViewSet(ValuesViewset):
400400
serializer_class = ChannelSerializer
401401
pagination_class = ChannelListPagination
402402
filterset_class = ChannelFilter
403-
ordering_fields = ["modified", "name"]
404-
ordering = "-modified"
405403

406404
field_map = channel_field_map
407405
values = base_channel_values + ("edit", "view", "unpublished_changes")
@@ -414,15 +412,6 @@ def get_queryset(self):
414412
queryset = super(ChannelViewSet, self).get_queryset()
415413
user_id = not self.request.user.is_anonymous and self.request.user.id
416414
user_queryset = User.objects.filter(id=user_id)
417-
# Add the last modified node modified value as the channel last modified
418-
channel_main_tree_nodes = ContentNode.objects.filter(
419-
tree_id=OuterRef("main_tree__tree_id")
420-
)
421-
queryset = queryset.annotate(
422-
modified=Subquery(
423-
channel_main_tree_nodes.values("modified").order_by("-modified")[:1]
424-
)
425-
)
426415

427416
return queryset.annotate(
428417
edit=Exists(user_queryset.filter(editable_channels=OuterRef("id"))),
@@ -445,6 +434,12 @@ def annotate_queryset(self, queryset):
445434
queryset = queryset.annotate(
446435
count=SQCount(non_topic_content_ids, field="content_id"),
447436
)
437+
# Add the last modified node modified value as the channel last modified
438+
queryset = queryset.annotate(
439+
modified=Subquery(
440+
channel_main_tree_nodes.values("modified").order_by("-modified")[:1]
441+
)
442+
)
448443

449444
queryset = queryset.annotate(unpublished_changes=Exists(_unpublished_changes_query(OuterRef("id"))))
450445

0 commit comments

Comments
 (0)