Skip to content

Features/637301 report layout override lifecycle#9428

Open
V-CWurtzen wants to merge 4 commits into
mainfrom
features/637301-report-layout-override-lifecycle
Open

Features/637301 report layout override lifecycle#9428
V-CWurtzen wants to merge 4 commits into
mainfrom
features/637301-report-layout-override-lifecycle

Conversation

@V-CWurtzen

@V-CWurtzen V-CWurtzen commented Jul 14, 2026

Copy link
Copy Markdown

Summary

Application-side implementation of CP0529-338: lifecycle/property changes to an extension-installed report layout now write a Tenant Report Layout Override record instead of copying the layout into Tenant Report Layout (which left the original visible with its default values).

  • Layout Status (Set Approved/Draft/Pending/Retired) on extension layouts writes an override. A batch run must be single-scope (a mix of global and company-specific layouts is rejected), and an all-global run asks for confirmation before applying.
  • Edit info overrides Description and IsObsolete (one-way — a layout already obsolete in metadata cannot be un-obsoleted), with global vs. company scope chosen via Available in All Companies. "Save Changes to a Copy" is kept as an opt-in escape hatch.
  • User-defined layouts are unchanged (in-place update in Tenant Report Layout).

The update path is chosen by Report Layout List."User Defined": true → in-place update; false → upsert a Tenant Report Layout Override record. Only fields whose Override * flag is set are applied.

Tests

Report Layouts Test (codeunit 139595): 23/23 pass, covering status/edit overrides, global vs. company scope, the global-scope confirmation, and mixed-scope rejection. A second shipped layout (MYLAYOUT2) was added to the test report so multi-layout scope scenarios are exercisable.

Platform dependency (CI compile)

This change references the platform table Tenant Report Layout Override (2000000248), which first ships in platform 29.0.52163.0. The repo currently pins artifact: bcinsider/Sandbox/29.0.51936.0, which predates that table, so the Compile Apps step fails with AL0185 Table 'Tenant Report Layout Override' is missing. CI can only go green once the platform artifact pin is advanced to a build that includes object 2000000248. The AL code itself compiles against the newer platform symbols.

AB#637301

Setting the Layout Status of an extension-installed layout (User Defined =
false) now writes a Tenant Report Layout Override record instead of a no-op,
rather than copying the layout into Tenant Report Layout.

- ReportLayoutsImpl: add UpsertLayoutOverride helper (writes only the flagged
  fields with their Override * flags; IsObsolete is one-way); reroute
  SetLayoutStatus non-user-defined branch to it; grant RIMD on
  Tenant Report Layout Override.
- ReportLayouts.page: relax CanModifyStatus so extension layouts can use the
  status actions.
- Tests: add TestSetExtensionLayoutStatusWritesOverride (asserts an override
  is written with no tenant copy); clear overrides in EnsureNewLayoutsAreCleaned.

Verified: codeunit 139595 all 19 tests pass.
Editing an extension-installed layout's info now writes a Tenant Report Layout
Override record (Description, one-way IsObsolete) instead of copying the layout.

- ReportLayoutsImpl.EditReportLayout: for a non-user-defined layout not being
  copied, write an override via UpsertLayoutOverride and exit; existing
  copy/in-place logic unchanged.
- ReportLayoutEditDialog: override mode for extension layouts - name read-only,
  Description editable, IsObsolete editable one-way (locked when already obsolete
  in metadata), AvailableInAllCompanies drives global vs company scope; copy kept
  as an opt-in escape hatch.
- ReportLayouts.page: EditLayout no longer forces the 'edit a copy instead'
  confirm for extension layouts; removed the now-unused label.
- Tests: add global-description and company-obsolete override tests (assert
  override written, no tenant copy).

Verified: codeunit 139595 all 21 tests pass.
Batch status actions (Set Approved/Draft/Pending/Retired) now resolve the
override scope per extension-installed layout and keep each run to one scope:

- LayoutStatusIsGlobalScope: current-company override wins (company scope);
  else an existing global override => global scop
