From d6c1da94acf603211927377972f58548c382c7c8 Mon Sep 17 00:00:00 2001 From: taoerman Date: Fri, 27 Jun 2025 22:31:05 -0700 Subject: [PATCH 1/8] Wait for publish draft to complete --- .../pages/StagingTreePage/index.vue | 29 +++++------ .../frontend/shared/data/resources.js | 29 +++++++++++ .../contentcuration/viewsets/channel.py | 50 +++++++++++++------ 3 files changed, 79 insertions(+), 29 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue index 6c7240e25f..54ce830226 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue @@ -25,7 +25,7 @@ {{ $tr('reviewMode') }} - + { - this.isPublishingDraft = false; - this.showSnackbar({ - text: this.$tr('draftPublished'), - }); - }) - .catch(error => { - this.isPublishingDraft = false; - this.showSnackbar({ - text: error.response?.data?.message || this.$tr('publishDraftError'), - color: 'error', - }); + Channel.waitForPublishingDraft(this.currentChannel.id).then(() => { + this.isPublishingDraft = false; + this.showSnackbar({ + text: this.$tr('draftPublished'), }); + }) + .catch(error => { + this.isPublishingDraft = false; + this.showSnackbar({ + text: error.response?.data?.message || this.$tr('publishDraftError'), + color: 'error', + }); + }); + + this.publishDraftChannel(); }, }, $trs: { diff --git a/contentcuration/contentcuration/frontend/shared/data/resources.js b/contentcuration/contentcuration/frontend/shared/data/resources.js index 17e3431cb7..f698c1ddba 100644 --- a/contentcuration/contentcuration/frontend/shared/data/resources.js +++ b/contentcuration/contentcuration/frontend/shared/data/resources.js @@ -1245,6 +1245,35 @@ export const Channel = new CreateModelResource({ }); }, + waitForPublishingDraft(id) { + return this.transaction({ mode: 'rw' }, () => { + return this.table.update(id, { staging_publishing: true }); + }).then(() => { + const observable = liveQuery(() => { + return this.table + .where('id') + .equals(id) + .filter(channel => !channel['staging_publishing']) + .toArray(); + }); + + return new Promise((resolve, reject) => { + const subscription = observable.subscribe({ + next(result) { + if (result.length === 1) { + subscription.unsubscribe(); + resolve(); + } + }, + error() { + subscription.unsubscribe(); + reject(); + }, + }); + }); + }); + }, + deploy(id) { const change = new DeployedChange({ key: id, diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py index c2462836d1..22f1223046 100644 --- a/contentcuration/contentcuration/viewsets/channel.py +++ b/contentcuration/contentcuration/viewsets/channel.py @@ -367,6 +367,7 @@ def format_demo_server_url(item): "description", "main_tree__published", "main_tree__publishing", + "staging_tree__publishing", "thumbnail", "thumbnail_encoding", "language", @@ -391,6 +392,7 @@ def format_demo_server_url(item): "thumbnail_url": get_thumbnail_url, "published": "main_tree__published", "publishing": "main_tree__publishing", + "staging_publishing": "staging_tree__publishing", "created": "main_tree__created", "root_id": "main_tree__id", "trash_root_id": "trash_tree__id", @@ -639,26 +641,44 @@ def publish_next(self, pk): progress_tracker=progress_tracker, use_staging_tree=True, ) - Change.create_changes( - [ - generate_update_event( - channel.id, - CHANNEL, - { - "primary_token": channel.get_human_token().token, - }, - channel_id=channel.id, - ), - ], + Change.create_change( + generate_update_event( + channel.id, + CHANNEL, + { + "primary_token": channel.get_human_token().token, + "staging_publishing": False, + }, + channel_id=channel.id, + ), applied=True, + created_by_id=self.request.user.id, ) except ChannelIncompleteError: - channel.staging_tree.publishing = False - channel.staging_tree.save() + Change.create_change( + generate_update_event( + channel.id, + CHANNEL, + {"staging_publishing": False}, + channel_id=channel.id, + ), + applied=True, + unpublishable=True, + created_by_id=self.request.user.id, + ) raise ValidationError("Channel is not ready to be published") except Exception: - channel.staging_tree.publishing = False - channel.staging_tree.save() + Change.create_changes( + generate_update_event( + channel.id, + CHANNEL, + {"staging_publishing": False}, + channel_id=channel.id, + ), + applied=True, + unpublishable=True, + created_by_id=self.request.user.id, + ) raise def sync_from_changes(self, changes): From 4d6c410d64eb1093aaf3eeeecad7e4f02c68b66e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 05:43:15 +0000 Subject: [PATCH 2/8] [pre-commit.ci lite] apply automatic fixes --- .../pages/StagingTreePage/index.vue | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue index 54ce830226..e99ff5d370 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue @@ -646,19 +646,20 @@ this.displayPublishDraftDialog = false; this.isPublishingDraft = true; - Channel.waitForPublishingDraft(this.currentChannel.id).then(() => { - this.isPublishingDraft = false; - this.showSnackbar({ - text: this.$tr('draftPublished'), - }); - }) - .catch(error => { - this.isPublishingDraft = false; - this.showSnackbar({ - text: error.response?.data?.message || this.$tr('publishDraftError'), - color: 'error', + Channel.waitForPublishingDraft(this.currentChannel.id) + .then(() => { + this.isPublishingDraft = false; + this.showSnackbar({ + text: this.$tr('draftPublished'), + }); + }) + .catch(error => { + this.isPublishingDraft = false; + this.showSnackbar({ + text: error.response?.data?.message || this.$tr('publishDraftError'), + color: 'error', + }); }); - }); this.publishDraftChannel(); }, From dc6dc1c8f2abf5088dc8dea6f13d22953720cec7 Mon Sep 17 00:00:00 2001 From: taoerman Date: Wed, 2 Jul 2025 17:07:19 -0700 Subject: [PATCH 3/8] Address comments --- .../pages/StagingTreePage/index.vue | 18 +++--- .../frontend/shared/data/resources.js | 58 +++++++++---------- .../contentcuration/viewsets/channel.py | 37 +++++------- 3 files changed, 52 insertions(+), 61 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue index e99ff5d370..b51edc02f3 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue @@ -646,22 +646,20 @@ this.displayPublishDraftDialog = false; this.isPublishingDraft = true; - Channel.waitForPublishingDraft(this.currentChannel.id) - .then(() => { + this.publishDraftChannel().then(() => { + Channel.waitForPublishingDraft(this.currentChannel.id).then(() => { this.isPublishingDraft = false; this.showSnackbar({ text: this.$tr('draftPublished'), }); }) - .catch(error => { - this.isPublishingDraft = false; - this.showSnackbar({ - text: error.response?.data?.message || this.$tr('publishDraftError'), - color: 'error', - }); + }).catch(error => { + this.isPublishingDraft = false; + this.showSnackbar({ + text: error.response?.data?.message || this.$tr('publishDraftError'), + color: 'error', }); - - this.publishDraftChannel(); + }); }, }, $trs: { diff --git a/contentcuration/contentcuration/frontend/shared/data/resources.js b/contentcuration/contentcuration/frontend/shared/data/resources.js index f698c1ddba..85ad11f264 100644 --- a/contentcuration/contentcuration/frontend/shared/data/resources.js +++ b/contentcuration/contentcuration/frontend/shared/data/resources.js @@ -1235,41 +1235,41 @@ export const Channel = new CreateModelResource({ }, publishDraft(id) { - const change = new PublishedNextChange({ - key: id, - table: this.tableName, - source: CLIENTID, - }); - return this.transaction({ mode: 'rw' }, CHANGES_TABLE, () => { - return this._saveAndQueueChange(change); - }); - }, - - waitForPublishingDraft(id) { return this.transaction({ mode: 'rw' }, () => { return this.table.update(id, { staging_publishing: true }); }).then(() => { - const observable = liveQuery(() => { - return this.table - .where('id') - .equals(id) - .filter(channel => !channel['staging_publishing']) - .toArray(); + const change = new PublishedNextChange({ + key: id, + table: this.tableName, + source: CLIENTID, + }); + return this.transaction({ mode: 'rw' }, CHANGES_TABLE, () => { + return this._saveAndQueueChange(change); }); + }); + }, - return new Promise((resolve, reject) => { - const subscription = observable.subscribe({ - next(result) { - if (result.length === 1) { - subscription.unsubscribe(); - resolve(); - } - }, - error() { + waitForPublishingDraft(id) { + const observable = liveQuery(() => { + return this.table + .where('id') + .equals(id) + .filter(channel => !channel['staging_publishing']) + .toArray(); + }); + + return new Promise((resolve, reject) => { + const subscription = observable.subscribe({ + next(result) { + if (result.length === 1) { subscription.unsubscribe(); - reject(); - }, - }); + resolve(); + } + }, + error() { + subscription.unsubscribe(); + reject(); + }, }); }); }, diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py index 22f1223046..684ee29153 100644 --- a/contentcuration/contentcuration/viewsets/channel.py +++ b/contentcuration/contentcuration/viewsets/channel.py @@ -655,32 +655,25 @@ def publish_next(self, pk): created_by_id=self.request.user.id, ) except ChannelIncompleteError: - Change.create_change( - generate_update_event( - channel.id, - CHANNEL, - {"staging_publishing": False}, - channel_id=channel.id, - ), - applied=True, - unpublishable=True, - created_by_id=self.request.user.id, - ) + self._finish_staging_publishing_on_error(channel) raise ValidationError("Channel is not ready to be published") except Exception: - Change.create_changes( - generate_update_event( - channel.id, - CHANNEL, - {"staging_publishing": False}, - channel_id=channel.id, - ), - applied=True, - unpublishable=True, - created_by_id=self.request.user.id, - ) + self._finish_staging_publishing_on_error(channel) raise + def _finish_staging_publishing_on_error(self, channel): + Change.create_changes( + generate_update_event( + channel.id, + CHANNEL, + {"staging_publishing": False}, + channel_id=channel.id, + ), + applied=True, + unpublishable=True, + created_by_id=self.request.user.id, + ) + def sync_from_changes(self, changes): errors = [] for sync in changes: From 3011ac21b0d9f01442e3310a4f71a3af13499f0c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 00:12:30 +0000 Subject: [PATCH 4/8] [pre-commit.ci lite] apply automatic fixes --- .../pages/StagingTreePage/index.vue | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue index b51edc02f3..41cf893e56 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue @@ -646,20 +646,22 @@ this.displayPublishDraftDialog = false; this.isPublishingDraft = true; - this.publishDraftChannel().then(() => { - Channel.waitForPublishingDraft(this.currentChannel.id).then(() => { + this.publishDraftChannel() + .then(() => { + Channel.waitForPublishingDraft(this.currentChannel.id).then(() => { + this.isPublishingDraft = false; + this.showSnackbar({ + text: this.$tr('draftPublished'), + }); + }); + }) + .catch(error => { this.isPublishingDraft = false; this.showSnackbar({ - text: this.$tr('draftPublished'), + text: error.response?.data?.message || this.$tr('publishDraftError'), + color: 'error', }); - }) - }).catch(error => { - this.isPublishingDraft = false; - this.showSnackbar({ - text: error.response?.data?.message || this.$tr('publishDraftError'), - color: 'error', }); - }); }, }, $trs: { From b938b2d501a64b85735011889475e74bed571415 Mon Sep 17 00:00:00 2001 From: taoerman Date: Thu, 3 Jul 2025 11:27:50 -0700 Subject: [PATCH 5/8] fix bug --- contentcuration/contentcuration/viewsets/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py index 684ee29153..7fa2947057 100644 --- a/contentcuration/contentcuration/viewsets/channel.py +++ b/contentcuration/contentcuration/viewsets/channel.py @@ -662,7 +662,7 @@ def publish_next(self, pk): raise def _finish_staging_publishing_on_error(self, channel): - Change.create_changes( + Change.create_change( generate_update_event( channel.id, CHANNEL, From 6b3e5581594336686d854595ef577d713e8760da Mon Sep 17 00:00:00 2001 From: taoerman Date: Tue, 8 Jul 2025 23:03:46 -0700 Subject: [PATCH 6/8] Wait on change table instead of channel table --- .../pages/StagingTreePage/index.vue | 9 ++-- .../frontend/shared/data/resources.js | 39 ++++++++-------- .../frontend/shared/data/serverSync.js | 1 + .../contentcuration/viewsets/channel.py | 44 +++++++------------ 4 files changed, 42 insertions(+), 51 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue index 41cf893e56..6939d232fc 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue @@ -647,12 +647,11 @@ this.isPublishingDraft = true; this.publishDraftChannel() + .then(publishDraftchange => Channel.waitForPublishingDraft(publishDraftchange)) .then(() => { - Channel.waitForPublishingDraft(this.currentChannel.id).then(() => { - this.isPublishingDraft = false; - this.showSnackbar({ - text: this.$tr('draftPublished'), - }); + this.isPublishingDraft = false; + this.showSnackbar({ + text: this.$tr('draftPublished'), }); }) .catch(error => { diff --git a/contentcuration/contentcuration/frontend/shared/data/resources.js b/contentcuration/contentcuration/frontend/shared/data/resources.js index 85ad11f264..2402471605 100644 --- a/contentcuration/contentcuration/frontend/shared/data/resources.js +++ b/contentcuration/contentcuration/frontend/shared/data/resources.js @@ -1235,40 +1235,43 @@ export const Channel = new CreateModelResource({ }, publishDraft(id) { - return this.transaction({ mode: 'rw' }, () => { - return this.table.update(id, { staging_publishing: true }); - }).then(() => { - const change = new PublishedNextChange({ - key: id, - table: this.tableName, - source: CLIENTID, - }); - return this.transaction({ mode: 'rw' }, CHANGES_TABLE, () => { - return this._saveAndQueueChange(change); - }); + const change = new PublishedNextChange({ + key: id, + table: this.tableName, + source: CLIENTID, }); + return this.transaction({ mode: 'rw' }, CHANGES_TABLE, () => { + return this._saveAndQueueChange(change); + }).then(() => change); }, - waitForPublishingDraft(id) { + waitForPublishingDraft(publishDraftChange) { const observable = liveQuery(() => { - return this.table - .where('id') - .equals(id) - .filter(channel => !channel['staging_publishing']) + return db[CHANGES_TABLE] + .where('rev') + .equals(publishDraftChange.rev) + .and(change => change.type === publishDraftChange.type) + .and(change => change.channel_id === publishDraftChange.channel_id) .toArray(); }); return new Promise((resolve, reject) => { const subscription = observable.subscribe({ next(result) { - if (result.length === 1) { + // Successfully applied change will be removed. + if (result.length === 0) { subscription.unsubscribe(); resolve(); + } else { + if (result[0].disallowed || result[0].errored) { + subscription.unsubscribe(); + reject("Publish draft failed"); + } } }, error() { subscription.unsubscribe(); - reject(); + reject("Live query failed"); }, }); }); diff --git a/contentcuration/contentcuration/frontend/shared/data/serverSync.js b/contentcuration/contentcuration/frontend/shared/data/serverSync.js index 72c8c60e4c..7f8e7daea0 100644 --- a/contentcuration/contentcuration/frontend/shared/data/serverSync.js +++ b/contentcuration/contentcuration/frontend/shared/data/serverSync.js @@ -136,6 +136,7 @@ function handleErrors(response) { return db[CHANGES_TABLE].where('server_rev') .anyOf(Object.keys(errorMap).map(Number)) .modify(obj => { + obj["errored"] = true; for (const key in errorMap[obj.server_rev]) { if (!noModifyKeys[key] || typeof obj[key] === 'undefined') { obj[key] = errorMap[obj.server_rev][key]; diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py index 7fa2947057..4b2d9c8a89 100644 --- a/contentcuration/contentcuration/viewsets/channel.py +++ b/contentcuration/contentcuration/viewsets/channel.py @@ -1,3 +1,4 @@ + import logging from functools import reduce from operator import or_ @@ -367,7 +368,6 @@ def format_demo_server_url(item): "description", "main_tree__published", "main_tree__publishing", - "staging_tree__publishing", "thumbnail", "thumbnail_encoding", "language", @@ -392,7 +392,6 @@ def format_demo_server_url(item): "thumbnail_url": get_thumbnail_url, "published": "main_tree__published", "publishing": "main_tree__publishing", - "staging_publishing": "staging_tree__publishing", "created": "main_tree__created", "root_id": "main_tree__id", "trash_root_id": "trash_tree__id", @@ -641,39 +640,28 @@ def publish_next(self, pk): progress_tracker=progress_tracker, use_staging_tree=True, ) - Change.create_change( - generate_update_event( - channel.id, - CHANNEL, - { - "primary_token": channel.get_human_token().token, - "staging_publishing": False, - }, - channel_id=channel.id, - ), + Change.create_changes( + [ + generate_update_event( + channel.id, + CHANNEL, + { + "primary_token": channel.get_human_token().token, + }, + channel_id=channel.id, + ), + ], applied=True, - created_by_id=self.request.user.id, ) except ChannelIncompleteError: - self._finish_staging_publishing_on_error(channel) + channel.staging_tree.publishing = False + channel.staging_tree.save() raise ValidationError("Channel is not ready to be published") except Exception: - self._finish_staging_publishing_on_error(channel) + channel.staging_tree.publishing = False + channel.staging_tree.save() raise - def _finish_staging_publishing_on_error(self, channel): - Change.create_change( - generate_update_event( - channel.id, - CHANNEL, - {"staging_publishing": False}, - channel_id=channel.id, - ), - applied=True, - unpublishable=True, - created_by_id=self.request.user.id, - ) - def sync_from_changes(self, changes): errors = [] for sync in changes: From bc37804cf9abc7e32110d6eb92bffe815d1c2b9e Mon Sep 17 00:00:00 2001 From: taoerman Date: Tue, 8 Jul 2025 23:04:48 -0700 Subject: [PATCH 7/8] Remove empty line --- contentcuration/contentcuration/viewsets/channel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/contentcuration/contentcuration/viewsets/channel.py b/contentcuration/contentcuration/viewsets/channel.py index 4b2d9c8a89..c2462836d1 100644 --- a/contentcuration/contentcuration/viewsets/channel.py +++ b/contentcuration/contentcuration/viewsets/channel.py @@ -1,4 +1,3 @@ - import logging from functools import reduce from operator import or_ From be9c929939131cbd2104b3708968ef2029d4b006 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 06:07:02 +0000 Subject: [PATCH 8/8] [pre-commit.ci lite] apply automatic fixes --- .../contentcuration/frontend/shared/data/resources.js | 9 ++++----- .../contentcuration/frontend/shared/data/serverSync.js | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/contentcuration/contentcuration/frontend/shared/data/resources.js b/contentcuration/contentcuration/frontend/shared/data/resources.js index 2402471605..09ee1490e7 100644 --- a/contentcuration/contentcuration/frontend/shared/data/resources.js +++ b/contentcuration/contentcuration/frontend/shared/data/resources.js @@ -1247,8 +1247,7 @@ export const Channel = new CreateModelResource({ waitForPublishingDraft(publishDraftChange) { const observable = liveQuery(() => { - return db[CHANGES_TABLE] - .where('rev') + return db[CHANGES_TABLE].where('rev') .equals(publishDraftChange.rev) .and(change => change.type === publishDraftChange.type) .and(change => change.channel_id === publishDraftChange.channel_id) @@ -1262,16 +1261,16 @@ export const Channel = new CreateModelResource({ if (result.length === 0) { subscription.unsubscribe(); resolve(); - } else { + } else { if (result[0].disallowed || result[0].errored) { subscription.unsubscribe(); - reject("Publish draft failed"); + reject('Publish draft failed'); } } }, error() { subscription.unsubscribe(); - reject("Live query failed"); + reject('Live query failed'); }, }); }); diff --git a/contentcuration/contentcuration/frontend/shared/data/serverSync.js b/contentcuration/contentcuration/frontend/shared/data/serverSync.js index 7f8e7daea0..35a5db2a83 100644 --- a/contentcuration/contentcuration/frontend/shared/data/serverSync.js +++ b/contentcuration/contentcuration/frontend/shared/data/serverSync.js @@ -136,7 +136,7 @@ function handleErrors(response) { return db[CHANGES_TABLE].where('server_rev') .anyOf(Object.keys(errorMap).map(Number)) .modify(obj => { - obj["errored"] = true; + obj['errored'] = true; for (const key in errorMap[obj.server_rev]) { if (!noModifyKeys[key] || typeof obj[key] === 'undefined') { obj[key] = errorMap[obj.server_rev][key];