Allow Template upgrade properties to be set and remove properties that no longer exist.#4783
Allow Template upgrade properties to be set and remove properties that no longer exist.#4783JC-wk wants to merge 75 commits into
Conversation
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
Unit Test Results947 tests 947 ✅ 36s ⏱️ Results for commit 674c059. ♻️ This comment has been updated with latest results. |
fix: allow upgrades on hidden properties
…property visibility
There was a problem hiding this comment.
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
ResourceRepositoryinstance insidepatch_resourceis unnecessary overhead and makes testing/mocking harder. Since this code is already insideResourceRepository, you can fetch the parent service viaself.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
…rvice template to avoid unauthorized GETs against admin-only template endpoints.
There was a problem hiding this comment.
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_namefromresource.parentWorkspaceServiceIdwhen the template doesn't carryparentWorkspaceService, but validate_patch only readsresource_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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
apiErrorbut leavesrequestLoadingStateasOk. This causes the dialog to render the normal upgrade UI and the error simultaneously. SetrequestLoadingStatetoErrorbefore 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;
…sitory and improve clarity in patch validation tests. Update ConfirmUpgradeResource component to streamline error handling and remove redundant workspace ID assignment.
There was a problem hiding this comment.
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,
parentWorkspaceServicecan 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 toparentWorkspaceServiceId, 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;
}
There was a problem hiding this comment.
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
finalSchemais built from only the new/invalid properties plus selectedallOfblocks. For conditionally introduced properties (defined underallOf.then/else) whose visibility/required-ness depends on an existing field that is not part ofallNewProperties, the controlling field value is never included innewPropertyValues(initialValues only copies top-level keys for new props). WithomitExtraData/liveOmit, this can prevent theifcondition 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
UserResourcecase,apiErroris set butrequestLoadingStateis never set toLoadingState.Error. SinceExceptionLayoutis only rendered whenrequestLoadingState === 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
apiErrorbut does not setrequestLoadingStatetoLoadingState.Error, soExceptionLayoutwill 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,
apiErroris set butrequestLoadingStateremainsOk, soExceptionLayoutnever 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;
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