- SetLayoutStatus: extension layouts override at the classified scope.
- SetLayoutStatusBatch: reject mixed-scope select
  confirm all-global changes (GlobalScopeConfirmQst) before applying;
  user-defined layouts still update in place and

Fresh extension layouts stay company-specific wit
shipped user-defined behaviour with minimal user-facing change.

Tests: add TestSetGlobalScopeExtensionLayoutStatusConfirmsAndUpdatesGlobal
(global override updated after confirm; no compan
Mixed-scope Error is not auto-tested (test report ships a single extension
layout); manually verifiable.
Wording/permission (ReportLayouts.page):
- Retire "user-defined" from the four Set<Status> tooltips; the status actions
  now cover extension-installed layouts too (via override).
- Document why the AccessByPermission gate stays tabledata "Tenant Report
  Layout" = M: an action takes a single tabledata permission, and the platform's
  BaseSystemPermissionSet grants Tenant Report Layout and Tenant Report Layout
  Override together, so this gate represents "may manage layout status" and
  matches the page's own permission.

Mixed-scope test coverage (Tests-Report):
- Add TestMixedScopeBatchStatusIsRejected, driving the internal Report Layouts
  Impl. codeunit directly (Tests-Report is in Bas a
  filtered Report Layout List spanning two extension layouts, asserting the
  mixed-scope error. A TestPage cannot multi-sele
- Add a second shipped layout (MYLAYOUT2, same rdl) to test report 139595 so
  multi-layout scope scenarios are exercisable.

Verified: codeunit 139595 all 23 tests pass.
@V-CWurtzen V-CWurtzen requested a review from a team July 14, 2026 12:35
if (CreateCopy) then
// In override mode (extension layout) the user always chooses global vs company
// scope, whether they override in place or opt into a copy.
if OverrideMode then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Accessibility} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

For an extension-installed layout (override mode), SetupDialog hard-codes LayoutNameEditable := false, and the CreateCopy field's OnValidate trigger only toggles AvailableInAllCompaniesEditable when OverrideMode is true -- it never re-enables LayoutNameEditable.

The surrounding comment describes 'Save Changes to a Copy' as an opt-in escape hatch 'to fork the layout content into a user layout', implying the fork can be named, but the Layout Name field stays read-only even after the user checks CreateCopy, so the resulting copy is always inserted under the exact same name as the source extension layout. Before this change the field had no Editable restriction and could always be renamed, so this is a regression, not a new deliberate restriction.

Recommendation:

  • also set LayoutNameEditable := CreateCopy in the OverrideMode branch of the CreateCopy field's OnValidate trigger.

Suggested fix (apply manually — could not be anchored as a one-click suggestion):

                trigger OnValidate()
                begin
                    // In override mode (extension layout) the user always chooses global vs company
                    // scope, whether they override in place or opt into a copy. Checking CreateCopy
                    // also re-enables the Layout Name so the forked copy can be given a distinct name.
                    if OverrideMode then begin
                        AvailableInAllCompaniesEditable := true;
                        LayoutNameEditable := CreateCopy;
                    end else
                        if (CreateCopy) then
                            AvailableInAllCompaniesEditable := true
                        else
                            if (IsLayoutOwnedByCurrentCompany) then begin
                                AvailableInAllCompaniesEditable := true;
                                AvailableInAllCompanies := false;
                            end else begin
                                AvailableInAllCompaniesEditable := false;
                                AvailableInAllCompanies := true;
                            end;
                end;

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

// mutable properties (Description, IsObsolete) instead of copying the layout. The layout
// name/identity cannot change here, so NewLayoutName is not used.
if (not SelectedReportLayoutList."User Defined") and (not CreateCopy) then begin
UpsertLayoutOverride(SelectedReportLayoutList, AvailableInAllCompanies, true, NewDescription, false, Enum::"Report Layout Status"::Draft, true, NewIsObsolete);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Agent} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

SetLayoutStatusBatch requires an explicit Confirm(GlobalScopeConfirmQst) before a status change touches a GLOBAL-scope override, because that write affects every company.

