Skip to content

Commit 2fd01d1

Browse files
authored
Merge pull request #3997 from ozer550/ActivationChangeEventBackend
Add Deploy as Change Event
2 parents 2db72b9 + 84cb511 commit 2fd01d1

File tree

21 files changed

+219
-224
lines changed

21 files changed

+219
-224
lines changed

contentcuration/contentcuration/api.py

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010
from django.core.files.storage import default_storage
1111

1212
import contentcuration.models as models
13-
from contentcuration.utils.garbage_collect import get_deleted_chefs_root
14-
from contentcuration.viewsets.sync.constants import CHANNEL
15-
from contentcuration.viewsets.sync.utils import generate_update_event
1613

1714

1815
def write_file_to_storage(fobj, check_valid=False, name=None):
@@ -68,33 +65,3 @@ def get_hash(fobj):
6865
md5.update(chunk)
6966
fobj.seek(0)
7067
return md5.hexdigest()
71-
72-
73-
def activate_channel(channel, user):
74-
user.check_channel_space(channel)
75-
76-
if channel.previous_tree and channel.previous_tree != channel.main_tree:
77-
# IMPORTANT: Do not remove this block, MPTT updating the deleted chefs block could hang the server
78-
with models.ContentNode.objects.disable_mptt_updates():
79-
garbage_node = get_deleted_chefs_root()
80-
channel.previous_tree.parent = garbage_node
81-
channel.previous_tree.title = "Previous tree for channel {}".format(channel.pk)
82-
channel.previous_tree.save()
83-
84-
channel.previous_tree = channel.main_tree
85-
channel.main_tree = channel.staging_tree
86-
channel.staging_tree = None
87-
channel.save()
88-
89-
user.staged_files.all().delete()
90-
user.set_space_used()
91-
92-
models.Change.create_change(generate_update_event(
93-
channel.id,
94-
CHANNEL,
95-
{
96-
"root_id": channel.main_tree.id,
97-
"staging_root_id": None
98-
},
99-
channel_id=channel.id,
100-
), applied=True, created_by_id=user.id)

contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.spec.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { mount, createLocalVue } from '@vue/test-utils';
22
import Vuex from 'vuex';
33
import VueRouter from 'vue-router';
44
import cloneDeep from 'lodash/cloneDeep';
5-
import flushPromises from 'flush-promises';
65

76
import { RouteNames } from '../../constants';
87
import StagingTreePage from './index';
98
import { createStore } from 'shared/vuex/draggablePlugin/test/setup';
109
import { ContentKindsNames } from 'shared/leUtils/ContentKinds';
10+
import { Channel } from 'shared/data/resources';
1111

1212
const localVue = createLocalVue();
1313
localVue.use(Vuex);
@@ -217,9 +217,14 @@ describe('StagingTreePage', () => {
217217
];
218218
};
219219

220-
mockDeployCurrentChannel = jest.fn().mockResolvedValue('');
220+
mockDeployCurrentChannel = jest.fn();
221221
actions.currentChannel.deployCurrentChannel = mockDeployCurrentChannel;
222-
//actions.channel.loadChannel = jest.fn().mockResolvedValue('')
222+
223+
Channel.waitForDeploying = () => {
224+
return new Promise(resolve => {
225+
return resolve(ROOT_ID);
226+
});
227+
};
223228

224229
wrapper = initWrapper({ getters, actions });
225230
// make sure router is reset before each test
@@ -414,9 +419,11 @@ describe('StagingTreePage', () => {
414419
});
415420

