Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
517a965
Merge pull request #3811 from learningequality/hotfixes
bjester Nov 14, 2022
3cb566d
Fix missing information on pending task results
bjester Nov 17, 2022
79963ab
Revert back to add_pending_result, and manually set necessary info
bjester Nov 17, 2022
f16a07a
Merge pull request #3822 from bjester/tasks-extended
rtibbles Nov 17, 2022
12e4827
Merge branch 'master' into hotfixes
bjester Nov 17, 2022
1fb3278
Fix issue comparing kwargs when it contains objects that will be seri…
bjester Nov 17, 2022
3dbc390
Fix other edge cases-- uuid be damned
bjester Nov 17, 2022
511865a
Merge pull request #3824 from bjester/hotfixes
bjester Nov 17, 2022
d66b065
Merge pull request #3823 from learningequality/hotfixes
bjester Nov 17, 2022
c18c353
Allow exercises with raw_data set to be complete.
rtibbles Nov 17, 2022
fd7b774
Merge pull request #3825 from rtibbles/raw_exercises
bjester Nov 21, 2022
23021fb
Revert server side ordering to fix performance issues.
rtibbles Nov 21, 2022
84ba18c
Merge pull request #3829 from rtibbles/revert_modified_order
bjester Nov 21, 2022
9125832
Simplify sentry request error collection.
rtibbles Nov 22, 2022
36d6ee2
Merge pull request #3830 from rtibbles/sentry_client_errors
bjester Nov 22, 2022
c6f7f9b
Merge branch 'master' into hotfixes
bjester Nov 22, 2022
9f0da69
Upload error as attachment.
rtibbles Nov 22, 2022
4acc580
Merge pull request #3832 from rtibbles/event_attachment
bjester Nov 22, 2022
85d80eb
Merge pull request #3831 from learningequality/hotfixes
bjester Nov 22, 2022
60080d6
Remove image from innerHTML on remove.
rtibbles Nov 22, 2022
01b4503
Create remove event mechanism for clearing custom span element.
rtibbles Nov 23, 2022
9b10ad4
Revert "Revert server side ordering to fix performance issues."
bjester Nov 23, 2022
76e8ce5
Optimize use of channel in unpublished_changes query
bjester Nov 23, 2022
2b1f10b
Merge pull request #3839 from bjester/channel-list-query
bjester Nov 23, 2022
7ceff62
Merge branch 'master' into hotfixes
bjester Nov 23, 2022
02216e9
Consolidate all blank space wrapping in custom markdown field logic.
rtibbles Nov 23, 2022
d5b981e
Clean up squire keydown handling.
rtibbles Nov 23, 2022
ff87c48
Modify prop values using web component interface rather than
rtibbles Nov 23, 2022
f1f0a5c
Add extra edge case handling for sibling checking.
rtibbles Nov 23, 2022
48a97be
Tweak markdown conversion to prevent addition of excess line breaks.
rtibbles Nov 23, 2022
0183261
Merge pull request #3840 from learningequality/hotfixes
bjester Nov 23, 2022
7f7e675
Merge pull request #3837 from rtibbles/assessment_images
bjester Nov 23, 2022
b6e08ba
Only enqueue storage calculation task for non-admins
bjester Nov 30, 2022
d2d1921
Add helpers to celery app class for production intervention
bjester Nov 30, 2022
94ae2ac
Add calculation delay to ricecooker bulk endpoint, update tests
bjester Nov 30, 2022
a997d0c
Reorganize task revocation logic
bjester Nov 30, 2022
a331538
Add management command that reconciles tasks for changes
bjester Nov 30, 2022
9d84374
Merge pull request #3852 from bjester/angry-tasks
rtibbles Nov 30, 2022
f0986c7
Fixes `sync_channel` assessment_item bug
vkWeb Dec 2, 2022
bafe68c
[#3827][#3828] Add probers for alerting to task and change abnormalities
bjester Dec 1, 2022
5345841
[#3795] Retain TASK_ID and COPYING_FLAG when fetching from the server
bjester Dec 1, 2022
cfbfe3d
[#3856][#3857] Update axios, silence aborted connection errors, and o…
bjester Dec 2, 2022
f16e24f
[#3845] Defensive check against double submit on channel activation
bjester Dec 2, 2022
a86e0a3
[#3845] Disalbing of channel activation submit button while processing
bjester Dec 2, 2022
fe66038
[#3860] Refactor file upload handling, making return values consisten…
bjester Dec 2, 2022
b714641
[#3856] Add axios to include it for transform in jest
bjester Dec 2, 2022
8bee2fd
[#3860] Defensive check on file duration, which might occur when file…
bjester Dec 2, 2022
706d7d6
Merge pull request #3855 from bjester/mo-patches
bjester Dec 2, 2022
b63e0a3
Merge pull request #3859 from vkWeb/fix/sync_channel_assessment_item
bjester Dec 2, 2022
28398d8
Merge branch 'master' into hotfixes
bjester Dec 2, 2022
ef66578
Fix axios key name for custom param serializer
bjester Dec 5, 2022
7517281
Merge pull request #3866 from bjester/fix-axios-serializer
bjester Dec 5, 2022
b310b20
Revert change that did markdown conversion last to avoid escaped mark…
rtibbles Dec 6, 2022
19c25ff
Prevent error when this.parentNode is already null.
rtibbles Dec 7, 2022
6e25862
Fix application of 'editing' attribute to custom markdown editing com…
rtibbles Dec 7, 2022
88d7008
Merge pull request #3870 from rtibbles/assessment_tweaks
bjester Dec 7, 2022
bffd980
Resolve merge conflicts
bjester Dec 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions contentcuration/contentcuration/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ class DelayUserStorageCalculation(ContextDecorator):
def is_active(self):
return self.depth > 0

def add(self, user_id):
if user_id not in self.queue:
self.queue.append(user_id)

def __enter__(self):
self.depth += 1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -705,10 +705,11 @@
audioVideoFiles = this.nodeFiles.filter(file => this.allowedFileType(file));
// return the last item in the array
const file = audioVideoFiles[audioVideoFiles.length - 1];
return file.duration;
} else {
return null;
if (file) {
return file.duration;
}
}
return null;
},
videoSelected() {
return this.oneSelected && this.firstNode.kind === ContentKindsNames.VIDEO;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@
data-test="deploy-dialog"
:title="$tr('deployChannel')"
:submitText="$tr('confirmDeployBtn')"
:submitDisabled="submitDisabled"
:cancelDisabled="submitDisabled"
:cancelText="$tr('cancelDeployBtn')"
@submit="onDeployChannelClick"
@cancel="displayDeployDialog = false"
Expand Down Expand Up @@ -293,6 +295,7 @@
displayDeployDialog: false,
drawer: false,
elevated: false,
submitDisabled: false,
};
},
computed: {
Expand Down Expand Up @@ -504,7 +507,13 @@
this.elevated = e.target.scrollTop > 0;
},
async onDeployChannelClick() {
await this.deployCurrentChannel();
this.submitDisabled = true;
try {
await this.deployCurrentChannel();
} catch (e) {
this.submitDisabled = false;
throw e;
}
await this.loadChannel(this.currentChannel.id);

this.$router.push(this.rootTreeRoute);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ export function deployCurrentChannel(context) {
let payload = {
channel_id: context.state.currentChannelId,
};
return client.post(window.Urls.activate_channel(), payload);
return client.post(window.Urls.activate_channel(), payload).catch(e => {
// If response is 'Bad request', channel must already be activated
if (e.response && e.response.status === 400) {
return Promise.resolve();
}
});
}

export function publishChannel(context, version_notes) {
Expand Down
80 changes: 39 additions & 41 deletions contentcuration/contentcuration/frontend/shared/client.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import omit from 'lodash/omit';
import axios from 'axios';
import qs from 'qs';
import * as Sentry from '@sentry/vue';
Expand Down Expand Up @@ -26,67 +27,64 @@ export function paramsSerializer(params) {
const client = axios.create({
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken',
paramsSerializer,
paramsSerializer: {
serialize: paramsSerializer,
},
});

// Track when the browser was last offline for error reporting purposes
let lastOffline = null;
window.addEventListener('offline', () => {
lastOffline = Date.now();
});

client.interceptors.response.use(
response => response,
error => {
let message;
let url;
let config;
const url = error.config.url;
let message = error.message;
let status = 0;
if (error.response) {
config = error.response.config;
url = config.url;
status = error.response.status;
message = error.response.statusText;
// Don't send a Sentry report for permissions errors
// Many 404s are in fact also unauthorized requests so
// we should silence those on the front end and try
// to catch legitimate request issues in the backend.
//
// Allow 412 too as that's specific to out of storage checks
if (
error.response.status === 403 ||
error.response.status === 404 ||
error.response.status === 405 ||
error.response.status === 412
) {
if (status === 403 || status === 404 || status === 405 || status === 412) {
return Promise.reject(error);
}

if (error.response.status === 0) {
message = 'Network Error: ' + url;
}

// Put the URL in the main message for timeouts
// so we can see which timeouts are most frequent.
if (error.response.status === 504) {
message = 'Request Timed Out: ' + url;
}
} else if (error.request && error.request.config) {
// Request was sent but no response received
config = error.request.config;
url = config.url;
message = 'Network Error: ' + url;
} else {
message = error.message;
}

const extraData = {
url,
type: config ? config.responseType : null,
data: config ? config.data : null,
status: error.response ? error.response.status : null,
error: message,
response: error.response ? error.response.data : null,
};
message = message ? `${message}: ${url}` : `Network Error: ${url}`;

if (process.env.NODE_ENV !== 'production') {
// In dev build log warnings to console for developer use
console.warn('AJAX Request Error: ' + message); // eslint-disable-line no-console
console.warn('Error data: ' + JSON.stringify(extraData)); // eslint-disable-line no-console
} else {
Sentry.captureMessage(message, {
extra: extraData,
console.warn('Error data: ', error); // eslint-disable-line no-console
} else if (error.code !== 'ECONNABORTED') {
Sentry.withScope(function(scope) {
scope.addAttachment({
filename: 'error.json',
// strip csrf token from headers
data: JSON.stringify(omit(error, ['config.headers.X-CSRFToken'])),
contentType: 'application/json',
});
Sentry.captureException(new Error(message), {
extra: {
Request: {
headers: error.config.headers,
method: error.config.method,
url,
},
Network: {
lastOffline: lastOffline ? `${Date.now() - lastOffline}ms ago` : 'never',
online: navigator.onLine,
},
},
});
});
}
return Promise.reject(error);
Expand Down
125 changes: 71 additions & 54 deletions contentcuration/contentcuration/frontend/shared/data/resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,65 +264,82 @@ class IndexedDBResource {
CHANGES_TABLE,
() => {
// Get any relevant changes that would be overwritten by this bulkPut
return db[CHANGES_TABLE].where('[table+key]')
const changesPromise = db[CHANGES_TABLE].where('[table+key]')
.anyOf(itemData.map(datum => [this.tableName, this.getIdValue(datum)]))
.sortBy('rev', changes => {
changes = mergeAllChanges(changes, true);
const collectedChanges = collectChanges(changes)[this.tableName] || {};
for (let changeType of Object.keys(collectedChanges)) {
const map = {};
for (let change of collectedChanges[changeType]) {
map[change.key] = change;
}
collectedChanges[changeType] = map;
.sortBy('rev');
const currentPromise = this.table
.where(this.idField)
.anyOf(itemData.map(datum => this.getIdValue(datum)))
.toArray();

return Promise.all([changesPromise, currentPromise]).then(([changes, currents]) => {
changes = mergeAllChanges(changes, true);
const collectedChanges = collectChanges(changes)[this.tableName] || {};
for (let changeType of Object.keys(collectedChanges)) {
const map = {};
for (let change of collectedChanges[changeType]) {
map[change.key] = change;
}
const data = itemData
.map(datum => {
datum[LAST_FETCHED] = now;
const id = this.getIdValue(datum);
// If we have an updated change, apply the modifications here
if (
collectedChanges[CHANGE_TYPES.UPDATED] &&
collectedChanges[CHANGE_TYPES.UPDATED][id]
) {
applyMods(datum, collectedChanges[CHANGE_TYPES.UPDATED][id].mods);
collectedChanges[changeType] = map;
}
const currentMap = {};
for (let currentObj of currents) {
currentMap[this.getIdValue(currentObj)] = currentObj;
}
const data = itemData
.map(datum => {
const id = this.getIdValue(datum);
datum[LAST_FETCHED] = now;
// Persist TASK_ID and COPYING_FLAG attributes when directly fetching from the server
if (currentMap[id] && currentMap[id][TASK_ID]) {
datum[TASK_ID] = currentMap[id][TASK_ID];
}
if (currentMap[id] && currentMap[id][COPYING_FLAG]) {
datum[COPYING_FLAG] = currentMap[id][COPYING_FLAG];
}
// If we have an updated change, apply the modifications here
if (
collectedChanges[CHANGE_TYPES.UPDATED] &&
collectedChanges[CHANGE_TYPES.UPDATED][id]
) {
applyMods(datum, collectedChanges[CHANGE_TYPES.UPDATED][id].mods);
}
return datum;
// If we have a deleted change, just filter out this object so we don't reput it
})
.filter(
datum =>
!collectedChanges[CHANGE_TYPES.DELETED] ||
!collectedChanges[CHANGE_TYPES.DELETED][this.getIdValue(datum)]
);
return this.table.bulkPut(data).then(() => {
// Move changes need to be reapplied on top of fetched data in case anything
// has happened on the backend.
return applyChanges(Object.values(collectedChanges[CHANGE_TYPES.MOVED] || {})).then(
results => {
if (!results || !results.length) {
return data;
}
return datum;
// If we have a deleted change, just filter out this object so we don't reput it
})
.filter(
datum =>
!collectedChanges[CHANGE_TYPES.DELETED] ||
!collectedChanges[CHANGE_TYPES.DELETED][this.getIdValue(datum)]
);
return this.table.bulkPut(data).then(() => {
// Move changes need to be reapplied on top of fetched data in case anything
// has happened on the backend.
return applyChanges(Object.values(collectedChanges[CHANGE_TYPES.MOVED] || {})).then(
results => {
if (!results || !results.length) {
return data;
}
const resultsMap = {};
for (let result of results) {
const id = this.getIdValue(result);
resultsMap[id] = result;
}
return data
.map(datum => {
const id = this.getIdValue(datum);
if (resultsMap[id]) {
applyMods(datum, resultsMap[id]);
}
return datum;
// Concatenate any unsynced created objects onto
// the end of the returned objects
})
.concat(Object.values(collectedChanges[CHANGE_TYPES.CREATED]).map(c => c.obj));
const resultsMap = {};
for (let result of results) {
const id = this.getIdValue(result);
resultsMap[id] = result;
}
);
});
return data
.map(datum => {
const id = this.getIdValue(datum);
if (resultsMap[id]) {
applyMods(datum, resultsMap[id]);
}
return datum;
// Concatenate any unsynced created objects onto
// the end of the returned objects
})
.concat(Object.values(collectedChanges[CHANGE_TYPES.CREATED]).map(c => c.obj));
}
);
});
});
}
);
}
Expand Down
Loading