Skip to content

Commit 51bfd3f

Browse files
authored
feat: Add blockType to xblockPreview & title to xblock_iframe [FC-0097] (#37362)
- Adds `blockType` and `is_modified` to the `showXBlockLibraryChangesPreview` iframe message. - Add title to the `xblock_iframe` - Add `is-modified` to `studio_xblock_wrapper` - Add `disable_staff_debug_info` as a query param in `render_xblock` - `downstream_is_modified` added to ComponentLink and ContainerLink
1 parent d34a6b9 commit 51bfd3f

11 files changed

Lines changed: 176 additions & 14 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 4.2.24 on 2025-09-23 19:47
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('contentstore', '0012_componentlink_top_level_parent_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='componentlink',
15+
name='downstream_is_modified',
16+
field=models.BooleanField(default=False),
17+
),
18+
migrations.AddField(
19+
model_name='containerlink',
20+
name='downstream_is_modified',
21+
field=models.BooleanField(default=False),
22+
),
23+
]

cms/djangoapps/contentstore/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class EntityLinkBase(models.Model):
108108
top_level_parent = models.ForeignKey("ContainerLink", on_delete=models.SET_NULL, null=True, blank=True)
109109
version_synced = models.IntegerField()
110110
version_declined = models.IntegerField(null=True, blank=True)
111+
downstream_is_modified = models.BooleanField(default=False)
111112
created = manual_date_time_field()
112113
updated = manual_date_time_field()
113114

@@ -257,6 +258,7 @@ def update_or_create(
257258
version_synced: int,
258259
top_level_parent_usage_key: UsageKey | None = None,
259260
version_declined: int | None = None,
261+
downstream_is_modified: bool = False,
260262
created: datetime | None = None,
261263
) -> "ComponentLink":
262264
"""
@@ -281,6 +283,7 @@ def update_or_create(
281283
'version_synced': version_synced,
282284
'version_declined': version_declined,
283285
'top_level_parent': top_level_parent,
286+
'downstream_is_modified': downstream_is_modified,
284287
}
285288
if upstream_block:
286289
new_values['upstream_block'] = upstream_block
@@ -482,6 +485,7 @@ def update_or_create(
482485
version_synced: int,
483486
top_level_parent_usage_key: UsageKey | None = None,
484487
version_declined: int | None = None,
488+
downstream_is_modified: bool = False,
485489
created: datetime | None = None,
486490
) -> "ContainerLink":
487491
"""
@@ -506,6 +510,7 @@ def update_or_create(
506510
'version_synced': version_synced,
507511
'version_declined': version_declined,
508512
'top_level_parent': top_level_parent,
513+
'downstream_is_modified': downstream_is_modified,
509514
}
510515
if upstream_container_id:
511516
new_values['upstream_container_id'] = upstream_container_id

