Skip to content

fix: Reference Variable setting and getting in the UI #540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

zacharyblasczyk
Copy link
Member

@zacharyblasczyk zacharyblasczyk commented May 2, 2025

Summary by CodeRabbit

  • New Features

    • Added support for reference variables with optional default values and a dedicated form field in the UI.
    • Resource variables are now clearly organized and displayed in three separate sections: Direct Variables, Reference Variables, and Deployment Variables.
    • Reference variables now show source resource links, path, and resolved values with fallback to default if unavailable.
  • Improvements

    • Enhanced handling and validation of resource variables, ensuring only valid and relevant data is displayed.
    • Improved empty state messaging for all variable categories.
    • Prevented key collisions when adding new variables.
    • Refined variable data processing by separating direct and reference variables and validating each before display.
  • Bug Fixes

    • Corrected value type handling and explicit field passing for variable creation, ensuring accurate variable categorization and data integrity.

Copy link
Contributor

coderabbitai bot commented May 2, 2025

Walkthrough

The changes introduce clear separation between direct and reference resource variables across the codebase. They add explicit handling and schema validation for both types, update API mutations, refactor UI components to display and manage them distinctly, and extend schema support for null as a valid default value in reference variables. Control flow and data structures are updated accordingly.

Changes

File(s) Change Summary
apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/CreateResourceVariableDialog.tsx
apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/DirectVariableForm.tsx
Mutation calls for creating direct variables now explicitly include a fixed valueType: "direct" property, with the mutation input fields made explicit.
apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/ReferenceVariableForm.tsx Reference variable form now includes an optional defaultValue (string, number, boolean, or undefined), uses a new createReference mutation, and adds a UI field for defaultValue. Mutation input is explicit and includes valueType: "reference".
apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/ResourceDrawer.tsx Adds a getDirectVariables helper to filter and process only direct variables, which are now validated and mapped before being passed to the VariableContent component.
apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/VariablesContent.tsx Updates the prop type for resourceVariables in VariableContent to expect only DirectResourceVariable[] instead of all resource variables.
apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/page.tsx Refactors variable handling and UI: splits variables into direct and reference arrays, adds separate sections for each, handles empty states, and updates dropdowns and key management.
packages/api/src/router/resource-variables.ts Refactors the create mutation to handle only direct variables, adds a new createReference mutation for reference variables, and ensures correct field assignments and authorization.
packages/api/src/router/resources.ts Refactors the byId query to process and return directVariables and referenceVariables separately, with explicit resolution logic for reference variables and schema validation.
packages/db/src/schema/resource.ts Extends the allowed types for defaultValue in referenceVariableSchema to include null in addition to string, number, or boolean.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant API
    participant DB

    User->>UI: Submit Direct Variable Form
    UI->>API: create({ key, value, sensitive, resourceId, valueType: "direct" })
    API->>DB: Insert direct variable (reference/path null)
    DB-->>API: Variable record
    API-->>UI: Return created variable

    User->>UI: Submit Reference Variable Form
    UI->>API: createReference({ key, reference, path, defaultValue, sensitive, resourceId, valueType: "reference" })
    API->>DB: Insert reference variable (value null, with defaultValue)
    DB-->>API: Variable record
    API-->>UI: Return created reference variable
Loading
sequenceDiagram
    participant UI
    participant API
    participant DB

    UI->>API: Fetch Resource by ID
    API->>DB: Query resource, variables, relationships, metadata
    DB-->>API: Resource data
    API->>API: Validate and split variables (direct/reference)
    API->>API: Resolve reference variables (use defaultValue if unresolved)
    API-->>UI: Return resource with directVariables and referenceVariables arrays
Loading

Poem

🐇
Two types of variables, now clear and bright,
Direct and reference, each in their own light.
With schemas extended and forms made precise,
The code now sorts them, both clever and nice.
Hopping through changes, the rabbit approves—
Clean paths for the data, in organized grooves!
🌱

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa8f5ab and 1fb5793.