EditReportLayout's new override branch (used when editing an extension layout's Description/IsObsolete) writes through the same UpsertLayoutOverride with MakeGlobal := AvailableInAllCompanies but has no equivalent confirmation gate -- and ReportLayoutEditDialog.SetupDialog hard-codes AvailableInAllCompanies := true for every extension-layout edit, regardless of the layout's current override scope. A user who opens 'Edit Info' on an extension layout, changes only the description, and clicks OK without touching the checkbox will silently create or update an override scoped to every company -- the same class of cross-company blast radius the batch-status path treats as important enough to gate behind a confirmation. The real-world impact of this gap is closer to major (silent cross-company effect with no prompt), but it is reported at minor per this pass's agent-finding ceiling; treat it as a candidate for a dedicated knowledge rule or a fix before merge.

Recommendation:

  • default AvailableInAllCompanies to the layout's current override scope (mirroring LayoutStatusIsGlobalScope) rather than hard-coding true, and/or add the same Confirm(GlobalScopeConfirmQst) gate in EditReportLayout when MakeGlobal is true.

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why


CustomDimensions.Add('ReportId', Format(SelectedReportLayoutList."Report ID"));
CustomDimensions.Add('LayoutName', SelectedReportLayoutList.Name);
CustomDimensions.Add('NewLayoutDescription', NewDescription);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Privacy} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

In the new extension-layout override branch of EditReportLayout, the user-typed 'Description' value (NewDescription) is added verbatim to the CustomDimensions dictionary ('NewLayoutDescription') passed to Session.LogMessage/Log, which ships with TelemetryScope::All.

Free-text description/notes fields are exactly the content this guidance says must not travel into telemetry, whether in the message text or a custom dimension. This mirrors a pre-existing pattern used by the sibling 'Edit' (non-override) path, but the new code re-introduces it.

Recommendation:

  • drop the 'NewLayoutDescription' dimension (and its counterpart in the pre-existing Edit path if this is revisited) or replace it with a non-content signal such as a boolean 'DescriptionChanged'.

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

CustomDimensions.Add('LayoutName', SelectedReportLayoutList.Name);
CustomDimensions.Add('NewLayoutDescription', NewDescription);
AddReportLayoutDimensionsAction('EditOverride', CustomDimensions);
Log('0000N0H', 'Report layout properties overridden by user', CustomDimensions);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$\textbf{🟡\ Medium\ Severity\ —\ Style} \quad \color{gray}{\texttt{\small Iteration\ 1}}$

The new 'EditOverride' action reuses event ID '0000N0H' with message 'Report layout properties overridden by user', which is the exact same event ID already used a few lines away by the unrelated 'Edit' action's 'Report layout properties changed by user' message.

The two describe different code paths (in-place override for extension layouts vs. copy/rename for user-defined layouts) and are only distinguishable downstream by inspecting the 'Action' custom dimension, not the event ID itself.

Recommendation:

  • assign 'EditOverride' its own registered event ID rather than reusing '0000N0H'.

Knowledge:

👍 useful · ❤️ especially valuable · 👎 wrong - reply with why

@github-actions

Copy link
Copy Markdown
Contributor

Copilot PR Review

Iteration 1 · Outcome: completed

Knowledge source: https://github.com/microsoft/BCQuality@be1b92b624679f8c031061602e7d3a3b5f71a688

Findings by domain

Findings split into Knowledge-backed (cite a BCQuality article) and Agent (the agent's own judgement, no matching BCQuality rule).

Domain Findings Knowledge-backed Agent Inline Fallback
Accessibility 1 1 0 1 0
Agent 1 0 1 1 0
Privacy 1 1 0 1 0
Style 1 1 0 1 0

Totals: 3 knowledge-backed · 1 agent findings.

Orchestrator pre-filter (16 file(s) excluded)

  • layer-disabled (knowledge) : 16 file(s)

Findings produced by the Copilot CLI agent against BCQuality at be1b92b624679f8c031061602e7d3a3b5f71a688. Reply 👎 on any inline comment to flag false positives.

@github-actions github-actions Bot added this to the Version 29.0 milestone Jul 14, 2026
@microsoft-github-policy-service

Copy link
Copy Markdown

@V-CWurtzen please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="{your company}"]

