Follow-up to #8318 (SharePoint Graph Module - extend functionality with additional methods), which was merged before these review points were addressed. Volodymyr Dvernytskyi (@Drakonian), could you pick these up in a follow-up PR?
Describe the issue
Code review of #8318 found one behavioural defect and several convention issues in the new UpdateListItem / UpdateDriveItem / RenameDriveItem code paths.
1. Silent field loss in UpdateListItem (main issue)
In SharePoint Graph Client Impl. (codeunit 9120), UpdateListItem does:
GraphListItem.Init();
GraphListItem.Id := CopyStr(ItemId, 1, MaxStrLen(GraphListItem.Id));
GraphListItem.ListId := CopyStr(ListId, 1, MaxStrLen(GraphListItem.ListId));
SharePointGraphParser.ParseListItemFieldValueSet(JsonResponse, GraphListItem);
if not GraphListItem.Insert() then
GraphListItem.Modify();
The PATCH to .../items/{id}/fields returns only a fieldValueSet, so it carries no ContentType, WebUrl, CreatedDateTime or LastModifiedDateTime. When a caller reuses a buffer that was previously populated by GetListItem, the Init() blanks those fields and the Modify() writes the blanks back over data that was already correct.
The XML doc says the record "is refreshed", which understates this - the record is effectively replaced, and four previously-valid fields are silently cleared.
2. ItemBufferCollisionTelemetryErr is missing Locked = true
ItemBufferCollisionTelemetryErr: Label 'The record already contains item from a different list. Use a separate record variable per list.';
It is only ever passed to Session.LogMessage, so it should be locked. It is the only telemetry-only label in the file without it (compare OperationSuccessTelemetryMsg and GraphSharePointCategoryLbl). As-is it gets sent for translation and emits localized telemetry.
3. Hand-rolled query string in GetListItem
Endpoint := SharePointGraphUriBuilder.GetListItemByIdEndpoint(ListId, ItemId);
if not GraphOptionalParameters.GetODataQueryParameters().ContainsKey(Format(Enum::"Graph OData Query Parameter"::expand)) then
Endpoint += '?$expand=fields';
This is the only raw query-string concatenation in the module. It works, but the idiomatic form is GraphOptionalParameters.SetODataQueryParameter(Enum::"Graph OData Query Parameter"::expand, 'fields') - the parameter is passed by value, so mutating the local copy is safe and it keeps all URI assembly inside the builder.
4. ParseListItemFieldValueSet has no return value
Every other parser in SharePoint Graph Parser returns Boolean and callers check it. This one does not, so UpdateListItem cannot detect a malformed PATCH response and will report success on garbage input.
5. Telemetry event IDs are shared across four public entry points
UpdateDriveItem, UpdateDriveItemByPath, RenameDriveItem and RenameDriveItemByPath all funnel through UpdateDriveItemAtEndpoint, so all four log 0000UKR / 0000UKS / 0000UKT with the operation name hard-coded as 'UpdateDriveItem'. A RenameDriveItemByPath failure is indistinguishable from an UpdateDriveItem failure in telemetry.
Separately, it is still worth confirming that 0000UKB-0000UKV are actually allocated to this area - this is the question Magnus Hartvig Grønbech (@Groenbech96) raised on the PR in July that was never answered.
Expected behavior
UpdateListItem should either preserve the fields the PATCH response does not return, or the XML doc on both the facade and the implementation should state plainly that the record is replaced and that ContentType, WebUrl, CreatedDateTime and LastModifiedDateTime are cleared. Preserving them is the better outcome.
ItemBufferCollisionTelemetryErr carries Locked = true.
GetListItem sets the default expand through Graph Optional Parameters rather than string concatenation.
ParseListItemFieldValueSet returns Boolean and UpdateListItem checks it, matching the surrounding parser contract.
- Each public update/rename entry point is distinguishable in telemetry.
Steps to reproduce
For the UpdateListItem field loss:
- Call
GetListItem(ListId, ItemId, TempGraphListItem) and observe WebUrl, ContentType, CreatedDateTime and LastModifiedDateTime are populated.
- Call
UpdateListItem(ListId, ItemId, FieldsJson, TempGraphListItem) with the same record variable.
- Observe those four fields are now blank, while
Title and the fields JSON reflect the update.
TestUpdateListItem_BufferReusedFromGet in codeunit 132984 covers this exact sequence but only asserts Id, Title and the request count, so the loss is not caught. Extending that test with assertions on WebUrl / ContentType reproduces it.
The remaining points are visible by inspection of SharePointGraphClientImpl.Codeunit.al and SharePointGraphParser.Codeunit.al.
Additional context
Two further review points from #8318 that are no longer actionable in code, noted here only so the thread is complete:
- The PR description's validation checklist described a follow-up GET after the PATCH and a
UpdateListItem_FollowUpGetFails test. Neither exists in the merged code - UpdateListItem issues a single PATCH. It also stated 22 new tests where there are 25. The description appears to have been written against an earlier iteration; worth keeping the checklist in sync with the final diff on future PRs, since reviewers lean on it.
- Roughly 837 of the 862 added README lines are a new architecture document (dependency graphs, object registry, test-coverage counts, pattern catalogue) unrelated to the linked issue. The object-registry and test-coverage sections in particular will drift out of date quickly - worth deciding whether they should be maintained or trimmed.
One consistency observation for whoever picks this up: the new methods use if not Insert() then Modify(), while the pre-existing GetDriveItem uses a bare Insert() and hard-errors on a reused buffer. TestStickyModeStillWorks works around this with a TempDriveItem.DeleteAll(). Not introduced by this PR, but the client now has two different buffer-reuse contracts, and aligning them would be worthwhile.
Original review context: #8318, fixes #8317.
Follow-up to #8318 (SharePoint Graph Module - extend functionality with additional methods), which was merged before these review points were addressed. Volodymyr Dvernytskyi (@Drakonian), could you pick these up in a follow-up PR?
Describe the issue
Code review of #8318 found one behavioural defect and several convention issues in the new
UpdateListItem/UpdateDriveItem/RenameDriveItemcode paths.1. Silent field loss in
UpdateListItem(main issue)In
SharePoint Graph Client Impl.(codeunit 9120),UpdateListItemdoes:The PATCH to
.../items/{id}/fieldsreturns only afieldValueSet, so it carries noContentType,WebUrl,CreatedDateTimeorLastModifiedDateTime. When a caller reuses a buffer that was previously populated byGetListItem, theInit()blanks those fields and theModify()writes the blanks back over data that was already correct.The XML doc says the record "is refreshed", which understates this - the record is effectively replaced, and four previously-valid fields are silently cleared.
2.
ItemBufferCollisionTelemetryErris missingLocked = trueIt is only ever passed to
Session.LogMessage, so it should be locked. It is the only telemetry-only label in the file without it (compareOperationSuccessTelemetryMsgandGraphSharePointCategoryLbl). As-is it gets sent for translation and emits localized telemetry.3. Hand-rolled query string in
GetListItemThis is the only raw query-string concatenation in the module. It works, but the idiomatic form is
GraphOptionalParameters.SetODataQueryParameter(Enum::"Graph OData Query Parameter"::expand, 'fields')- the parameter is passed by value, so mutating the local copy is safe and it keeps all URI assembly inside the builder.4.
ParseListItemFieldValueSethas no return valueEvery other parser in
SharePoint Graph ParserreturnsBooleanand callers check it. This one does not, soUpdateListItemcannot detect a malformed PATCH response and will report success on garbage input.5. Telemetry event IDs are shared across four public entry points
UpdateDriveItem,UpdateDriveItemByPath,RenameDriveItemandRenameDriveItemByPathall funnel throughUpdateDriveItemAtEndpoint, so all four log0000UKR/0000UKS/0000UKTwith the operation name hard-coded as'UpdateDriveItem'. ARenameDriveItemByPathfailure is indistinguishable from anUpdateDriveItemfailure in telemetry.Separately, it is still worth confirming that
0000UKB-0000UKVare actually allocated to this area - this is the question Magnus Hartvig Grønbech (@Groenbech96) raised on the PR in July that was never answered.Expected behavior
UpdateListItemshould either preserve the fields the PATCH response does not return, or the XML doc on both the facade and the implementation should state plainly that the record is replaced and thatContentType,WebUrl,CreatedDateTimeandLastModifiedDateTimeare cleared. Preserving them is the better outcome.ItemBufferCollisionTelemetryErrcarriesLocked = true.GetListItemsets the default expand throughGraph Optional Parametersrather than string concatenation.ParseListItemFieldValueSetreturnsBooleanandUpdateListItemchecks it, matching the surrounding parser contract.Steps to reproduce
For the
UpdateListItemfield loss:GetListItem(ListId, ItemId, TempGraphListItem)and observeWebUrl,ContentType,CreatedDateTimeandLastModifiedDateTimeare populated.UpdateListItem(ListId, ItemId, FieldsJson, TempGraphListItem)with the same record variable.Titleand the fields JSON reflect the update.TestUpdateListItem_BufferReusedFromGetin codeunit 132984 covers this exact sequence but only assertsId,Titleand the request count, so the loss is not caught. Extending that test with assertions onWebUrl/ContentTypereproduces it.The remaining points are visible by inspection of
SharePointGraphClientImpl.Codeunit.alandSharePointGraphParser.Codeunit.al.Additional context
Two further review points from #8318 that are no longer actionable in code, noted here only so the thread is complete:
UpdateListItem_FollowUpGetFailstest. Neither exists in the merged code -UpdateListItemissues a single PATCH. It also stated 22 new tests where there are 25. The description appears to have been written against an earlier iteration; worth keeping the checklist in sync with the final diff on future PRs, since reviewers lean on it.One consistency observation for whoever picks this up: the new methods use
if not Insert() then Modify(), while the pre-existingGetDriveItemuses a bareInsert()and hard-errors on a reused buffer.TestStickyModeStillWorksworks around this with aTempDriveItem.DeleteAll(). Not introduced by this PR, but the client now has two different buffer-reuse contracts, and aligning them would be worthwhile.Original review context: #8318, fixes #8317.