Skip to content

Allow Template upgrade properties to be set and remove properties that no longer exist.#4783

Open
JC-wk wants to merge 75 commits into
microsoft:mainfrom
JC-wk:template-upgrade-properties
Open

Allow Template upgrade properties to be set and remove properties that no longer exist.#4783
JC-wk wants to merge 75 commits into
microsoft:mainfrom
JC-wk:template-upgrade-properties

Conversation

@JC-wk

@JC-wk JC-wk commented Dec 16, 2025

Copy link
Copy Markdown
Collaborator

Resolves #4732 #4730

What is being addressed

Currently if you add a new property to a template there is no way to specify this property before an upgrade is ran.
The upgrade may fail due to the missing property (although the template version is still incremented)
The user then has to click update and supply the property.
Similarly when removing a property from a template and running an upgrade, the property still exists on the resource.

Todo

How is this addressed

  • Adds a form to allow the user to specify new properties prior to the upgrade
  • Removes any template properties that no longer exist in the new template
  • Added tests
  • Updated CHANGELOG.md
  • Increment API version
image image

James Chapman and others added 11 commits December 10, 2025 15:23
This commit aligns the resource upgrade process with the update process by correctly handling conditional properties in the JSON schema.

- The schema generation logic in `ConfirmUpgradeResource.tsx` is updated to include conditional blocks (`if`/`then`/`else`) when the condition is based on an existing property.
- New read-only properties are now submitted during the upgrade process.
This commit aligns the resource upgrade process with the update process by correctly handling conditional properties in the JSON schema.

- The schema generation logic in `ConfirmUpgradeResource.tsx` is updated to include conditional blocks (`if`/`then`/`else`) when the condition is based on an existing property.
- New read-only properties are now submitted during the upgrade process.
- The `liveOmit` prop is added to the form to prevent the submission of unevaluated properties from conditionally hidden fields.
…1346005040390942732

Fix Upgrade Conditional Properties
@JC-wk
JC-wk requested a review from a team as a code owner December 16, 2025 09:54
@github-actions

github-actions Bot commented Dec 16, 2025

Copy link
Copy Markdown

Unit Test Results

947 tests   947 ✅  36s ⏱️
 28 suites    0 💤
  2 files      0 ❌

Results for commit 674c059.

♻️ This comment has been updated with latest results.

@JC-wk
JC-wk marked this pull request as draft December 16, 2025 10:02
Copilot AI review requested due to automatic review settings July 24, 2026 13:22

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • ui/app/package-lock.json: Generated file
Comments suppressed due to low confidence (1)

api_app/db/repositories/resources.py:194

  • Creating a new ResourceRepository instance inside patch_resource is unnecessary overhead and makes testing/mocking harder. Since this code is already inside ResourceRepository, you can fetch the parent service via self.get_resource_by_id(...) directly.
            parent_service_name = None
            if resource.resourceType == ResourceType.UserResource:
                parent_service_name = getattr(resource_template, "parentWorkspaceService", None)
                if not parent_service_name and hasattr(resource, "parentWorkspaceServiceId") and resource.parentWorkspaceServiceId:
                    resource_repo = await ResourceRepository.create()
                    parent_service = await resource_repo.get_resource_by_id(resource.parentWorkspaceServiceId)
                    parent_service_name = parent_service.templateName

Comment thread e2e_tests/test_resource_upgrade.py Outdated
Comment thread ui/app/src/utils/schemaUpgradeUtils.ts
Comment thread ui/app/src/components/shared/ConfirmUpgradeResource.tsx
Copilot AI review requested due to automatic review settings July 24, 2026 13:34
James Chapman added 2 commits July 24, 2026 13:36
…rvice template to avoid unauthorized GETs against admin-only template endpoints.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • ui/app/package-lock.json: Generated file
Comments suppressed due to low confidence (2)

api_app/db/repositories/resources.py:194

  • Creating a new ResourceRepository inside patch_resource adds an unnecessary extra repository/container initialization for a call that can be served by the current instance. This adds avoidable latency and resource usage during upgrades.

You can call get_resource_by_id on self instead of creating a new repository instance.

                if not parent_service_name and hasattr(resource, "parentWorkspaceServiceId") and resource.parentWorkspaceServiceId:
                    resource_repo = await ResourceRepository.create()
                    parent_service = await resource_repo.get_resource_by_id(resource.parentWorkspaceServiceId)
                    parent_service_name = parent_service.templateName

api_app/db/repositories/resources.py:294

  • For UserResource upgrades, patch_resource has fallback logic to derive parent_service_name from resource.parentWorkspaceServiceId when the template doesn't carry parentWorkspaceService, but validate_patch only reads resource_template.parentWorkspaceService. If that attribute is missing, validate_patch may fetch the wrong target template (or fail) and validate against a different schema than patch_resource used earlier.

Consider passing the already-resolved parent_service_name from patch_resource into validate_patch (or adding equivalent fallback logic in validate_patch) so both code paths validate against the same target template.

        if resource_patch.templateVersion is not None:
            # fetch the template for the target version
            parent_service_name = None
            if resource_template.resourceType == ResourceType.UserResource:
                parent_service_name = getattr(resource_template, "parentWorkspaceService", None)

Copilot AI review requested due to automatic review settings July 24, 2026 13:41

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • ui/app/package-lock.json: Generated file
Comments suppressed due to low confidence (1)