📒 Files selected for processing (1)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/ResourceDrawer.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/ResourceDrawer.tsx
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@zacharyblasczyk zacharyblasczyk requested a review from jsbroks May 2, 2025 15:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (4)
apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/ReferenceVariableForm.tsx (1)

67-72: Initial defaultValue should be undefined, not empty string

Using an empty string sentinel later (defaultValue === "" ? undefined) is brittle and will briefly violate the schema (which forbids empty strings). Keeping the form state as undefined avoids the workaround altogether.

-      defaultValue: undefined,
+      defaultValue: undefined, // keep it truly undefined from the start
apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/ResourceDrawer.tsx (1)

36-46: Utility could be strongly typed & side-effect free

vars: any[] discards compile-time safety. Accept ResourceVariable[] (or whatever the actual union type is) to surface future breakage during refactors.

-const getDirectVariables = (vars: any[]): DirectResourceVariable[] =>
+const getDirectVariables = (
+  vars: Array<DirectResourceVariable | ReferenceResourceVariable>,
+): DirectResourceVariable[] =>

Also consider leaving objects as objects and letting the renderer stringify; serialising here permanently loses type information.

packages/api/src/router/resources.ts (1)

417-452: Comprehensive reference variable resolution logic.

The implementation properly handles all edge cases for reference variables:

  1. Validates relationship existence
  2. Checks for target resource existence
  3. Handles empty paths by returning the entire target resource
  4. Extracts values from metadata or falls back to the provided default value

Consider adding error logging when relationships or target resources aren't found, to help debug issues with reference variables:

 if (!relationshipInfo)
+  console.debug(`Reference variable ${v.key}: relationship ${v.reference} not found`);
   return { ...v, resolvedValue: v.defaultValue };

 if (!targetResource)
+  console.debug(`Reference variable ${v.key}: target resource for ${relationshipInfo.target.id} not found`);
   return { ...v, resolvedValue: v.defaultValue };
packages/api/src/router/resource-variables.ts (1)

52-86: New mutation for reference variables.

The createReference mutation is well-implemented with:

  1. Proper input validation requiring reference-specific fields
  2. Setting the value to null as expected for reference variables
  3. Handling the optional defaultValue correctly
  4. Same authorization checks as the direct variable mutation
  5. Enqueueing the created variable for background processing

Consider refactoring the common post-creation logic (parsing and enqueueing) into a shared helper function to avoid duplication across the create mutations:

// Add this helper function at the top of the file
const processCreatedVariable = async (variable: unknown) => {
  const parsedVariable = schema.resourceVariableSchema.parse(variable);
  await updateResourceVariableQueue.add(parsedVariable.id, parsedVariable);
  return parsedVariable;
};

// Then use it in both mutations
const variable = await ctx.db
  .insert(schema.resourceVariable)
  .values(data)
  .returning()
  .then(takeFirst);

return processCreatedVariable(variable);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b52bb66 and afbd52f.

📒 Files selected for processing (10)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/CreateResourceVariableDialog.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/ResourceDrawer.tsx (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/VariablesContent.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/DirectVariableForm.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/ReferenceVariableForm.tsx (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/page.tsx (4 hunks)
  • apps/webservice/src/app/api/v1/resource-relationship-rules/route.ts (1 hunks)
  • packages/api/src/router/resource-variables.ts (1 hunks)
  • packages/api/src/router/resources.ts (2 hunks)
  • packages/db/src/schema/resource.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{ts,tsx}`: **Note on Error Handling:** Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error...

**/*.{ts,tsx}: Note on Error Handling:
Avoid strict enforcement of try/catch blocks. Code may use early returns, Promise chains (.then().catch()), or other patterns for error handling. These are acceptable as long as they maintain clarity and predictability.

  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/VariablesContent.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/CreateResourceVariableDialog.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/ResourceDrawer.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/DirectVariableForm.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/ReferenceVariableForm.tsx
  • packages/db/src/schema/resource.ts
  • packages/api/src/router/resources.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/page.tsx
  • apps/webservice/src/app/api/v1/resource-relationship-rules/route.ts
  • packages/api/src/router/resource-variables.ts
🧬 Code Graph Analysis (5)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/VariablesContent.tsx (1)
packages/db/src/schema/resource.ts (1)
  • DirectResourceVariable (513-513)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/ResourceDrawer.tsx (1)
packages/db/src/schema/resource.ts (2)
  • DirectResourceVariable (513-513)
  • resource (59-87)
apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/ReferenceVariableForm.tsx (1)
packages/db/src/schema/resource.ts (1)
  • createResourceVariable (433-458)
packages/api/src/router/resources.ts (1)
packages/db/src/schema/resource.ts (2)
  • DirectResourceVariable (513-513)
  • ReferenceResourceVariable (514-514)
packages/api/src/router/resource-variables.ts (2)
packages/api/src/trpc.ts (1)
  • protectedProcedure (173-173)
packages/db/src/common.ts (1)
  • takeFirst (9-13)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (16)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/CreateResourceVariableDialog.tsx (1)

68-68: Added proper valueType to resource variable creation

This change explicitly specifies that the created variable is a "direct" type, which aligns with the broader refactoring that distinguishes between direct and reference resource variables in the system.

apps/webservice/src/app/api/v1/resource-relationship-rules/route.ts (1)

35-35: Adjusted permission level from SystemUpdate to WorkspaceUpdate

This change reduces the required permission level from a system-wide update permission to a workspace-specific update permission, making the API more accessible to workspace administrators who don't need system-wide privileges.

apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/VariablesContent.tsx (1)

121-121: Updated resourceVariables prop type for better type safety

The type change from ResourceVariable[] to DirectResourceVariable[] ensures this component explicitly works with direct variables only, aligning with the broader refactoring to separate direct and reference variable handling throughout the application.

apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/DirectVariableForm.tsx (1)

69-75: Improved mutation payload construction

Changed from spreading the entire form data to explicitly specifying each field in the mutation payload, and added the necessary valueType: "direct" parameter. This makes the API contract more explicit and aligns with the type separation refactoring.

apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/ReferenceVariableForm.tsx (1)

60-61: Mutation renamed – check optimistic updates / caches

Swapping to createReference is correct, but the surrounding cache-invalidation still targets utils.resource.byId. If other screens rely on referenceVariables lists, verify those query keys as well to avoid stale UI after creation.

apps/webservice/src/app/[workspaceSlug]/(app)/_components/resources/drawer/ResourceDrawer.tsx (1)

229-230: Potentially empty array – guard against undefined

resource.variables is nullable while the query is loading; getDirectVariables receives undefined during the first render.
Either default to [] or add an enabled: !!resourceId check before mapping.

apps/webservice/src/app/[workspaceSlug]/(app)/resources/(raw)/[resourceId]/variables/page.tsx (1)

31-35: rules may be undefined on older resources

const { directVariables, referenceVariables, rules } = resource;
If rules was introduced in this PR, dereferencing on a pre-migration record will throw. Guard with ?. or default to [].

-const references = rules.map((r) => r.reference);
+const references = (rules ?? []).map((r) => r.reference);
packages/api/src/router/resources.ts (4)

397-397: Improved variable handling with target resolution.

The code now correctly handles fetching relationship targets with their metadata, which is essential for the new reference variable resolution.


399-412: Keep existing variable parsing logic.

This parsing logic correctly handles malformed variables by attempting to parse them through the schema validation and logging errors for any that fail.


413-415: Good separation of direct variables.

The code appropriately filters direct variables using type guards to ensure type safety. This separation is crucial for the new distinction between variable types.


460-462: Return separated variable arrays.

This change properly structures the API response to include both direct and reference variables as separate arrays, maintaining a clear distinction between the two types.

packages/api/src/router/resource-variables.ts (5)

14-15: Clear comment for direct variables section.

Good documentation to indicate that this mutation is specifically for direct variables.


16-24: Properly constrained input schema for direct variables.

The input schema now correctly includes the valueType: "direct" literal type, ensuring that this mutation can only be used for direct variables.


32-35: Encryption handling for sensitive values.

The encryption logic correctly handles sensitive values, converting them to strings before encryption.


36-50: Explicit null handling for reference fields.

The code properly sets reference and path to null for direct variables, ensuring a clear distinction between variable types in the database.


71-76: Clean handling of null default values.

The code correctly handles the case where defaultValue might be null or undefined, converting it to undefined for the database insertion.

Comment on lines +503 to +505
defaultValue: z
.union([z.string(), z.number(), z.boolean(), z.null()])
.optional(),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

defaultValue union now inconsistent with earlier schemas

createResourceVariable (lines 433-441) still accepts z.record(z.any()) for defaultValue, but the new referenceVariableSchema omits that possibility and only allows primitives/null.
Persisting an object in default_value will therefore validate on insert/update but fail on subsequent fetch / validation against this discriminated union.

-  defaultValue: z
-    .union([z.string(), z.number(), z.boolean(), z.null()])
-    .optional(),
+  defaultValue: z
+    .union([
+      z.string(),
+      z.number(),
+      z.boolean(),
+      z.record(z.any()),
+      z.null(),
+    ])
+    .optional(),

Add the object arm (or, if objects are intentionally disallowed for references, tighten the earlier schemas instead) to keep the type-system and runtime validation in sync.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
defaultValue: z
.union([z.string(), z.number(), z.boolean(), z.null()])
.optional(),
defaultValue: z
.union([
z.string(),
z.number(),
z.boolean(),
z.record(z.any()),
z.null(),
])
.optional(),

Comment on lines +191 to +211
<FormField
control={form.control}
name="defaultValue"
render={({ field }) => (
<FormItem>
<FormLabel>Default Value (Optional)</FormLabel>
<FormControl>
<Input
{...field}
value={field.value === undefined ? "" : String(field.value)}
onChange={(e) => {
const value = e.target.value.trim();
field.onChange(value === "" ? undefined : value);
}}
placeholder="Fallback if reference is not available"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

String-only input silently drops numbers & booleans

defaultValue may be a number or boolean, yet the input always serialises to a string, causing every entry to arrive as "123" or "true".
Parse user input (e.g. JSON.parse) or provide a type selector so the backend receives correctly-typed data.

-                  const value = e.target.value.trim();
-                  field.onChange(value === "" ? undefined : value);
+                  const raw = e.target.value.trim();
+                  if (raw === "") {
+                    field.onChange(undefined);
+                  } else {
+                    // attempt JSON parse – falls back to string on error
+                    try {
+                      field.onChange(JSON.parse(raw));
+                    } catch {
+                      field.onChange(raw);
+                    }
+                  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<FormField
control={form.control}
name="defaultValue"
render={({ field }) => (
<FormItem>
<FormLabel>Default Value (Optional)</FormLabel>
<FormControl>
<Input
{...field}
value={field.value === undefined ? "" : String(field.value)}
onChange={(e) => {
const value = e.target.value.trim();
field.onChange(value === "" ? undefined : value);
}}
placeholder="Fallback if reference is not available"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="defaultValue"
render={({ field }) => (
<FormItem>
<FormLabel>Default Value (Optional)</FormLabel>
<FormControl>
<Input
{...field}
value={field.value === undefined ? "" : String(field.value)}
onChange={(e) => {
const raw = e.target.value.trim();
if (raw === "") {
field.onChange(undefined);
} else {
// attempt JSON parse – falls back to string on error
try {
field.onChange(JSON.parse(raw));
} catch {
field.onChange(raw);
}
}
}}
placeholder="Fallback if reference is not available"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

@zacharyblasczyk zacharyblasczyk merged commit d143d8c into ctrlplanedev:main May 8, 2025
9 of 10 checks passed
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.

2 participants