cms/djangoapps/contentstore/rest_api/v2/views/tests/test_downstream_sync_integration.py

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ def test_unit_sync(self):
384384
'updated': date_format,
385385
'upstream_key': self.upstream_html1["id"],
386386
'upstream_type': 'component',
387+
'downstream_is_modified': False,
387388
},
388389
{
389390
'id': 2,
@@ -400,7 +401,8 @@ def test_unit_sync(self):
400401
'created': date_format,
401402
'updated': date_format,
402403
'upstream_key': self.upstream_problem1["id"],
403-
'upstream_type': 'component'
404+
'upstream_type': 'component',
405+
'downstream_is_modified': False,
404406
},
405407
{
406408
'id': 3,
@@ -417,7 +419,8 @@ def test_unit_sync(self):
417419
'created': date_format,
418420
'updated': date_format,
419421
'upstream_key': self.upstream_problem2["id"],
420-
'upstream_type': 'component'
422+
'upstream_type': 'component',
423+
'downstream_is_modified': False,
421424
},
422425
{
423426
'id': 1,
@@ -434,7 +437,8 @@ def test_unit_sync(self):
434437
'created': date_format,
435438
'updated': date_format,
436439
'upstream_key': self.upstream_unit["id"],
437-
'upstream_type': 'container'
440+
'upstream_type': 'container',
441+
'downstream_is_modified': False,
438442
}
439443
]
440444
data = downstreams.json()
@@ -533,6 +537,7 @@ def test_unit_sync(self):
533537
'updated': date_format,
534538
'upstream_key': self.upstream_html1["id"],
535539
'upstream_type': 'component',
540+
'downstream_is_modified': False,
536541
},
537542
{
538543
'id': 2,
@@ -549,7 +554,8 @@ def test_unit_sync(self):
549554
'created': date_format,
550555
'updated': date_format,
551556
'upstream_key': self.upstream_problem1["id"],
552-
'upstream_type': 'component'
557+
'upstream_type': 'component',
558+
'downstream_is_modified': False,
553559
},
554560
{
555561
'id': 3,
@@ -566,7 +572,8 @@ def test_unit_sync(self):
566572
'created': date_format,
567573
'updated': date_format,
568574
'upstream_key': self.upstream_problem2["id"],
569-
'upstream_type': 'component'
575+
'upstream_type': 'component',
576+
'downstream_is_modified': False,
570577
},
571578
{
572579
'id': 1,
@@ -583,7 +590,8 @@ def test_unit_sync(self):
583590
'created': date_format,
584591
'updated': date_format,
585592
'upstream_key': self.upstream_unit["id"],
586-
'upstream_type': 'container'
593+
'upstream_type': 'container',
594+
'downstream_is_modified': False,
587595
}
588596
]
589597
data = downstreams.json()
@@ -681,6 +689,7 @@ def test_unit_sync(self):
681689
'updated': date_format,
682690
'upstream_key': self.upstream_html1["id"],
683691
'upstream_type': 'component',
692+
'downstream_is_modified': False,
684693
},
685694
{
686695
'id': 2,
@@ -697,7 +706,8 @@ def test_unit_sync(self):
697706
'created': date_format,
698707
'updated': date_format,
699708
'upstream_key': self.upstream_problem1["id"],
700-
'upstream_type': 'component'
709+
'upstream_type': 'component',
710+
'downstream_is_modified': False,
701711
},
702712
{
703713
'id': 4,
@@ -714,7 +724,8 @@ def test_unit_sync(self):
714724
'created': date_format,
715725
'updated': date_format,
716726
'upstream_key': upstream_problem3["id"],
717-
'upstream_type': 'component'
727+
'upstream_type': 'component',
728+
'downstream_is_modified': False,
718729
},
719730
{
720731
'id': 1,
@@ -731,7 +742,8 @@ def test_unit_sync(self):
731742
'created': date_format,
732743
'updated': date_format,
733744
'upstream_key': self.upstream_unit["id"],
734-
'upstream_type': 'container'
745+
'upstream_type': 'container',
746+
'downstream_is_modified': False,
735747
}
736748
]
737749
data = downstreams.json()
@@ -810,6 +822,7 @@ def test_unit_sync(self):
810822
'updated': date_format,
811823
'upstream_key': self.upstream_html1["id"],
812824
'upstream_type': 'component',
825+
'downstream_is_modified': False,
813826
},
814827
{
815828
'id': 2,
@@ -826,7 +839,8 @@ def test_unit_sync(self):
826839
'created': date_format,
827840
'updated': date_format,
828841
'upstream_key': self.upstream_problem1["id"],
829-
'upstream_type': 'component'
842+
'upstream_type': 'component',
843+
'downstream_is_modified': False,
830844
},
831845
{
832846
'id': 4,
@@ -843,7 +857,8 @@ def test_unit_sync(self):
843857
'created': date_format,
844858
'updated': date_format,
845859
'upstream_key': upstream_problem3["id"],
846-
'upstream_type': 'component'
860+
'upstream_type': 'component',
861+
'downstream_is_modified': False,
847862
},
848863
{
849864
'id': 1,
@@ -860,7 +875,8 @@ def test_unit_sync(self):
860875
'created': date_format,
861876
'updated': date_format,
862877
'upstream_key': self.upstream_unit["id"],
863-
'upstream_type': 'container'
878+
'upstream_type': 'container',
879+
'downstream_is_modified': False,
864880
}
865881
]
866882
data = downstreams.json()
@@ -1047,6 +1063,7 @@ def test_modified_html_copy_paste(self):
10471063
Test that we can sync a html from a library into a course.
10481064
"""
10491065
# 1️⃣ First, create the html in the course, using the upstream problem as a template:
1066+
date_format = self.now.isoformat().split("+")[0] + 'Z'
10501067
downstream_html1 = self._create_block_from_upstream(
10511068
block_category="html",
10521069
parent_usage_key=str(self.course_subsection.usage_key),
@@ -1079,6 +1096,34 @@ def test_modified_html_copy_paste(self):
10791096
>This is the HTML.</html>
10801097
""")
10811098

1099+
# Check that: The downstream links are created as expected for the component
1100+
downstreams = self._get_downstream_links(
1101+
course_id=str(self.course.id)
1102+
)
1103+
expected_downstreams = [
1104+
{
1105+
'id': 1,
1106+
'upstream_context_title': self.library_title,
1107+
'upstream_version': 2,
1108+
'ready_to_sync': False,
1109+
'ready_to_sync_from_children': False,
1110+
'upstream_context_key': self.library_id,
1111+
'downstream_usage_key': downstream_html1["locator"],
1112+
'downstream_context_key': str(self.course.id),
1113+
'top_level_parent_usage_key': None,
1114+
'version_synced': 2,
1115+
'version_declined': None,
1116+
'created': date_format,
1117+
'updated': date_format,
1118+
'upstream_key': self.upstream_html1["id"],
1119+
'upstream_type': 'component',
1120+
'downstream_is_modified': False,
1121+
},
1122+
]
1123+
data = downstreams.json()
1124+
self.assertEqual(data["count"], 1)
1125+
self.assertListEqual(data["results"], expected_downstreams)
1126+
10821127
# 2️⃣ Now, lets modify the upstream html AND the downstream display_name:
10831128
self._update_course_block_fields(downstream_html1["locator"], {
10841129
"display_name": "New Text Content",
@@ -1111,9 +1156,36 @@ def test_modified_html_copy_paste(self):
11111156
'version_declined': None,
11121157
'ready_to_sync': True, # <--- updated
11131158
'error_message': None,
1114-
'is_modified': True,
1159+
'is_modified': True, # <--- updated
11151160
})
11161161

1162+
downstreams = self._get_downstream_links(
1163+
course_id=str(self.course.id)
1164+
)
1165+
expected_downstreams = [
1166+
{
1167+
'id': 1,
1168+
'upstream_context_title': self.library_title,
1169+
'upstream_version': 3, # <--- updated
1170+
'ready_to_sync': True, # <--- updated
1171+
'ready_to_sync_from_children': False,
1172+
'upstream_context_key': self.library_id,
1173+
'downstream_usage_key': downstream_html1["locator"],
1174+
'downstream_context_key': str(self.course.id),
1175+
'top_level_parent_usage_key': None,
1176+
'version_synced': 2,
1177+
'version_declined': None,
1178+
'created': date_format,
1179+
'updated': date_format,
1180+
'upstream_key': self.upstream_html1["id"],
1181+
'upstream_type': 'component',
1182+
'downstream_is_modified': True, # <--- updated
1183+
},
1184+
]
1185+
data = downstreams.json()
1186+
self.assertEqual(data["count"], 1)
1187+
self.assertListEqual(data["results"], expected_downstreams)
1188+
11171189
# 3️⃣ Now, sync and check the resulting OLX of the downstream
11181190

11191191
self._sync_downstream(downstream_html1["locator"])

0 commit comments

Comments
 (0)