api_app/db/repositories/resources.py:194

  • patch_resource() creates a new ResourceRepository instance just to read the parent workspace service when upgrading a UserResource. Since this code already runs inside ResourceRepository, it can call self.get_resource_by_id(...) directly. Avoiding the extra repository creation reduces overhead and makes the flow easier to follow.
            parent_service_name = None
            if resource.resourceType == ResourceType.UserResource:
                parent_service_name = getattr(resource_template, "parentWorkspaceService", None)
                if not parent_service_name and hasattr(resource, "parentWorkspaceServiceId") and resource.parentWorkspaceServiceId:
                    resource_repo = await ResourceRepository.create()
                    parent_service = await resource_repo.get_resource_by_id(resource.parentWorkspaceServiceId)
                    parent_service_name = parent_service.templateName

Comment thread api_app/db/repositories/resources.py
Copilot AI review requested due to automatic review settings July 24, 2026 14:36

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • ui/app/package-lock.json: Generated file
Comments suppressed due to low confidence (1)

ui/app/src/components/shared/ConfirmUpgradeResource.tsx:158

  • When parent workspace service info is missing for a user resource, the code sets apiError but leaves requestLoadingState as Ok. This causes the dialog to render the normal upgrade UI and the error simultaneously. Set requestLoadingState to Error before returning so only the error state is shown.
        const err = new APIError();
        err.userMessage = "Parent workspace service information is missing for this user resource.";
        err.status = 400;
        setApiError(err);
        return;

Comment thread ui/app/src/components/shared/ConfirmUpgradeResource.tsx
Comment thread ui/app/src/components/shared/ConfirmUpgradeResource.tsx Outdated
…sitory and improve clarity in patch validation tests. Update ConfirmUpgradeResource component to streamline error handling and remove redundant workspace ID assignment.
Copilot AI review requested due to automatic review settings July 24, 2026 14:49

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • ui/app/package-lock.json: Generated file
Comments suppressed due to low confidence (1)

ui/app/src/components/shared/ConfirmUpgradeResource.tsx:129

  • For UserResource upgrades, parentWorkspaceService can be passed as an empty object (e.g. initial state in ResourceContextMenu before the parent service GET completes). Because {} is truthy, the current logic treats it as present and never falls back to parentWorkspaceServiceId, causing a false "Parent workspace service information is missing" error.
        // Prefer explicit prop when provided
        let parentService: WorkspaceService | undefined = props.parentWorkspaceService;

        // Otherwise, try to read any embedded parentWorkspaceService in the resource properties
        if (!parentService && props.resource.properties?.parentWorkspaceService) {
          parentService = props.resource.properties.parentWorkspaceService as WorkspaceService;
        }

Comment thread api_app/db/repositories/resources.py
Comment thread e2e_tests/test_resource_upgrade.py Outdated
Copilot AI review requested due to automatic review settings July 24, 2026 14:59

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • ui/app/package-lock.json: Generated file
Comments suppressed due to low confidence (4)

ui/app/src/components/shared/ConfirmUpgradeResource.tsx:361

  • finalSchema is built from only the new/invalid properties plus selected allOf blocks. For conditionally introduced properties (defined under allOf.then/else) whose visibility/required-ness depends on an existing field that is not part of allNewProperties, the controlling field value is never included in newPropertyValues (initialValues only copies top-level keys for new props). With omitExtraData/liveOmit, this can prevent the if condition from ever evaluating to true, meaning the conditional new field may never render and the upgrade can proceed without prompting for required values (or get stuck disabled).

This needs additional handling to ensure condition dependencies (e.g., keys referenced in allOf[].if.properties) are present in formData/schema (likely hidden/readOnly) when rendering conditional new properties.

  // Extract any conditional blocks from full schema, filtered by all new properties
  const conditionalBlocks = newTemplateSchema ? extractConditionalBlocks(newTemplateSchema, allNewProperties) : {};

  // Compose final schema combining reduced properties with conditional blocks
  const finalSchema = reducedSchemaProperties ? { ...reducedSchemaProperties, ...conditionalBlocks } : null;

ui/app/src/components/shared/ConfirmUpgradeResource.tsx:147

  • In the early-return error paths inside the UserResource case, apiError is set but requestLoadingState is never set to LoadingState.Error. Since ExceptionLayout is only rendered when requestLoadingState === LoadingState.Error (later in the component), these errors won't be surfaced to the user and the dialog can remain in an inconsistent state.
            const err = new APIError();
            err.userMessage =
              "Cannot resolve parent workspace service for this user resource because workspace context is missing.";
            err.status = 400;
            setApiError(err);

ui/app/src/components/shared/ConfirmUpgradeResource.tsx:157

  • This error path sets apiError but does not set requestLoadingState to LoadingState.Error, so ExceptionLayout will not render and the user won't see why the schema couldn't be loaded.
        const err = new APIError();
        err.userMessage = "Parent workspace service information is missing for this user resource.";
        err.status = 400;
        setApiError(err);
        return;

ui/app/src/components/shared/ConfirmUpgradeResource.tsx:165

  • For unsupported resource types, apiError is set but requestLoadingState remains Ok, so ExceptionLayout never shows and the user gets no feedback.
        // Report unsupported resource type to UI rather than throwing
        const err = new APIError();
        err.userMessage = `Unsupported resource type: ${props.resource.resourceType}`;
        err.status = 400;
        setApiError(err);
        return;

Comment thread ui/app/src/utils/schemaUpgradeUtils.ts
Copilot AI review requested due to automatic review settings July 24, 2026 16:05

Copilot AI left a comment

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@JC-wk
JC-wk requested a review from Copilot July 24, 2026 16:11

Copilot AI left a comment

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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.

When upgrading a template that has a new property the user should be prompted to enter it or the defaults used (if provided)

4 participants