416421
it('redirects to a root tree page after deploy channel button click', async () => {
417-
getDeployDialog(wrapper).vm.$emit('submit');
418-
await flushPromises();
422+
let waitForDeployingSpy = jest.spyOn(Channel, 'waitForDeploying');
423+
424+
await getDeployDialog(wrapper).vm.$emit('submit');
419425

426+
expect(waitForDeployingSpy).toHaveBeenCalledTimes(1);
420427
expect(wrapper.vm.$router.currentRoute.name).toBe(RouteNames.TREE_VIEW);
421428
expect(wrapper.vm.$router.currentRoute.params).toEqual({
422429
nodeId: ROOT_ID,

contentcuration/contentcuration/frontend/channelEdit/pages/StagingTreePage/index.vue

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<span class="grey--darken-2 grey--text">{{ $tr('reviewMode') }}</span>
2222
</ToolBar>
2323
<MainNavigationDrawer v-model="drawer" />
24-
<LoadingText v-if="isLoading" />
24+
<LoadingText v-if="isLoading || isDeploying" />
2525
<VContent v-else-if="isEmpty">
2626
<VLayout justify-center fill-height class="pt-5">
2727
<VFlex class="text-xs-center">
@@ -204,8 +204,8 @@
204204
data-test="deploy-dialog"
205205
:title="$tr('deployChannel')"
206206
:submitText="$tr('confirmDeployBtn')"
207-
:submitDisabled="submitDisabled"
208-
:cancelDisabled="submitDisabled"
207+
:submitDisabled="isDeploying"
208+
:cancelDisabled="isDeploying"
209209
:cancelText="$tr('cancelDeployBtn')"
210210
@submit="onDeployChannelClick"
211211
@cancel="displayDeployDialog = false"
@@ -258,6 +258,7 @@
258258
import ToolBar from 'shared/views/ToolBar';
259259
import MainNavigationDrawer from 'shared/views/MainNavigationDrawer';
260260
import OfflineText from 'shared/views/OfflineText';
261+
import { Channel } from 'shared/data/resources';
261262
262263
export default {
263264
name: 'StagingTreePage',
@@ -295,7 +296,7 @@
295296
displayDeployDialog: false,
296297
drawer: false,
297298
elevated: false,
298-
submitDisabled: false,
299+
isDeploying: false,
299300
};
300301
},
301302
computed: {
@@ -403,9 +404,11 @@
403404
});
404405
},
405406
stagingId() {
406-
this.$router.push({
407-
name: RouteNames.STAGING_TREE_VIEW_REDIRECT,
408-
});
407+
if (this.hasStagingTree) {
408+
this.$router.push({
409+
name: RouteNames.STAGING_TREE_VIEW_REDIRECT,
410+
});
411+
}
409412
},
410413
},
411414
created() {
@@ -432,7 +435,6 @@
432435
},
433436
methods: {
434437
...mapActions(['showSnackbar', 'addViewModeOverride', 'removeViewModeOverride']),
435-
...mapActions('channel', ['loadChannel']),
436438
...mapActions('currentChannel', [
437439
'loadCurrentChannelStagingDiff',
438440
'deployCurrentChannel',
@@ -506,21 +508,24 @@
506508
scroll(e) {
507509
this.elevated = e.target.scrollTop > 0;
508510
},
509-
async onDeployChannelClick() {
510-
this.submitDisabled = true;
511-
try {
512-
await this.deployCurrentChannel();
513-
} catch (e) {
514-
this.submitDisabled = false;
515-
throw e;
516-
}
517-
await this.loadChannel(this.currentChannel.id);
511+
onDeployChannelClick() {
512+
this.displayDeployDialog = false;
513+
this.isDeploying = true;
518514
519-
this.$router.push(this.rootTreeRoute);
520-
521-
this.showSnackbar({
522-
text: this.$tr('channelDeployed'),
515+
Channel.waitForDeploying(this.currentChannel.id).then(rootId => {
516+
this.isDeploying = false;
517+
this.$router.push({
518+
name: RouteNames.TREE_VIEW,
519+
params: {
520+
nodeId: rootId,
521+
},
522+
});
523+
this.showSnackbar({
524+
text: this.$tr('channelDeployed'),
525+
});
523526
});
527+
528+
this.deployCurrentChannel();
524529
},
525530
},
526531
$trs: {

contentcuration/contentcuration/frontend/channelEdit/vuex/currentChannel/actions.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,7 @@ export function reloadCurrentChannelStagingDiff(context) {
4848
}
4949

5050
export function deployCurrentChannel(context) {
51-
let payload = {
52-
channel_id: context.state.currentChannelId,
53-
};
54-
return client.post(window.Urls.activate_channel(), payload).catch(e => {
55-
// If response is 'Bad request', channel must already be activated
56-
if (e.response && e.response.status === 400) {
57-
return Promise.resolve();
58-
}
59-
});
51+
return Channel.deploy(context.state.currentChannelId);
6052
}
6153

6254
export function publishChannel(context, version_notes) {

contentcuration/contentcuration/frontend/shared/data/applyRemoteChanges.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CHANGE_TYPES, IGNORED_SOURCE, TABLE_NAMES } from './constants';
44
import db from './db';
55
import { INDEXEDDB_RESOURCES } from './registry';
66

7-
const { CREATED, DELETED, UPDATED, MOVED, PUBLISHED, SYNCED } = CHANGE_TYPES;
7+
const { CREATED, DELETED, UPDATED, MOVED, PUBLISHED, SYNCED, DEPLOYED } = CHANGE_TYPES;
88

99
export function applyMods(obj, mods) {
1010
for (let keyPath in mods) {
@@ -28,6 +28,7 @@ export function collectChanges(changes) {
2828
[MOVED]: [],
2929
[PUBLISHED]: [],
3030
[SYNCED]: [],
31+
[DEPLOYED]: [],
3132
};
3233
}
3334
collectedChanges[change.table][change.type].push(change);

contentcuration/contentcuration/frontend/shared/data/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export const CHANGE_TYPES = {
66
COPIED: 5,
77
PUBLISHED: 6,
88
SYNCED: 7,
9+
DEPLOYED: 8,
910
};
1011
/**
1112
* An array of change types that directly result in the creation of nodes

contentcuration/contentcuration/frontend/shared/data/resources.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,44 @@ export const Channel = new Resource({
10851085
});
10861086
},
10871087

1088+
deploy(id) {
1089+
const change = {
1090+
key: id,
1091+
source: CLIENTID,
1092+
table: this.tableName,
1093+
type: CHANGE_TYPES.DEPLOYED,
1094+
channel_id: id,
1095+
};
1096+
return this.transaction({ mode: 'rw', source: IGNORED_SOURCE }, CHANGES_TABLE, () => {
1097+
return db[CHANGES_TABLE].put(change);
1098+
});
1099+
},
1100+
1101+
waitForDeploying(id) {
1102+
const observable = Dexie.liveQuery(() => {
1103+
return this.table
1104+
.where('id')
1105+
.equals(id)
1106+
.filter(channel => !channel['staging_root_id'] || channel['staging_root_id'] === null)
1107+
.toArray();
1108+
});
1109+
1110+
return new Promise((resolve, reject) => {
1111+
const subscription = observable.subscribe({
1112+
next(result) {
1113+
if (result.length === 1) {
1114+
subscription.unsubscribe();
1115+
resolve(result[0].root_id);
1116+
}
1117+
},
1118+
error() {
1119+
subscription.unsubscribe();
1120+
reject();
1121+
},
1122+
});
1123+
});
1124+
},
1125+
10881126
sync(id, { attributes = false, tags = false, files = false, assessment_items = false } = {}) {
10891127
const change = {
10901128
key: id,

contentcuration/contentcuration/frontend/shared/data/serverSync.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const ChangeTypeMapFields = {
5454
]),
5555
[CHANGE_TYPES.PUBLISHED]: commonFields.concat(['version_notes', 'language']),
5656
[CHANGE_TYPES.SYNCED]: commonFields.concat(['attributes', 'tags', 'files', 'assessment_items']),
57+
[CHANGE_TYPES.DEPLOYED]: commonFields,
5758
};
5859

5960
function isSyncableChange(change) {

contentcuration/contentcuration/tests/utils/test_garbage_collect.py

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from le_utils.constants import format_presets
1717

1818
from contentcuration import models as cc
19-
from contentcuration.api import activate_channel
2019
from contentcuration.constants import user_history
2120
from contentcuration.models import ContentNode
2221
from contentcuration.models import File
@@ -146,35 +145,6 @@ def test_old_staging_tree(self):
146145
self.assertFalse(cc.ContentNode.objects.filter(parent=garbage_node).exists())
147146
self.assertFalse(cc.ContentNode.objects.filter(pk=child_pk).exists())
148147

149-
def test_activate_channel(self):
150-
previous_tree = self.channel.previous_tree
151-
tree(parent=previous_tree)
152-
garbage_node = get_deleted_chefs_root()
153-
154-
# Previous tree shouldn't be in garbage tree until activate_channel is called
155-
self.assertFalse(
156-
garbage_node.get_descendants().filter(pk=previous_tree.pk).exists()
157-
)
158-
activate_channel(self.channel, self.user)
159-
garbage_node.refresh_from_db()
160-
previous_tree.refresh_from_db()
161-
self.channel.refresh_from_db()
162-
163-
# We can't use MPTT methods on the deleted chefs tree because we are not running the sort code
164-
# for performance reasons, so just do a parent test instead.
165-
self.assertTrue(previous_tree.parent == garbage_node)
166-
167-
# New previous tree should not be in garbage tree
168-
self.assertFalse(self.channel.previous_tree.parent)
169-
self.assertNotEqual(garbage_node.tree_id, self.channel.previous_tree.tree_id)
170-
171-
child_pk = previous_tree.children.first().pk
172-
173-
clean_up_deleted_chefs()
174-
175-
self.assertFalse(cc.ContentNode.objects.filter(parent=garbage_node).exists())
176-
self.assertFalse(cc.ContentNode.objects.filter(pk=child_pk).exists())
177-
178148

179149
THREE_MONTHS_AGO = datetime.now() - timedelta(days=93)
180150

contentcuration/contentcuration/tests/views/test_views_base.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,10 @@
55
from django.urls import reverse_lazy
66

77
from ..base import BaseAPITestCase
8-
from contentcuration.models import Channel
98
from contentcuration.models import TaskResult
109
from contentcuration.utils.db_tools import TreeBuilder
1110

1211

13-
class APIActivateChannelEndpointTestCase(BaseAPITestCase):
14-
def test_200_post(self):
15-
main_tree = TreeBuilder(user=self.user)
16-
staging_tree = TreeBuilder(user=self.user)
17-
self.channel.main_tree = main_tree.root
18-
self.channel.staging_tree = staging_tree.root
19-
self.channel.save()
20-
response = self.post(
21-
reverse_lazy("activate_channel"), {"channel_id": self.channel.id}
22-
)
23-
self.assertEqual(response.status_code, 200)
24-
25-
def test_404_no_permission(self):
26-
new_channel = Channel.objects.create()
27-
staging_tree = TreeBuilder(user=self.user, levels=1)
28-
new_channel.staging_tree = staging_tree.root
29-
new_channel.save()
30-
response = self.post(
31-
reverse_lazy("activate_channel"), {"channel_id": new_channel.id}
32-
)
33-
self.assertEqual(response.status_code, 404)
34-
35-
def test_200_no_change_in_space(self):
36-
main_tree = TreeBuilder(user=self.user)
37-
staging_tree = TreeBuilder(user=self.user)
38-
self.channel.main_tree = main_tree.root
39-
self.channel.staging_tree = staging_tree.root
40-
self.channel.save()
41-
self.user.disk_space = self.user.get_space_used(active_files=self.user.get_user_active_files())
42-
self.user.save()
43-
response = self.post(
44-
reverse_lazy("activate_channel"), {"channel_id": self.channel.id}
45-
)
46-
self.assertEqual(response.status_code, 200)
47-
48-
4912
class PublishingStatusEndpointTestCase(BaseAPITestCase):
5013
def test_200_get(self):
5114
self.user.is_admin = True

0 commit comments

Comments
 (0)