Skip to content

Commit 7e42ce3

Browse files
authored
feat(content-libraries): include version numbers in history entries [FC-0123] (#38481)
- Added `old_version` and `new_version` to `LibraryHistoryEntry` and its serializer. - Populated these fields in block and container history APIs (draft history, publish history entries, and creation entry). - Preserved delete semantics by allowing `new_version` to be `null` for soft-deletes.
1 parent 39dacc6 commit 7e42ce3

5 files changed

Lines changed: 57 additions & 0 deletions

File tree

openedx/core/djangoapps/content_libraries/api/block_metadata.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ class LibraryHistoryEntry:
9595
title: str # title at time of change
9696
item_type: str
9797
action: str # "created" | "edited" | "renamed" | "deleted"
98+
old_version: int
99+
new_version: int | None
98100

99101

100102
@dataclass(frozen=True)

openedx/core/djangoapps/content_libraries/api/blocks.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,18 @@ def _contributor(user):
220220
entries = []
221221
for record in draft_change_records:
222222
version = record.new_version if record.new_version is not None else record.old_version
223+
# old_version is None only for the very first publish (entity had no prior published version)
224+
old_version_num = record.old_version.version_num if record.old_version else 0
225+
# new_version is None for soft-delete publishes (component deleted without a new draft version)
226+
new_version_num = record.new_version.version_num if record.new_version else None
223227
entries.append(LibraryHistoryEntry(
224228
contributor=_contributor(record.draft_change_log.changed_by),
225229
changed_at=record.draft_change_log.changed_at,
226230
title=version.title if version is not None else "",
227231
item_type=record.entity.component.component_type.name,
228232
action=resolve_change_action(record.old_version, record.new_version),
233+
old_version=old_version_num,
234+
new_version=new_version_num,
229235
))
230236
return entries
231237

@@ -351,12 +357,18 @@ def _contributor(user):
351357
# Deleted components can't reach this endpoint, so new_version is always set.
352358
# (Unlike containers — see get_library_container_publish_history_entries.)
353359
assert record.new_version is not None # for satisfy the type check
360+
# old_version is None only for the very first publish (entity had no prior published version)
361+
old_version_num = record.old_version.version_num if record.old_version else 0
362+
# new_version is None for soft-delete publishes (component deleted without a new draft version)
363+
new_version_num = record.new_version.version_num if record.new_version else None
354364
entries.append(LibraryHistoryEntry(
355365
contributor=_contributor(record.draft_change_log.changed_by),
356366
changed_at=record.draft_change_log.changed_at,
357367
title=record.new_version.title,
358368
item_type=record.entity.component.component_type.name,
359369
action=resolve_change_action(record.old_version, record.new_version),
370+
old_version=old_version_num,
371+
new_version=new_version_num,
360372
))
361373
return entries
362374

@@ -396,6 +408,8 @@ def get_library_component_creation_entry(
396408
title=first_version.title,
397409
item_type=component.component_type.name,
398410
action="created",
411+
old_version=0,
412+
new_version=first_version.version_num,
399413
)
400414

401415

openedx/core/djangoapps/content_libraries/api/containers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,13 +346,19 @@ def _contributor(user):
346346
# Use the new version when available; fall back to the old version
347347
# (e.g. for delete records where new_version is None).
348348
version = record.new_version if record.new_version is not None else record.old_version
349+
# old_version is None only for the very first publish (entity had no prior published version)
350+
old_version_num = record.old_version.version_num if record.old_version else 0
351+
# new_version is None for soft-delete publishes (container deleted without a new draft version)
352+
new_version_num = record.new_version.version_num if record.new_version else None
349353
item_type = get_entity_item_type(record.entity)
350354
results.append(LibraryHistoryEntry(
351355
contributor=_contributor(record.draft_change_log.changed_by),
352356
changed_at=record.draft_change_log.changed_at,
353357
title=version.title if version is not None else "",
354358
item_type=item_type,
355359
action=resolve_change_action(record.old_version, record.new_version),
360+
old_version=old_version_num,
361+
new_version=new_version_num,
356362
))
357363

358364
# Return all entries sorted newest-first across the container and its children.
@@ -576,13 +582,19 @@ def _contributor(user):
576582

577583
for record in records:
578584
version = record.new_version if record.new_version is not None else record.old_version
585+
# old_version is None only for the very first publish (entity had no prior published version)
586+
old_version_num = record.old_version.version_num if record.old_version else 0
587+
# new_version is None for soft-delete publishes (component deleted without a new draft version)
588+
new_version_num = record.new_version.version_num if record.new_version else None
579589
item_type = get_entity_item_type(record.entity)
580590
entries.append(LibraryHistoryEntry(
581591
contributor=_contributor(record.draft_change_log.changed_by),
582592
changed_at=record.draft_change_log.changed_at,
583593
title=version.title if version is not None else "",
584594
item_type=item_type,
585595
action=resolve_change_action(record.old_version, record.new_version),
596+
old_version=old_version_num,
597+
new_version=new_version_num,
586598
))
587599

588600
# Return entries sorted newest-first; use title as tiebreaker for determinism.
@@ -619,4 +631,6 @@ def get_library_container_creation_entry(
619631
title=first_version.title,
620632
item_type=container.container_type.type_code,
621633
action="created",
634+
old_version=0,
635+
new_version=first_version.version_num,
622636
)

openedx/core/djangoapps/content_libraries/rest_api/serializers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ class LibraryHistoryEntrySerializer(serializers.Serializer):
194194
title = serializers.CharField(read_only=True)
195195
item_type = serializers.CharField(read_only=True)
196196
action = serializers.CharField(read_only=True)
197+
old_version = serializers.IntegerField(read_only=True)
198+
new_version = serializers.IntegerField(read_only=True, allow_null=True)
197199

198200

199201
class UsageKeyV2Serializer(serializers.BaseSerializer):

openedx/core/djangoapps/content_libraries/tests/test_content_libraries.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,8 @@ def test_draft_history_action_created(self):
977977
history = self._get_block_draft_history(block_key)
978978
assert len(history) >= 1
979979
assert history[-1]["action"] == "created"
980+
assert history[-1]["old_version"] == 0
981+
assert history[-1]["new_version"] is not None
980982

981983
def test_draft_history_action_deleted(self):
982984
"""
@@ -1153,6 +1155,25 @@ def test_publish_history_entries(self):
11531155
assert "changed_at" in entry
11541156
assert "title" in entry
11551157
assert "action" in entry
1158+
assert "old_version" in entry
1159+
assert "new_version" in entry
1160+
1161+
def test_draft_history_deleted_has_null_new_version(self):
1162+
"""
1163+
Deleted draft history entry exposes new_version as null.
1164+
"""
1165+
lib = self._create_library(slug="draft-hist-delete-null", title="Draft History Delete Null")
1166+
block = self._add_block_to_library(lib["id"], "problem", "prob1")
1167+
block_key = block["id"]
1168+
1169+
self._publish_library_block(block_key)
1170+
self._delete_library_block(block_key)
1171+
1172+
history = self._get_block_draft_history(block_key)
1173+
assert len(history) >= 1
1174+
assert history[0]["action"] == "deleted"
1175+
assert history[0]["old_version"] > 0
1176+
assert history[0]["new_version"] is None
11561177

11571178
def test_publish_history_entries_unknown_uuid(self):
11581179
"""
@@ -1436,6 +1457,8 @@ def test_creation_entry_returns_first_version(self):
14361457
assert entry is not None
14371458
assert entry["action"] == "created"
14381459
assert entry["item_type"] == "problem"
1460+
assert entry["old_version"] == 0
1461+
assert entry["new_version"] == 1
14391462
assert "changed_at" in entry
14401463
assert "title" in entry
14411464
assert "contributor" in entry
@@ -1492,6 +1515,8 @@ def test_container_creation_entry_returns_first_version(self):
14921515
assert entry is not None
14931516
assert entry["action"] == "created"
14941517
assert entry["item_type"] == "unit"
1518+
assert entry["old_version"] == 0
1519+
assert entry["new_version"] == 1
14951520
assert entry["title"] == "My Unit"
14961521
assert "changed_at" in entry
14971522
assert "contributor" in entry

0 commit comments

Comments
 (0)