Skip to content

chore: clean up edit policy ui #546

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
merged 2 commits into from
May 4, 2025
Merged

Conversation

adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented May 4, 2025

Summary by CodeRabbit

  • New Features

    • Added support for rendering tag-based deployment conditions alongside version-based conditions in deployment policies.
    • Introduced a new context provider for unified policy form management, enabling consistent form handling across policy editing sections.
  • Refactor

    • Simplified and reorganized policy editing components to use shared form context, removing internal form state and mutation logic.
    • Updated tab navigation in policy editing to provide more granular and explicit tabs for configuration, time windows, deployment flow, and quality/security.
    • Streamlined layout and UI structures for policy editing pages, improving clarity and maintainability.
  • Style

    • Enhanced layout styling for policy pages with improved scrollbar appearance and scrolling behavior.
  • Chores

    • Added new URL builder for approval gates within policy editing for consistent navigation.

Copy link
Contributor

coderabbitai bot commented May 4, 2025

Walkthrough

This update refactors several policy editing components to centralize form state management into a new PolicyFormContextProvider, removing local form state and mutation logic from individual components. It modifies components handling deployment version/tag conditions to support tag conditions alongside version conditions. The tab navigation in the policy editing interface is restructured into distinct tabs for configuration, time windows, deployment flow, and quality/security. URL utilities are extended to support approval gates. Layout components are simplified by removing navigation UI and wrapping children with the new form context. Several page components are simplified to synchronous renderers without data fetching, relying on the shared form context for state.

Changes