Options:

  • (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
@microsoft-github-policy-service agree
  • (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
@microsoft-github-policy-service agree company="Microsoft"
Contributor License Agreement

Contribution License Agreement

This Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
and conveys certain license rights to Microsoft Corporation and its affiliates (“Microsoft”) for Your
contributions to Microsoft open source projects. This Agreement is effective as of the latest signature
date below.

  1. Definitions.
    “Code” means the computer software code, whether in human-readable or machine-executable form,
    that is delivered by You to Microsoft under this Agreement.
    “Project” means any of the projects owned or managed by Microsoft and offered under a license
    approved by the Open Source Initiative (www.opensource.org).
    “Submit” is the act of uploading, submitting, transmitting, or distributing code or other content to any
    Project, including but not limited to communication on electronic mailing lists, source code control
    systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
    discussing and improving that Project, but excluding communication that is conspicuously marked or
    otherwise designated in writing by You as “Not a Submission.”
    “Submission” means the Code and any other copyrightable material Submitted by You, including any
    associated comments and documentation.
  2. Your Submission. You must agree to the terms of this Agreement before making a Submission to any
    Project. This Agreement covers any and all Submissions that You, now or in the future (except as
    described in Section 4 below), Submit to any Project.
  3. Originality of Work. You represent that each of Your Submissions is entirely Your original work.
    Should You wish to Submit materials that are not Your original work, You may Submit them separately
    to the Project if You (a) retain all copyright and license information that was in the materials as You
    received them, (b) in the description accompanying Your Submission, include the phrase “Submission
    containing materials of a third party:” followed by the names of the third party and any licenses or other
    restrictions of which You are aware, and (c) follow any other instructions in the Project’s written
    guidelines concerning Submissions.
  4. Your Employer. References to “employer” in this Agreement include Your employer or anyone else
    for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
    Submission is made in the course of Your work for an employer or Your employer has intellectual
    property rights in Your Submission by contract or applicable law, You must secure permission from Your
    employer to make the Submission before signing this Agreement. In that case, the term “You” in this
    Agreement will refer to You and the employer collectively. If You change employers in the future and
    desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
    and secure permission from the new employer before Submitting those Submissions.
  5. Licenses.
  • Copyright License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license in the
    Submission to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute
    the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third
    parties.
  • Patent License. You grant Microsoft, and those who receive the Submission directly or
    indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license under
    Your patent claims that are necessarily infringed by the Submission or the combination of the
    Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
    import or otherwise dispose of the Submission alone or with the Project.
  • Other Rights Reserved. Each party reserves all rights not expressly granted in this Agreement.
    No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
    granted by implication, exhaustion, estoppel or otherwise.
  1. Representations and Warranties. You represent that You are legally entitled to grant the above
    licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
    have disclosed under Section 3). You represent that You have secured permission from Your employer to
    make the Submission in cases where Your Submission is made in the course of Your work for Your
    employer or Your employer has intellectual property rights in Your Submission by contract or applicable
    law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
    have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
    You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
    REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
    EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
    PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
    NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
  2. Notice to Microsoft. You agree to notify Microsoft in writing of any facts or circumstances of which
    You later become aware that would make Your representations in this Agreement inaccurate in any
    respect.
  3. Information about Submissions. You agree that contributions to Projects and information about
    contributions may be maintained indefinitely and disclosed publicly, including Your name and other
    information that You submit with Your Submission.
  4. Governing Law/Jurisdiction. This Agreement is governed by the laws of the State of Washington, and
    the parties consent to exclusive jurisdiction and venue in the federal courts sitting in King County,
    Washington, unless no federal subject matter jurisdiction exists, in which case the parties consent to
    exclusive jurisdiction and venue in the Superior Court of King County, Washington. The parties waive all
    defenses of lack of personal jurisdiction and forum non-conveniens.
  5. Entire Agreement/Assignment. This Agreement is the entire agreement between the parties, and
    supersedes any and all prior agreements, understandings or communications, written or oral, between
    the parties relating to the subject matter hereof. This Agreement may be assigned by Microsoft.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant