Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to Hermes will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed

- [Clients] Fixed Jinja expressions relying on multiple attributes not rendered properly when one of them was removed (#1).
**IMPORTANT NOTE** This bug only affects clients that contain Jinja expressions that rely on multiple attributes from the server. If your clients are likely to be impacted, here are the steps to fix invalid values (proceed on each client):
1. Edit the client's config file, and in each data-type declared and maybe impacted, insert a non-significant space into the Jinja expression : e.g. replace `{{ attr1 ~ attr2 }}` by `{{ attr1 ~ attr2 }}`. This simple change will trigger a datamodel update on the next client startup, which will recalculate the values of all Jinja expressions and propagate the new (good) values if they differ from the previous ones.
2. Restart your client.

## [v1.0.5] - 2025-07-08

### Fixed
Expand Down
50 changes: 38 additions & 12 deletions clients/datamodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,13 +509,13 @@ def convertEventToLocal(
# Handle that event.objattrs is 1 depth deeper for "modified" events
if event.eventtype == "modified":
sources = ("added", "modified", "removed")
objattrs = {"added": {}, "modified": {}, "removed": {}}
else:
sources = (None,)
objattrs = {}

hasContent: bool = False
objattrs = {}
for source in sources:
attrs = {}
if source is None:
src = event.objattrs
else:
Expand Down Expand Up @@ -549,17 +549,43 @@ def convertEventToLocal(
if type(val) is list:
val = [v for v in val if v is not None]

if source == "removed" or (val is not None and val != []):
attrs[dest] = val
else:
attrs[dest] = v
if attrs:
hasContent = True
if val is None or val == []:
# No value
if event.eventtype == "modified":
objattrs["removed"].update({dest: val})
elif event.eventtype == "removed":
objattrs.update({dest: val})
else:
# In modified events, we have to determine if the
# attribute is added or modified
if event.eventtype == "modified":
_, cachedObj = self.getObjectFromCacheOrTrashbin(
self.localdata_complete,
self.typesmapping[event.objtype],
event.objpkey,
)

if source is None:
objattrs = attrs
else:
objattrs[source] = attrs
if cachedObj is not None and hasattr(
cachedObj, dest
):
# Ensure the value has changed
previousVal = getattr(cachedObj, dest)
if DataObject.isDifferent(previousVal, val):
objattrs["modified"].update({dest: val})
hasContent = True
else:
# Attr is added
objattrs["added"].update({dest: val})
hasContent = True
else:
objattrs.update({dest: val})
hasContent = True
else:
if source is None:
objattrs.update({dest: v})
else:
objattrs[source].update({dest: v})
hasContent = True

res = None
if hasContent or allowEmptyEvent or event.eventtype == "removed":
Expand Down
3 changes: 3 additions & 0 deletions tests/functional/fixtures/config_files/client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ hermes-client:
first_name: first_name
middle_name: middle_name
last_name: last_name
displayname: "{{ (first_name ~ ' ' ~ last_name ~ ' (Engineering)') if 'engineering' in (specialty|default('')) else first_name ~ ' ' ~ last_name }}"
dateOfBirth: dateOfBirth
login: login
specialty: specialty
Expand Down Expand Up @@ -113,6 +114,7 @@ hermes-client:
first_name: first_name
middle_name: middle_name
last_name: last_name
displayname: "{{ (first_name ~ ' ' ~ last_name ~ ' (Engineering)') if 'engineering' in (specialty|default('')) else first_name ~ ' ' ~ last_name }}"
dateOfBirth: dateOfBirth
login: login
specialty: specialty
Expand Down Expand Up @@ -142,6 +144,7 @@ hermes-client:
first_name: first_name
middle_name: middle_name
last_name: last_name
displayname: "{{ (first_name ~ ' ' ~ last_name ~ ' (Engineering)') if 'engineering' in (specialty|default('')) else first_name ~ ' ' ~ last_name }}"
dateOfBirth: dateOfBirth
login: login
specialty: specialty
Expand Down
8 changes: 8 additions & 0 deletions tests/functional/test_scenario_01_single_datasource.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,14 @@ def test_005_update_values(self):
"dateOfBirth": "1965-01-13T12:34:56", # Modify time
"desired_jobs_joined": "Arboriculturist", # Remove Copywriter, advertising
"desired_job_1": None, # Remove Copywriter, advertising
"specialty": None, # Remove Automotive engineering
}
expectedjvang = deepcopy(self.serverdata("SRVUsers")[jvanguid].toNative())
expectedjvang["middle_name"] = "Jack"
expectedjvang["dateOfBirth"] = datetime(1965, 1, 13, 12, 34, 56)
expectedjvang["desired_jobs_joined"].remove("Copywriter, advertising")
expectedjvang["desired_jobs_columns"].remove("Copywriter, advertising")
del expectedjvang["specialty"]

mpateluid = "97fe56c5-4c9a-4f24-97b4-c294bd44089d"
mpatel = {
Expand Down Expand Up @@ -307,12 +309,14 @@ def test_005_update_values(self):
self.assertClientdataLen()

expectedjvang["_pkey_id"] = jvanguid
expectedjvang["displayname"] = "Joe Vang"
del expectedjvang["id"]
del expectedjvang["simpleid"]
self.assertDictEqual(
expectedjvang, self.clientdata("Users")[jvanguid].toNative()
)
expectedmpatel["_pkey_id"] = mpateluid
expectedmpatel["displayname"] = "Maria Patel"
del expectedmpatel["id"]
del expectedmpatel["simpleid"]
self.assertDictEqual(
Expand Down Expand Up @@ -1130,6 +1134,7 @@ def test_401b_client_restore_trashed_user(self):
"_pkey_id": tmays_id,
"first_name": "Troy",
"last_name": "Mays",
"displayname": "Troy Mays",
"dateOfBirth": datetime(1977, 12, 9, 0, 0),
"login": "tmays",
"specialty": "Mechatronics",
Expand Down Expand Up @@ -1162,6 +1167,7 @@ def test_401b_client_restore_trashed_user(self):
"_pkey_id": tmays_id,
"first_name": "Troy",
"last_name": "Mays",
"displayname": "Troy Mays",
"login": "tmays_modified",
"specialty": "Mechatronics",
"desired_jobs_joined": [
Expand Down Expand Up @@ -1294,6 +1300,7 @@ def test_501e_client_maxremediation_removed_then_added_with_previous_added(
# Verify that cached data is expected data
expectedtwagner = deepcopy(self.serverdata("SRVUsers")[twagneruid].toNative())
expectedtwagner["_pkey_id"] = twagneruid
expectedtwagner["displayname"] = "Tamara Wagner (Engineering)"
del expectedtwagner["id"]
del expectedtwagner["simpleid"]
self.assertDictEqual(
Expand Down Expand Up @@ -1398,6 +1405,7 @@ def test_502f_client_maxremediation_removed_then_added_with_prev_local_modified(
# Verify that cached data is expected data
expectedtwagner = deepcopy(self.serverdata("SRVUsers")[twagneruid].toNative())
expectedtwagner["_pkey_id"] = twagneruid
expectedtwagner["displayname"] = "Tamara Wagner (Engineering)"
del expectedtwagner["id"]
del expectedtwagner["simpleid"]
self.assertDictEqual(
Expand Down