Files Change Summary
.../deployments/version/condition/DeploymentVersionConditionRender.tsx Added support for rendering isTagCondition alongside isVersionCondition; renders DeploymentVersionTagConditionRender.
.../deployments/version/condition/DeploymentVersionTagConditionRender.tsx Expanded condition prop type to accept `VersionCondition
.../policies/[policyId]/_components/PolicyTabs.tsx Replaced single generic "edit" tab with multiple specific tabs (configuration, time-windows, deployment-flow, quality-security).
.../policies/[policyId]/edit/_components/PolicyFormContext.tsx Added PolicyFormContextProvider, PolicyFormContext, and usePolicyFormContext hook to centralize form state and submission.
.../policies/[policyId]/edit/configuration/EditConfiguration.tsx Removed local form and mutation logic; now uses external form context; UI reorganized and simplified; removed policy prop.
.../policies/[policyId]/edit/configuration/page.tsx Changed from async data-fetching page to synchronous component rendering EditConfiguration without props.
.../policies/[policyId]/edit/deployment-flow/EditDeploymentFlow.tsx Removed local form and mutation logic; now uses external form context; simplified UI and removed policy prop.
.../policies/[policyId]/edit/deployment-flow/page.tsx Changed from async data-fetching page to synchronous component rendering EditDeploymentFlow without props.
.../policies/[policyId]/edit/layout.tsx Removed breadcrumb, header, and tab UI; now only wraps children with PolicyFormContextProvider.
.../policies/[policyId]/edit/quality-security/EditQualitySecurity.tsx Removed local form and mutation logic; now uses external form context; simplified UI; removed policy prop; updated prop types.
.../policies/[policyId]/edit/quality-security/page.tsx Removed policyId param; simplified data fetching to only load workspace; renders EditQualitySecurity with workspaceId only.
.../policies/[policyId]/edit/time-windows/EditTimeWindow.tsx Removed local form and mutation logic; now uses external form context; simplified UI layout; no signature changes.
.../policies/[policyId]/layout.tsx Added custom scrollbar styling and scrolling behavior to container div wrapping main content.
apps/webservice/src/app/urls.ts Added approvalGates URL builder method to workspacePolicyEdit for approval gates editing path.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PolicyFormContextProvider
    participant EditComponent
    participant API

    User->>EditComponent: Interact with form fields
    EditComponent->>PolicyFormContextProvider: Update form state
    User->>EditComponent: Submit form
    EditComponent->>PolicyFormContextProvider: Trigger submit handler
    PolicyFormContextProvider->>API: Validate and send update mutation
    API-->>PolicyFormContextProvider: Return success or error
    PolicyFormContextProvider-->>EditComponent: Update form state and UI accordingly
Loading

Possibly related PRs

Poem

🐇
In code fields where conditions grow,
Tags and versions now both show.
Forms once scattered, now unite,
Context holds them tight and right.
Tabs split paths with clearer sight,
Policies dance in shared light!
Hop along, the updates glow! 🌸✨

✨ 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.

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: 7

🔭 Outside diff range comments (3)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/deployment-flow/EditDeploymentFlow.tsx (1)

178-186: 🛠️ Refactor suggestion

Guard query with a meaningful key instead of empty string

Passing "" as a placeholder workspace id makes the query key ambiguous and may pollute the cache:

-const deploymentsQ = api.deployment.byWorkspaceId.useQuery(
-  workspace?.id ?? "",
-  { enabled: workspace != null },
-);
+const deploymentsQ = api.deployment.byWorkspaceId.useQuery(
+  workspace?.id as string,
+  { enabled: !!workspace },
+);

Using as string (or a non-nullable assertion) together with enabled keeps the key stable and avoids accidental sharing between workspaces.

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/quality-security/EditQualitySecurity.tsx (2)

95-101: 🛠️ Refactor suggestion

Tighten typing of fields prop

fields is currently typed as Record<"id", string>[], which loses the rest of the information React-Hook-Form attaches (e.g. field values, internal meta). This weakens type-safety and forces downstream any casts.

-interface UserSectionProps {
-  control: Control<SCHEMA.UpdatePolicy>;
-  fields: Record<"id", string>[];
+import type { FieldArrayWithId } from "react-hook-form";
+
+interface UserSectionProps {
+  control: Control<SCHEMA.UpdatePolicy>;
+  fields: FieldArrayWithId<SCHEMA.UpdatePolicy, "versionUserApprovals", "id">[];

Apply the same change to RoleSectionProps.


206-214: ⚠️ Potential issue

parseInt may yield NaN; add safe-guard

If the input is cleared, parseInt("") returns NaN, which violates the schema and can make the form value non-serialisable. A small guard keeps the value valid:

- onChange={(e) => field.onChange(parseInt(e.target.value))}
+ onChange={(e) => {
+   const num = parseInt(e.target.value, 10);
+   field.onChange(Number.isNaN(num) ? 1 : num);
+ }}

Apply the same pattern for the general-approval count field below.

Also applies to: 313-321

🧹 Nitpick comments (13)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployments/version/condition/DeploymentVersionTagConditionRender.tsx (2)

12-12: Extended component to support both VersionCondition and TagCondition types.

Broadened the generic type parameter to handle both condition types, which aligns with the updated conditional logic in DeploymentVersionConditionRender.tsx.

However, the component name "DeploymentVersionTagConditionRender" might be slightly misleading since it now handles both version and tag conditions. Consider renaming it to better reflect its dual purpose in a future refactoring.


25-25: Consider dynamic title based on condition type.

The title is hardcoded as "Tag" even when the component may be rendering a version condition. For better clarity, consider making this dynamic:

-      title="Tag"
+      title={condition.type === "tag" ? "Tag" : "Version"}
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/_components/PolicyTabs.tsx (1)

30-37: Reduce repetition with a mapping table.

Both getInitialTab and onTabChange repeat the same if cascade, which is error-prone. A map improves readability and maintains a single source of truth.

 const routes = {
   overview: baseUrl,
   configuration: configurationUrl,
   "time-windows": timeWindowsUrl,
   "deployment-flow": deploymentFlowUrl,
   "quality-security": qualitySecurityUrl,
 };

 const getInitialTab = () =>
   Object.entries(routes).find(([, url]) => url === pathname)?.[0] ??
   "overview";

 ...

 const onTabChange = (value: keyof typeof routes) => {
   router.push(routes[value]);
   setActiveTab(value);
 };

Also applies to: 45-51

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/time-windows/EditTimeWindow.tsx (1)

45-54: Duplicate policy fetch may be unnecessary.

The parent PolicyFormContextProvider already provides the full policy; fetching it again (api.policy.byId.useQuery) duplicates network traffic and may lead to inconsistent data if the two sources diverge. Consider:

  1. Passing policy through context and reading policy.denyWindows directly, or
  2. Converting this query to enabled: false and refetching only when a manual “refresh” is requested.

Reducing duplicate requests will improve performance on slow networks.

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/deployment-flow/EditDeploymentFlow.tsx (2)

62-67: Avoid repeated .find() calls inside the button label

deployments.find(...) is executed up to three times on every render.
For large deployment lists this becomes unnecessarily expensive.

-? deployments.find((d) => d.id === selectedId)
-  ? `${deployments.find((d) => d.id === selectedId)?.system.name} / ${deployments.find((d) => d.id === selectedId)?.name}`
+const selected = deployments.find((d) => d.id === selectedId);
+selected ? `${selected.system.name} / ${selected.name}`

By computing selected once you gain O(n) → O(1) performance and improve readability.


254-260: Multiple “Save” buttons within the same form can cause UX confusion

All edit sub-pages render their own <Button type="submit">. When two tabs are visible concurrently (e.g. in split-screen) users may not realise the buttons are identical. Consider:

• Rendering a single save action in a shared toolbar/layout, or
• Giving contextual labels (“Save Deployment Flow”, “Save Targets”, …).

This also prevents accidental double-submissions triggered by Enter key presses in nested sections.

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx (2)

48-56: Remove stray console.log statements

Debug logs left in production code clutter the console and may leak PII.

-  console.log(policy);
 ...
-  console.log(form.getValues());

Please delete or wrap them behind a dev-only flag.


60-61: Mutation hook is created on every render

const updatePolicy = api.policy.update.useMutation();

Because the provider re-renders whenever children change, the mutation instance is recreated.
Wrapping it in useMemo (or moving it outside the component if possible) avoids resetting internal mutation state.

-const updatePolicy = api.policy.update.useMutation();
+const updatePolicy = useMemo(() => api.policy.update.useMutation(), []);
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/configuration/EditConfiguration.tsx (3)

72-76: Debug statement should be removed

console.log(form.getValues()); leaks form data and clutters the console.


217-238: Inefficient detection of selected target scope

TARGET_SCOPE_OPTIONS.find is executed on every render for every target row.
Caching the matched option or storing an explicit targetScope field (enum) in the form state would be more performant and simpler to reason about.


360-369: Removing a target without confirmation can lead to data loss

A single click instantly deletes the target. Consider adding:

• A confirmation dialog, or
• An undo snackbar.

This aligns with common UX patterns and prevents accidental loss.

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/quality-security/EditQualitySecurity.tsx (2)

38-42: Handle loading / error states for members query

api.workspace.members.list.useQuery can return isLoading / error. While the component gracefully handles undefined data, the combobox is still rendered and may appear empty while loading or permanently blank on network failure.
Consider disabling the trigger or showing a spinner / error message until data is available so the UX is clearer.


360-374: Remove stale commented-out code

The appendRole button & handler are commented out but the appendRole hook is no longer declared. Keeping dead code around adds noise and confuses future maintainers.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6a9f979 and 22eaf74.

📒 Files selected for processing (13)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployments/version/condition/DeploymentVersionConditionRender.tsx (2 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployments/version/condition/DeploymentVersionTagConditionRender.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/_components/PolicyTabs.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/configuration/EditConfiguration.tsx (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/configuration/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/deployment-flow/EditDeploymentFlow.tsx (4 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/layout.tsx (2 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/quality-security/EditQualitySecurity.tsx (6 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/quality-security/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/time-windows/EditTimeWindow.tsx (2 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/layout.tsx (1 hunks)
  • apps/webservice/src/app/urls.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/deployments/version/condition/DeploymentVersionConditionRender.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/layout.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/_components/PolicyTabs.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployments/version/condition/DeploymentVersionTagConditionRender.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/quality-security/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/configuration/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/deployment-flow/EditDeploymentFlow.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/layout.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/configuration/EditConfiguration.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/time-windows/EditTimeWindow.tsx
  • apps/webservice/src/app/urls.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/quality-security/EditQualitySecurity.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx
🧬 Code Graph Analysis (5)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployments/version/condition/DeploymentVersionConditionRender.tsx (1)
packages/validators/src/releases/conditions/release-condition.ts (2)
  • isVersionCondition (91-94)
  • isTagCondition (101-104)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployments/version/condition/DeploymentVersionTagConditionRender.tsx (2)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployments/version/condition/deployment-version-condition-props.ts (1)
  • DeploymentVersionConditionRenderProps (3-11)
packages/validators/src/releases/conditions/tag-condition.ts (1)
  • TagCondition (11-11)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/quality-security/page.tsx (2)
packages/db/src/schema/workspace.ts (1)
  • workspace (18-27)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/quality-security/EditQualitySecurity.tsx (1)
  • EditQualitySecurity (234-388)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/layout.tsx (2)
packages/db/src/schema/policy.ts (1)
  • policy (42-62)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx (1)
  • PolicyFormContextProvider (44-103)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/_components/PolicyFormContext.tsx (6)
packages/db/src/schema/policy.ts (5)
  • PolicyTarget (186-186)
  • PolicyDeploymentVersionSelector (187-189)
  • UpdatePolicy (176-176)
  • policy (42-62)
  • updatePolicy (154-175)
packages/db/src/schema/rules/deny-window.ts (1)
  • PolicyRuleDenyWindow (142-144)
packages/db/src/schema/rules/approval-any.ts (1)
  • PolicyRuleAnyApproval (77-79)
packages/db/src/schema/rules/approval-user.ts (1)
  • PolicyRuleUserApproval (82-84)
packages/db/src/schema/rules/approval-role.ts (1)
  • PolicyRuleRoleApproval (88-90)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/_utils/policy-targets.ts (2)
  • convertEmptySelectorsToNull (24-49)
  • isValidTarget (77-101)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
🔇 Additional comments (9)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployments/version/condition/DeploymentVersionConditionRender.tsx (2)

8-8: Added support for tag conditions.

Added import for the isTagCondition validator function to enable tag condition type checking alongside version conditions.


58-58: Logic enhancement to handle both version and tag conditions.

Extending the conditional check to support both version and tag conditions is a good improvement that unifies the handling of similar condition types.

apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployments/version/condition/DeploymentVersionTagConditionRender.tsx (1)

5-5: Added TagCondition type import.

Added import for the TagCondition type to support the component's type extension.

apps/webservice/src/app/urls.ts (1)

59-59: Added URL builder function for approval gates appropriately.

The addition of the approvalGates() URL builder method to the workspacePolicyEdit function is consistent with the existing URL pattern in the codebase. This enhances the API by making the URL structure for approval gates accessible at the policy edit level.

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/layout.tsx (1)

64-64: Improved container with proper scrolling behavior.

The enhanced container div now includes custom scrollbar styling with explicit height calculation and overflow control. This improvement provides a better user experience by ensuring consistent scrolling behavior.

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/configuration/page.tsx (1)

3-4: Simplified page component correctly.

The ConfigurationPage component has been appropriately simplified to a synchronous function that renders the EditConfiguration component without props. This aligns with the broader refactoring effort to centralize form state management through context.

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/quality-security/page.tsx (1)

7-12: Simplified page component to follow context pattern.

The QualitySecurityPage has been refactored to only fetch workspace data and no longer handle policy data directly. This change properly aligns with using the PolicyFormContext for form state management.

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/time-windows/EditTimeWindow.tsx (1)

49-53: Potentially destructive reset during form editing.

useEffect unconditionally calls form.reset({ …denyWindows }) when policy arrives or refreshes.
If the user has already modified other fields in the same form, those unsaved edits are silently preserved via ...currentValues, but local edits to denyWindows are overwritten. This can surprise users if the policy query refetches (e.g., window focus).

Add a guard:

if (!form.formState.isDirty) {
  form.reset({ ...currentValues, denyWindows: policy.denyWindows });
}

or debounce refetches.

apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/deployment-flow/EditDeploymentFlow.tsx (1)

169-176: useParams generic omits policyId – risk of silent undefined

useParams<{ workspaceSlug: string }>() ignores policyId, yet this page is nested under .../policies/[policyId]/edit/....
Accessing policyId elsewhere would yield undefined, leading to hard-to-trace bugs.

-const { workspaceSlug } = useParams<{ workspaceSlug: string }>();
+const { workspaceSlug, policyId } =
+  useParams<{ workspaceSlug: string; policyId: string }>();

Even if policyId is not currently used, declaring it prevents accidental omissions later.

Comment on lines 14 to 16
const { params, children } = props;
const { workspaceSlug, policyId } = await params;
const { policyId } = await params;

Copy link
Contributor

@coderabbitai coderabbitai bot May 4, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider accepting params synchronously instead of as a Promise.

nextjs’s Layout/Page server components receive params synchronously ({ params: { … } }).
Treating it as a Promise adds an unnecessary await, complicates the type signature, and may confuse future maintainers.

-export default async function Layout(props: {
-  children: React.ReactNode;
-  params: Promise<{
-    workspaceSlug: string;
-    policyId: string;
-  }>;
-}) {
-  const { params, children } = props;
-  const { policyId } = await params;
+export default async function Layout({
+  children,
+  params,
+}: {
+  children: React.ReactNode;
+  params: {
+    workspaceSlug: string;
+    policyId: string;
+  };
+}) {
+  const { policyId } = params;

This also removes the accidental shadowing of the local params variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is not true as of the latest next js update

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 You're absolutely right! I apologize for the incorrect suggestion. In Next.js 15, dynamic route parameters (params) do return Promises in async contexts, so the current implementation awaiting params is correct:

const { policyId } = await params;

Thank you for the correction. This aligns with our existing knowledge about Next.js 15's behavior where params should be awaited before accessing their properties in async contexts.


🧠 Learnings used
Learnt from: zacharyblasczyk
PR: ctrlplanedev/ctrlplane#382
File: apps/webservice/src/app/api/v1/deployments/[deploymentId]/route.ts:82-88
Timestamp: 2025-03-16T19:41:44.129Z
Learning: In Next.js 15, dynamic route parameters (params) return Promises instead of direct values in async contexts. When accessing properties like `params.deploymentId` in an async function, use `(await params).deploymentId` to avoid the "params should be awaited before using its properties" error. This applies to API routes, page components, and other async contexts.

If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment on lines 41 to 46
const [activeTab, setActiveTab] = useState(getInitialTab());

const router = useRouter();

const onTabChange = (value: string) => {
if (value === "overview") router.push(baseUrl);
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

Keep tab state in sync with external navigation.

activeTab is initialised only once, so a browser back/forward action that changes pathname will desynchronise the UI. Add an effect to update state whenever the URL changes.

-  const [activeTab, setActiveTab] = useState(getInitialTab());
+  const [activeTab, setActiveTab] = useState(() => getInitialTab());
+
+  // sync tab state when user navigates with browser controls
+  useEffect(() => {
+    setActiveTab(getInitialTab());
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [pathname]);

Remember to import useEffect.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +62 to +94
const onSubmit = form.handleSubmit((data) => {
const targets =
data.targets === undefined
? undefined
: data.targets.map(convertEmptySelectorsToNull);

const isTargetsValid =
targets === undefined || targets.every(isValidTarget);
if (!isTargetsValid) {
const errorStr = "One or more of your targets are invalid";
form.setError("targets", { message: errorStr });
toast.error("Error creating policy", { description: errorStr });
return;
}

return updatePolicy
.mutateAsync({
id: policy.id,
data: { ...data, targets },
})
.then(() => {
toast.success("Policy updated successfully");
form.reset(data);
router.refresh();
utils.policy.byId.invalidate();
utils.policy.list.invalidate();
})
.catch((error) => {
toast.error("Failed to update policy", {
description: error.message,
});
});
});
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

Prefer async/await and invalidate queries with parameters

  1. form.handleSubmit already supports async handlers – using await improves readability and ensures thrown errors are caught by the wrapper.
  2. utils.policy.byId.invalidate() without { id } may not hit the correct cache entry.
-const onSubmit = form.handleSubmit((data) => {
+const onSubmit = form.handleSubmit(async (data) => {
   ...
-  return updatePolicy
-    .mutateAsync({ id: policy.id, data: { ...data, targets } })
-    .then(() => {
+  try {
+    await updatePolicy.mutateAsync({ id: policy.id, data: { ...data, targets } });
     toast.success("Policy updated successfully");
     form.reset(data);
     router.refresh();
-    utils.policy.byId.invalidate();
+    utils.policy.byId.invalidate({ id: policy.id });
     utils.policy.list.invalidate();
-  })
-  .catch((error) => {
+  } catch (error: any) {
     toast.error("Failed to update policy", { description: error.message });
-  });
+  }
 });

This yields clearer flow control and guarantees the right cache key is invalidated.

📝 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
const onSubmit = form.handleSubmit((data) => {
const targets =
data.targets === undefined
? undefined
: data.targets.map(convertEmptySelectorsToNull);
const isTargetsValid =
targets === undefined || targets.every(isValidTarget);
if (!isTargetsValid) {
const errorStr = "One or more of your targets are invalid";
form.setError("targets", { message: errorStr });
toast.error("Error creating policy", { description: errorStr });
return;
}
return updatePolicy
.mutateAsync({
id: policy.id,
data: { ...data, targets },
})
.then(() => {
toast.success("Policy updated successfully");
form.reset(data);
router.refresh();
utils.policy.byId.invalidate();
utils.policy.list.invalidate();
})
.catch((error) => {
toast.error("Failed to update policy", {
description: error.message,
});
});
});
const onSubmit = form.handleSubmit(async (data) => {
const targets =
data.targets === undefined
? undefined
: data.targets.map(convertEmptySelectorsToNull);
const isTargetsValid =
targets === undefined || targets.every(isValidTarget);
if (!isTargetsValid) {
const errorStr = "One or more of your targets are invalid";
form.setError("targets", { message: errorStr });
toast.error("Error creating policy", { description: errorStr });
return;
}
try {
await updatePolicy.mutateAsync({
id: policy.id,
data: { ...data, targets },
});
toast.success("Policy updated successfully");
form.reset(data);
router.refresh();
utils.policy.byId.invalidate({ id: policy.id });
utils.policy.list.invalidate();
} catch (error: any) {
toast.error("Failed to update policy", {
description: error.message,
});
}
});

Comment on lines +158 to +161
const value = parseInt(e.target.value);
field.onChange(value);
}}
/>
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

Handle NaN when parsing priority

parseInt("") or non-numeric input yields NaN, which the schema probably rejects.

-const value = parseInt(e.target.value);
-field.onChange(value);
+const parsed = Number.isFinite(+e.target.value) ? parseInt(e.target.value, 10) : undefined;
+field.onChange(parsed);

Alternatively, rely on e.target.valueAsNumber which returns NaN for empty input and can be handled explicitly.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +349 to +357
<div className="flex gap-2">
<Button
type="button"
variant="outline"
size="sm"
className="flex-1"
onClick={() => appendUser({ userId: "" })}
>
<IconPlus className="mr-2 h-4 w-4" />
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

Default object passed to appendUser is incomplete

appendUser({ userId: "" }) omits required fields such as requiredApprovalsCount (and possibly the RHF-generated id). This will immediately put the form in an invalid state and can break validation.

- onClick={() => appendUser({ userId: "" })}
+ onClick={() =>
+   appendUser({
+     userId: "",
+     requiredApprovalsCount: 1,
+   })
+ }

Ensure the structure matches SCHEMA.UpdatePolicy["versionUserApprovals"][number].

📝 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
<div className="flex gap-2">
<Button
type="button"
variant="outline"
size="sm"
className="flex-1"
onClick={() => appendUser({ userId: "" })}
>
<IconPlus className="mr-2 h-4 w-4" />
<div className="flex gap-2">
<Button
type="button"
variant="outline"
size="sm"
className="flex-1"
- onClick={() => appendUser({ userId: "" })}
+ onClick={() =>
+ appendUser({
+ userId: "",
+ requiredApprovalsCount: 1,
+ })
+ }
>
<IconPlus className="mr-2 h-4 w-4" />

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: 0

♻️ Duplicate comments (1)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/_components/PolicyTabs.tsx (1)

39-39: Keep tab state in sync with external navigation.

activeTab is initialised only once, so a browser back/forward action that changes pathname will desynchronise the UI. Consider adding an effect to update state whenever the URL changes.

  const [activeTab, setActiveTab] = useState(getInitialTab());
+
+  // sync tab state when user navigates with browser controls
+  useEffect(() => {
+    setActiveTab(getInitialTab());
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [pathname]);

Note: Don't forget to import useEffect from "react".

🧹 Nitpick comments (4)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/_components/PolicyTabs.tsx (4)

30-37: Consider using switch statement for better readability and maintenance.

The multiple consecutive if statements could be replaced with a more structured switch statement to improve readability, especially as more tabs might be added in the future.

  const getInitialTab = () => {
-    if (pathname === baseUrl) return "overview";
-    if (pathname === configurationUrl) return "configuration";
-    if (pathname === timeWindowsUrl) return "time-windows";
-    if (pathname === deploymentFlowUrl) return "deployment-flow";
-    if (pathname === qualitySecurityUrl) return "quality-security";
-    return "overview";
+    switch (pathname) {
+      case baseUrl:
+        return "overview";
+      case configurationUrl:
+        return "configuration";
+      case timeWindowsUrl:
+        return "time-windows";
+      case deploymentFlowUrl:
+        return "deployment-flow";
+      case qualitySecurityUrl:
+        return "quality-security";
+      default:
+        return "overview";
+    }
  };

43-50: Consider using switch statement in the tab change handler for consistency.

Similar to the initial tab detection, using a switch statement here would improve readability and maintenance.

  const onTabChange = (value: string) => {
-    if (value === "overview") router.push(baseUrl);
-    if (value === "configuration") router.push(configurationUrl);
-    if (value === "time-windows") router.push(timeWindowsUrl);
-    if (value === "deployment-flow") router.push(deploymentFlowUrl);
-    if (value === "quality-security") router.push(qualitySecurityUrl);
+    switch (value) {
+      case "overview":
+        router.push(baseUrl);
+        break;
+      case "configuration":
+        router.push(configurationUrl);
+        break;
+      case "time-windows":
+        router.push(timeWindowsUrl);
+        break;
+      case "deployment-flow":
+        router.push(deploymentFlowUrl);
+        break;
+      case "quality-security":
+        router.push(qualitySecurityUrl);
+        break;
+    }
     setActiveTab(value);
  };

56-56: Consider updating tab label for clarity.

The tab labeled "Edit" has a value of "configuration", which might be confusing. Consider updating the label to "Configuration" to match its functionality.

-        <TabsTrigger value="configuration">Edit</TabsTrigger>
+        <TabsTrigger value="configuration">Configuration</TabsTrigger>

62-67: Label mismatch with value for deployment-flow tab.

The tab has a value of "deployment-flow" but is labeled "Version Conditions", which might cause confusion. Consider updating the label to match the value for better clarity and consistency.

        <TabsTrigger
          value="deployment-flow"
          className="flex items-center gap-1"
        >
          <IconTag className="h-4 w-4" />
-         Version Conditions
+         Deployment Flow
        </TabsTrigger>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 22eaf74 and c7889d6.

📒 Files selected for processing (3)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/_components/PolicyTabs.tsx (2 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/deployment-flow/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/layout.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/layout.tsx
🧰 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)/policies/[policyId]/edit/deployment-flow/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/_components/PolicyTabs.tsx
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (1)
apps/webservice/src/app/[workspaceSlug]/(app)/policies/[policyId]/edit/deployment-flow/page.tsx (1)

3-4: LGTM - Clean component refactoring.

The refactoring simplifies this component by leveraging the context-based state management pattern, which is a good practice for reducing prop drilling and centralizing form state.

@adityachoudhari26 adityachoudhari26 merged commit a17652e into main May 4, 2025
5 of 6 checks passed
@adityachoudhari26 adityachoudhari26 deleted the cleanup-edit-policy branch May 4, 2025 02:11
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