Skip to content
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

init view #395

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

init view #395

wants to merge 13 commits into from

Conversation

jsbroks
Copy link
Member

@jsbroks jsbroks commented Mar 18, 2025

Summary by CodeRabbit

  • New Features

    • Launched a revamped Environment Overview page displaying detailed statistics, deployment summaries, resource counts, and telemetry.
    • Introduced intuitive tab navigation for seamless switching between sections including deployments, policies, and resources.
    • Rolled out enhanced views with card-based layouts, detailed deployment modals, and a handy environment ID copy button.
    • Added a new component for displaying deployment telemetry data and a button to copy the Environment ID to the clipboard.
    • Implemented new pages for deployments, policies, and resources, each providing specific insights and functionalities.
  • Refactor

    • Streamlined the UI by removing sidebar elements and improving URL redirection logic for a more focused experience.

Copy link
Contributor

coderabbitai bot commented Mar 18, 2025

Walkthrough

The pull request refactors and extends the environment pages of the web service. It renames the main component to "EnvironmentOverviewPage" and updates URL generation using a new utility. The layout is simplified by removing sidebar components. Multiple new components are introduced to support deployments, policies, resources, telemetry, and tab-based navigation. In addition, the API routing is enhanced by integrating new routers and helper functions for deployment statistics and version distribution, and new UI components for detailed deployment views and copying environment IDs are added.

Changes

File(s) Change Summary
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/page.tsx
apps/webservice/src/app/urls.ts
Renamed EnvironmentPage to EnvironmentOverviewPage and updated URL construction using the new overview method in the urls utility.
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/layout.tsx Removed sidebar components and related logic; simplified the layout structure.
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/_components/EnvironmentTabs.tsx Introduced the EnvironmentTabs component for tab-based navigation across environment views.
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/_deployments/page.tsx
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/deployments/page.tsx
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/deployments/_components/DeploymentDetail.tsx
Added and restructured deployment pages using a new card layout, incorporated EnvironmentDeploymentsPageContent for displaying deployments, and introduced DeploymentDetail for detailed modal views.
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/_policies/page.tsx
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/policies/page.tsx
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/policies/PoliciesPageContent.tsx
Revised the policies pages to update parameter handling and replace redirect logic with component rendering via the new PoliciesPageContent.
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/_resources/page.tsx
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/resources/page.tsx
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/resources/ResourcesPageContent.tsx
Updated the resources page to a card-based layout, streamlined the function signature, and added ResourcesPageContent for resource management with filtering features.
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/overview/page.tsx
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/overview/_components/CopyEnvIdButton.tsx
apps/webservice/src/app/[workspaceSlug]/.../environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx
Introduced the EnvironmentOverviewPage for detailed environment stats, added a CopyEnvIdButton for copying IDs, and created DeploymentTelemetryTable (with subcomponents) for displaying telemetry data.
packages/api/src/router/environment.ts
packages/api/src/router/environment-page/router.ts
packages/api/src/router/environment-page/overview/router.ts
packages/api/src/router/environment-page/overview/deployment-stats.ts
packages/api/src/router/environment-page/overview/version-distro.ts
Enhanced API routing by adding an environmentPageRouter and overviewRouter with new procedures (latestDeploymentStats and telemetry.byDeploymentId), along with helper functions getDeploymentStats and getVersionDistro for deployment statistics and version distribution.

Sequence Diagram(s)

sequenceDiagram
  participant U as User
  participant T as EnvironmentTabs
  participant R as Router
  U->>T: Click on a tab
  T->>T: Update active tab state based on pathname
  T->>R: Invoke router.push(new URL)
  R->>App: Navigate to the selected environment view
Loading
sequenceDiagram
  participant U as User
  participant DP as DeploymentsPage
  participant EDP as EnvironmentDeploymentsPageContent
  participant DD as DeploymentDetail Modal
  U->>DP: Load DeploymentsPage
  DP->>EDP: Pass environmentId as prop
  EDP->>U: Render deployments list in card layout
  U->>EDP: Click on a deployment row
  EDP->>DD: Open DeploymentDetail modal with deployment details
  DD->>U: Display detailed metrics and actions
Loading

Possibly related PRs

  • fix: Set release channels for policy and env #183: The changes in the main PR, specifically the modifications to the EnvironmentDrawer component's logic regarding policy handling, are directly related to the changes in the retrieved PR, which also involves updates to the EnvironmentDrawer component, particularly in how it manages environment-related information and policies.
  • fix: Add override policy to env #295: The changes in the main PR, specifically the modifications to the EnvironmentDrawer component's logic regarding policy handling, are directly related to the changes in the retrieved PR, which also focuses on the management of environment policies within the EnvironmentDrawer. Both PRs involve updates to how policies are rendered and managed in the same component.

Poem

I'm a hopping rabbit, coding with glee,
Skipping through changes both wild and free.
New views and tabs make my heart tick,
With deployments and policies looking slick.
I nibble on bytes with a joyful hop,
Celebrating refactored code non-stop! 🐇

✨ Finishing Touches
  • 📝 Generate Docstrings

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

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

🧹 Nitpick comments (1)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx (1)

445-479: Placeholders for Deployments, Resources, and Policies
These placeholders are well-labeled, providing a clear location for future data. Ensure that you eventually integrate real, dynamic data for these tabs to keep the user experience consistent across the entire environment overview.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a7b3104 and b5eed21.

📒 Files selected for processing (1)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx (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)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Lint
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (3)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx (3)

80-81: Handle zero division for deployment success calculation
If stats.deployments.total is 0, the expression (stats.deployments.successful / stats.deployments.total) will trigger a division by zero, resulting in Infinity or NaN. Consider a conditional check to avoid errors or skewed results.

Would you like me to auto-generate a script that verifies if stats.deployments.total can be 0 in usage?


89-213: Great use of UI components for environment overview
The descriptive headers and structured data grid are clear and user-friendly, promoting easy navigation.


387-440: Flexible policy configuration
The policy settings code is well-structured. Ensure that future expansions (e.g., more or different approvalRequirement values or dynamic release windows) can be easily supported. Also confirm that the static release window times from 2023 are intentional.

Comment on lines 5 to 10
export default function EnvironmentOverviewPage(props: {
params: {
workspaceSlug: string;
systemSlug: string;
environmentId: string;
}>;
};
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

Use the dynamic props to retrieve environment data
It appears we're not using the params from props. Switching from a static environment object to dynamic data ensures that the environment details reflect real or fetched data.

If you'd like, we can fetch environment data for the environmentId from an external API or server function. For example:

 export default function EnvironmentOverviewPage(props: {
   params: {
     workspaceSlug: string;
     systemSlug: string;
     environmentId: string;
   };
 }) {
-  // Sample static data
-  const environmentData = { ... }
+  const environmentData = await fetchEnvironmentData(props.params.environmentId);
   ...
 }
📝 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
export default function EnvironmentOverviewPage(props: {
params: {
workspaceSlug: string;
systemSlug: string;
environmentId: string;
}>;
};
export default async function EnvironmentOverviewPage(props: {
params: {
workspaceSlug: string;
systemSlug: string;
environmentId: string;
};
}) {
const environmentData = await fetchEnvironmentData(props.params.environmentId);
// ... rest of your component code that uses environmentData
}

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

🧹 Nitpick comments (10)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/page.tsx (2)

6-8: Consider updating function signature to align with other environment pages.

The function signature includes the workspaceSlug parameter, but in other files like resources/page.tsx, this has been simplified to only include environmentId. For consistency, consider following the same pattern.

export default async function ResourcesPage(props: {
-  params: Promise<{ workspaceSlug: string; environmentId: string }>;
+  params: Promise<{ environmentId: string }>;
}) {

16-19: Consider implementing consistent UI pattern.

The UI pattern here (using a simple div with container and margin classes) differs from the Card-based pattern used in the updated resources/page.tsx file. For consistency across environment pages, consider implementing the Card-based UI pattern.

return (
-  <div className="container m-8 mx-auto">
-    <EditFilterForm environment={environment} workspaceId={workspace.id} />
-  </div>
+  <Card>
+    <CardHeader>
+      <CardTitle>Resources</CardTitle>
+      <CardDescription>Resources managed in this environment</CardDescription>
+    </CardHeader>
+    <CardContent>
+      <EditFilterForm environment={environment} workspaceId={workspace.id} />
+    </CardContent>
+  </Card>
);
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (1)

3-7: Missing error handling for non-existent environment.

The function doesn't check if the environment exists after awaiting the params, unlike the previous implementation. While the error might be caught downstream in the PoliciesPageContent component, it's a good practice to handle this at the page level.

export default async function PoliciesPage(props: {
  params: Promise<{ environmentId: string }>;
}) {
  const { environmentId } = await props.params;
+  const environment = await api.environment.byId(environmentId);
+  if (environment == null) notFound();
  return <PoliciesPageContent environmentId={environmentId} />;
}
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (1)

39-45: Consider optimizing tab change handler.

The current implementation has potentially redundant state updates. The tab change handler sets the active tab after navigation occurs, which might lead to unnecessary renders.

const onTabChange = (value: string) => {
+  setActiveTab(value);
  if (value === "overview") router.push(overviewUrl);
  if (value === "deployments") router.push(deploymentsUrl);
  if (value === "resources") router.push(resourcesUrl);
  if (value === "policies") router.push(policiesUrl);
-  setActiveTab(value);
};

Additionally, consider using a switch statement or object lookup for better readability and maintainability:

const onTabChange = (value: string) => {
  setActiveTab(value);
-  if (value === "overview") router.push(overviewUrl);
-  if (value === "deployments") router.push(deploymentsUrl);
-  if (value === "resources") router.push(resourcesUrl);
-  if (value === "policies") router.push(policiesUrl);
+  const urlMap = {
+    overview: overviewUrl,
+    deployments: deploymentsUrl,
+    resources: resourcesUrl,
+    policies: policiesUrl
+  };
+  router.push(urlMap[value as keyof typeof urlMap]);
};
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx (1)

11-24: Missing error handling for non-existent environment.

Similar to the PoliciesPage component, this page doesn't verify if the environment exists before rendering the content. Consider adding error handling to prevent rendering with an invalid environment ID.

export default async function ResourcesPage(props: {
  params: Promise<{ environmentId: string }>;
}) {
  const { environmentId } = await props.params;
+  const environment = await api.environment.byId(environmentId);
+  if (environment == null) notFound();
  return (
    <Card>
      <CardHeader>
        <CardTitle>Resources</CardTitle>
        <CardDescription>Resources managed in this environment</CardDescription>
      </CardHeader>
      <CardContent>
-        <ResourcesPageContent environmentId={environmentId} />
+        <ResourcesPageContent environmentId={environment.id} />
      </CardContent>
    </Card>
  );
}
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx (1)

433-485: Remove or clarify large commented code blocks.
This extensive commented-out section may cause confusion and code bloat. If it’s not used soon, consider removing it or moving these references into documentation or separate feature-flagged code paths.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (2)

80-138: Consider dynamic data fetching for deployments.
All deployment entries are static, which might become stale quickly. If these are placeholders, clarify in comments or plan for integration with a real API soon.

Would you like me to provide a stub for an async data-fetching mechanism or open a new issue for future implementation?


306-311: Leverage row selection logic for more user-friendly interactions.
You already have onClick={() => setSelectedDeployment(deployment)} on each row. Consider highlighting or focusing the row for clarity.

<TableRow
  key={deployment.id}
  className="cursor-pointer border-b border-neutral-800/50 hover:bg-neutral-800/20"
+ style={{ outline: selectedDeployment?.id === deployment.id ? '2px solid #4f46e5' : 'none' }}
  onClick={() => setSelectedDeployment(deployment)}
>
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx (2)

273-316: Consider fallback logic for filter conditions.
When resourceFilter.operator is "and" or "or", each condition is matched by exact string comparison. If value is empty or the condition type is unrecognized, resources pass through. You might want to handle incomplete or unknown filter conditions more explicitly.


550-554: Enhance accessibility in grid view.
Resource cards lack explicit interactive elements or ARIA attributes for accessibility. Consider adding focus, role, and label attributes so keyboard and screen-reader users can navigate effectively.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between dce8bd1 and 732c9a2.

📒 Files selected for processing (15)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_deployments/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_policies/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/DeploymentDetail.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx (1 hunks)
  • apps/webservice/src/app/urls.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.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/urls.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_deployments/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_policies/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/DeploymentDetail.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx
🧬 Code Definitions (8)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_deployments/page.tsx (1)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/page.tsx (1) (1)
  • DeploymentsPage (11:26)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_policies/page.tsx (1)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (1) (1)
  • PoliciesPage (3:8)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/page.tsx (2)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx (1) (1)
  • ResourcesPage (11:26)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/EditFilterForm.tsx (1) (1)
  • EditFilterForm (113:214)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx (2)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/page.tsx (1) (1)
  • ResourcesPage (6:21)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx (1) (1)
  • ResourcesPageContent (18:915)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (1)
apps/webservice/src/app/urls.ts (1) (1)
  • urls (194:194)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (1)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/DeploymentDetail.tsx (1) (1)
  • DeploymentDetail (13:646)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (2)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_policies/page.tsx (1) (1)
  • PoliciesPage (3:13)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (1) (1)
  • PoliciesPageContent (34:495)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx (2)
apps/webservice/src/app/urls.ts (1) (1)
  • urls (194:194)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (1) (1)
  • EnvironmentTabs (11:57)
🔇 Additional comments (18)
apps/webservice/src/app/urls.ts (1)

142-142: Clean and consistent addition of the overview URL method.

The new overview method follows the established pattern in the codebase and correctly utilizes the existing buildUrl function with the appropriate base segments.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_deployments/page.tsx (1)

1-13: Implementation looks good, but clarify the purpose of this component.

This component correctly handles the async parameters and renders the DeploymentsCard. The implementation is clean and straightforward.

However, there's a similar component at deployments/page.tsx with a different Card-based UI structure. Could you clarify the relationship between these two files and why we need both implementations?

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_policies/page.tsx (1)

1-13: Simple redirection logic implemented correctly.

The implementation correctly redirects to a specific policies URL, constructed using the provided parameters.

Note that this differs from the implementation in policies/page.tsx which renders a PoliciesPageContent component instead of redirecting. It would be helpful to understand the intended purpose of each implementation.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/page.tsx (1)

1-24: UI enhancement with proper Card components.

The changes improve the presentation by implementing a structured Card layout with appropriate semantic sections (header, title, description, content). The implementation correctly delegates the content rendering to the specialized EnvironmentDeploymentsPageContent component.

The code maintains the same functionality while enhancing the UI, which is a good improvement.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/page.tsx (1)

9-11: Error handling pattern is solid.

Good implementation of error handling using the notFound() function when expected data isn't available. This prevents the app from crashing and provides a better user experience.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (4)

11-17: Good use of type-safe params extraction.

The component correctly uses the useParams hook with TypeScript generics to ensure type safety for the URL parameters, which is a good practice.


18-25: Clean URL construction with utility method.

Using the urls utility for URL construction is a good practice as it centralizes URL generation logic, making it easier to maintain and update URLs across the application.


27-34: Tab initialization logic is well-structured.

The getInitialTab function effectively determines the initial active tab based on the current pathname, providing a good user experience by highlighting the correct tab when navigating directly to a specific URL.


47-56: Semantic and accessible tab structure.

The tab structure using Tabs, TabsList, and TabsTrigger components is semantically correct and likely provides good accessibility. The component correctly binds the active tab value and change handler.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx (3)

1-7: Well-structured imports for UI components.

The imports are well-organized, grouping related UI components from the card library together, which improves code readability.


11-14: Function signature simplification is a good improvement.

The function now only requires the environmentId parameter, which simplifies the interface and reduces the need for fetching workspace data that may not be necessary for rendering resource information.


15-24: Consistent card-based UI pattern.

The implementation uses a clean, consistent card-based UI pattern with appropriate header and content sections, making it visually consistent with other parts of the application. The descriptive title and subtitle provide clear context for users.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (2)

1-458: All-around solid component structure and descriptive tooltips.

Your usage of tooltips, badges, and cards creates a clear hierarchy of environment policy details, providing a good user experience.


477-495: Layout and styling look consistent.

Beyond the noted release-interval mismatch, the rest of the UI elements here operate cleanly and match the overall design well.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx (1)

42-87: New layout structure is cohesive and user-friendly.

The simplified approach with <EnvironmentTabs> and the top-level PageHeader suits the updated design by removing sidebar complexity and focusing on key environment details.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx (1)

19-25: Question the usage of a Promise type for props.
Declaring params as a Promise<{ ... }> is unconventional and may cause issues if not properly awaited or resolved before rendering. Typically, you would accept a direct object or an async function that awaits the params before rendering.

Are there other references in the codebase that ensure this promise is resolved before rendering? Consider verifying its usage in related routes or data-fetching logic.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (1)

140-154:

❓ Verification inconclusive

Check edge cases in time and duration formatting.
formatDuration and formatTimeAgo handle positive values but may behave unexpectedly with negative, zero, or extremely large numbers.

Please ensure no negative or unintended values occur by verifying upstream data. Use a script or test suite to confirm correct usage:


🏁 Script executed:

#!/bin/bash
# Searching for potential zero/negative usage of time values
rg --case-sensitive 'formatDuration|formatTimeAgo' -A 5

Length of output: 12654


ACTION: Verify and Align Edge Case Handling in Time Formatting

There appears to be an inconsistency in how edge cases are managed across similar functions:

  • In EnvironmentDeploymentsPageContent.tsx, formatDuration returns "—" when the input is null and formats 0 as "0s". In contrast, in DeploymentPerformance.tsx, the version of formatDuration returns "N/A" when given a falsy value.
  • Neither implementation explicitly guards against negative numbers or extremely large values. Similarly, formatTimeAgo may yield unexpected output if the supplied date is in the future.

Recommendations:

  • Upstream Verification: Ensure that the data passed to these functions is sanitized upstream, so that negative, future, or otherwise invalid values do not occur.
  • Consistency Check: Consider unifying the behavior across components—for example, deciding whether 0 seconds should display as "0s" or "N/A", and ensuring similar guard clauses for negative values in both formatDuration and formatTimeAgo.
  • Additional Guards: If necessary, add explicit checks to handle negative or overly large numbers, and safeguard against dates in the future in formatTimeAgo.
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx (1)

19-35: Validate or remove default filter condition.
By default, the filter is set to "kind" = "Pod", which may undesirably hide non-Pod resources from the start. Make sure this is intended or consider a neutral default.

Comment on lines +459 to +476
Release Interval
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<IconHelpCircle className="h-3.5 w-3.5 text-neutral-500 hover:text-neutral-300" />
</TooltipTrigger>
<TooltipContent className="max-w-[350px]">
<p>
Minimum time that must pass between active releases
to this environment. This "cooling period" helps
ensure stability between deployments.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="text-right font-medium text-neutral-100">
{environmentPolicy.releaseWindows.length}
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

Logic mismatch: "Release Interval" displays the count of release windows.

Currently, the code shows environmentPolicy.releaseWindows.length under the "Release Interval" label, which is misleading. If you intend to show the minimum release interval duration, replace it with the relevant value (e.g., minimumReleaseInterval) and format it, similar to how you handle rolloutDuration.

Consider this example fix:

- {environmentPolicy.releaseWindows.length}
+ {formatDurationText(environmentPolicy.minimumReleaseInterval)}
📝 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
Release Interval
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<IconHelpCircle className="h-3.5 w-3.5 text-neutral-500 hover:text-neutral-300" />
</TooltipTrigger>
<TooltipContent className="max-w-[350px]">
<p>
Minimum time that must pass between active releases
to this environment. This "cooling period" helps
ensure stability between deployments.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="text-right font-medium text-neutral-100">
{environmentPolicy.releaseWindows.length}
Release Interval
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<IconHelpCircle className="h-3.5 w-3.5 text-neutral-500 hover:text-neutral-300" />
</TooltipTrigger>
<TooltipContent className="max-w-[350px]">
<p>
Minimum time that must pass between active releases
to this environment. This "cooling period" helps
ensure stability between deployments.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="text-right font-medium text-neutral-100">
{formatDurationText(environmentPolicy.minimumReleaseInterval)}

Comment on lines 52 to 55
Deployed {formatTimeAgo(deployment.deployedAt)}
</p>
</div>
<div>{renderStatusBadge(deployment.status)}</div>
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

Missing function references for status badge and time formatting.

You call formatTimeAgo(deployment.deployedAt) at line 52 and renderStatusBadge(deployment.status) at line 55, but neither is imported or defined. This will cause a runtime error.

One approach:

+function formatTimeAgo(date: Date) {
+  // Replace with actual logic or library call:
+  // e.g., from 'date-fns' or custom logic
+  return 'some time ago';
+}

+function renderStatusBadge(status: string) {
+  // Implement your own status -> badge logic
+  return <span>Status: {status}</span>;
+}

Then incorporate them into the component or import from a shared utility.

Comment on lines 72 to 74
const deploymentSuccess = Math.round(
(stats.deployments.successful / stats.deployments.total) * 100,
);
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

Avoid potential division by zero.
The expression (stats.deployments.successful / stats.deployments.total) * 100 can throw an error when stats.deployments.total is zero.

You can guard against zero to prevent runtime errors:

- const deploymentSuccess = Math.round(
-   (stats.deployments.successful / stats.deployments.total) * 100,
- );
+ const deploymentSuccess = stats.deployments.total > 0
+   ? Math.round((stats.deployments.successful / stats.deployments.total) * 100)
+   : 0;
📝 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 deploymentSuccess = Math.round(
(stats.deployments.successful / stats.deployments.total) * 100,
);
const deploymentSuccess = stats.deployments.total > 0
? Math.round((stats.deployments.successful / stats.deployments.total) * 100)
: 0;

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

🧹 Nitpick comments (7)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx (3)

13-16: Consider adding proper TypeScript typing for the deployment prop.

The deployment prop is typed as any, which loses type safety benefits. Consider creating a proper interface or type for the deployment object.

+interface DeploymentProps {
+  id: string;
+  name: string;
+  status: "success" | "pending" | "failed" | "running";
+  version: string;
+  deployedAt: Date;
+  duration: number | null;
+  resources: number;
+  initiatedBy: string;
+  successRate: number | null;
+}

 export const DeploymentDetail: React.FC<{
-  deployment: any;
+  deployment: DeploymentProps;
   onClose: () => void;
 }> = ({ deployment, onClose }) => {

20-46: Consider extracting complex conditional class logic.

The conditional class string construction is complex and repeated in multiple places. Consider extracting this pattern into a helper function for better maintainability.

+ const getStatusBackgroundClass = (status: string) => {
+   return status === "success"
+     ? "bg-green-500/10"
+     : status === "pending"
+       ? "bg-amber-500/10"
+       : status === "failed"
+         ? "bg-red-500/10"
+         : "bg-blue-500/10";
+ };

 <div
-  className={`-mx-6 -mt-6 mb-6 flex items-center justify-between px-6 py-4 ${
-    deployment.status === "success"
-      ? "bg-green-500/10"
-      : deployment.status === "pending"
-        ? "bg-amber-500/10"
-        : deployment.status === "failed"
-          ? "bg-red-500/10"
-          : "bg-blue-500/10"
-  }`}
+  className={`-mx-6 -mt-6 mb-6 flex items-center justify-between px-6 py-4 ${getStatusBackgroundClass(deployment.status)}`}
 >

208-217: Simplify complex class conditionals.

Multiple class determination conditionals are similarly complex. Consider simplifying the determination of class names based on status.

<div
 className={`absolute left-0 top-1 h-5 w-5 rounded-full ${
-  deployment.status === "success"
-    ? "border-2 border-green-500 bg-green-500/20"
-    : deployment.status === "failed"
-      ? "border-2 border-red-500 bg-red-500/20"
-      : deployment.status === "pending"
-        ? "border border-neutral-700 bg-neutral-800/80"
-        : "border-2 border-blue-500 bg-blue-500/20"
+  {
+    "success": "border-2 border-green-500 bg-green-500/20",
+    "failed": "border-2 border-red-500 bg-red-500/20",
+    "pending": "border border-neutral-700 bg-neutral-800/80",
+    "running": "border-2 border-blue-500 bg-blue-500/20"
+  }[deployment.status] || "border border-neutral-700 bg-neutral-800/80"
} flex items-center justify-center`}
>
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (4)

173-237: Add loading states for summary metrics.

The summary cards display static data with no loading states. Consider adding loading states for these metrics when data is being fetched.

 <div className="mb-6 grid grid-cols-1 gap-4 md:grid-cols-4">
+  {isLoading ? (
+    Array(4).fill(0).map((_, i) => (
+      <div key={i} className="rounded-lg border border-neutral-800 bg-neutral-900/50 p-4">
+        <div className="h-4 w-24 animate-pulse rounded bg-neutral-800"></div>
+        <div className="mt-2 h-8 w-16 animate-pulse rounded bg-neutral-800"></div>
+      </div>
+    ))
+  ) : (

239-273: Add functionality to search and filter controls.

The search and filter controls are not functional. Implement event handlers to filter the deployments based on user input.

 <Input
   placeholder="Search deployments..."
   className="w-full pl-8 md:w-80"
+  onChange={(e) => {
+    const searchValue = e.target.value.toLowerCase();
+    // Filter deployments by search value
+  }}
 />

305-356: Add empty state for no deployments.

There's no handling for when the deployments array is empty. Consider adding an empty state message for better user experience.

 <TableBody>
+  {deployments.length === 0 ? (
+    <TableRow>
+      <TableCell colSpan={8} className="py-8 text-center text-neutral-400">
+        No deployments found
+      </TableCell>
+    </TableRow>
+  ) : (
   {deployments.map((deployment) => (
     // ...existing code
   ))}
+  )}
 </TableBody>

140-168: Consider using date-fns for consistent formatting.

The component defines its own time formatting functions (formatDuration and formatTimeAgo) while the imported DeploymentDetail component uses formatDuration from date-fns. Consider using date-fns consistently for all time formatting.

-const formatDuration = (seconds: number | null) => {
-  if (seconds === null) return "—";
-
-  if (seconds < 60) {
-    return `${seconds}s`;
-  } else if (seconds < 3600) {
-    const minutes = Math.floor(seconds / 60);
-    const remainingSeconds = seconds % 60;
-    return `${minutes}m ${remainingSeconds}s`;
-  } else {
-    const hours = Math.floor(seconds / 3600);
-    const minutes = Math.floor((seconds % 3600) / 60);
-    return `${hours}h ${minutes}m`;
-  }
-};
+import { formatDuration as formatDurationFn, formatDistanceToNow } from "date-fns";
+
+const formatDuration = (seconds: number | null) => {
+  if (seconds === null) return "—";
+  
+  // Convert seconds to milliseconds for date-fns
+  return formatDurationFn({ seconds });
+};
+
+const formatTimeAgo = (date: Date) => {
+  return formatDistanceToNow(date, { addSuffix: true });
+};
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 732c9a2 and 7a24e39.

📒 Files selected for processing (2)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx (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)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx
🧬 Code Definitions (1)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (1)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx (1) (1)
  • DeploymentDetail (13-646)
🔇 Additional comments (1)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (1)

373-378: Properly implement the DeploymentDetail component.

Good integration of the DeploymentDetail component with proper state management.

Comment on lines +273 to +399
).toLocaleTimeString()}
] Creating deployment plan for {deployment.resources}{" "}
resources...
</p>
<p className="text-neutral-400">
[
{new Date(
deployment.deployedAt.getTime() + 62000,
).toLocaleTimeString()}
] Updating configuration...
</p>
<p className="text-neutral-400">
[
{new Date(
deployment.deployedAt.getTime() + 95000,
).toLocaleTimeString()}
] Applying changes to resources...
</p>
{deployment.status === "success" ? (
<>
<p className="text-neutral-400">
[
{new Date(
deployment.deployedAt.getTime() + 145000,
).toLocaleTimeString()}
] Running post-deployment verification...
</p>
<p className="text-neutral-400">
[
{new Date(
deployment.deployedAt.getTime() + 180000,
).toLocaleTimeString()}
] All health checks passed.
</p>
<p className="text-green-400">
[
{new Date(
deployment.deployedAt.getTime() + 185000,
).toLocaleTimeString()}
] Deployment completed successfully!
</p>
</>
) : deployment.status === "failed" ? (
<>
<p className="text-neutral-400">
[
{new Date(
deployment.deployedAt.getTime() + 110000,
).toLocaleTimeString()}
] Updating resource '{deployment.name}-1'...
</p>
<p className="text-red-400">
[
{new Date(
deployment.deployedAt.getTime() + 125000,
).toLocaleTimeString()}
] Error: Failed to update resource '{deployment.name}
-1'.
</p>
<p className="text-red-400">
[
{new Date(
deployment.deployedAt.getTime() + 126000,
).toLocaleTimeString()}
] Error details: Configuration validation failed -
insufficient permissions.
</p>
<p className="text-neutral-400">
[
{new Date(
deployment.deployedAt.getTime() + 127000,
).toLocaleTimeString()}
] Rolling back changes...
</p>
<p className="text-red-400">
[
{new Date(
deployment.deployedAt.getTime() + 135000,
).toLocaleTimeString()}
] Deployment failed. See detailed logs for more
information.
</p>
</>
) : (
<>
<p className="text-blue-400">
[
{new Date(
deployment.deployedAt.getTime() + 105000,
).toLocaleTimeString()}
] Currently updating resource {deployment.name}-3...
</p>
<p className="text-blue-400">
[{new Date().toLocaleTimeString()}] Deployment in
progress (2/{deployment.resources} resources
completed)...
</p>
</>
)}
</div>
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

Consider using a real logging library or API endpoint.

The deployment logs section uses hardcoded mock data with calculated timestamps. In a real implementation, these should be fetched from an API.

Comment on lines +77 to +79
export const EnvironmentDeploymentsPageContent: React.FC<{
environmentId: string;
}> = () => {
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

Unused environmentId prop.

The component accepts an environmentId prop but doesn't use it anywhere. Either utilize this prop for fetching data or remove it if unnecessary.

 export const EnvironmentDeploymentsPageContent: React.FC<{
   environmentId: string;
-}> = () => {
+}> = ({ environmentId }) => {
+  // Use environmentId to fetch deployments data
+  // useEffect(() => {
+  //   fetchDeployments(environmentId);
+  // }, [environmentId]);
📝 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
export const EnvironmentDeploymentsPageContent: React.FC<{
environmentId: string;
}> = () => {
export const EnvironmentDeploymentsPageContent: React.FC<{
environmentId: string;
}> = ({ environmentId }) => {
// Use environmentId to fetch deployments data
// useEffect(() => {
// fetchDeployments(environmentId);
// }, [environmentId]);
// ... rest of the component implementation
}

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

♻️ Duplicate comments (3)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx (3)

20-20: 🛠️ Refactor suggestion

Enhance modal accessibility.

The modal implementation lacks critical accessibility features.

-<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60">
+<div 
+  className="fixed inset-0 z-50 flex items-center justify-center bg-black/60" 
+  role="dialog" 
+  aria-modal="true"
+  onKeyDown={(e) => {
+    if (e.key === 'Escape') onClose();
+  }}
+>

52-53: ⚠️ Potential issue

Missing helper function definition.

The formatTimeAgo function is used but not defined within this component or imported.

+const formatTimeAgo = (date: Date) => {
+  const now = new Date();
+  const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
+
+  if (diffInSeconds < 60) return `${diffInSeconds} seconds ago`;
+  if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`;
+  if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`;
+  if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 86400)} days ago`;
+  return date.toLocaleDateString();
+};

 <p className="text-sm text-neutral-400">
   Deployed {formatTimeAgo(deployment.deployedAt)}
 </p>

55-55: ⚠️ Potential issue

Missing helper function definition.

The renderStatusBadge function is used but not defined within this component or imported.

+const renderStatusBadge = (status: string) => {
+  switch (status) {
+    case "success":
+      return (
+        <Badge variant="outline" className="border-green-500/30 bg-green-500/10 text-green-400">
+          Success
+        </Badge>
+      );
+    case "failed":
+      return (
+        <Badge variant="outline" className="border-red-500/30 bg-red-500/10 text-red-400">
+          Failed
+        </Badge>
+      );
+    case "pending":
+      return (
+        <Badge variant="outline" className="border-amber-500/30 bg-amber-500/10 text-amber-400">
+          Pending
+        </Badge>
+      );
+    default:
+      return (
+        <Badge variant="outline" className="border-blue-500/30 bg-blue-500/10 text-blue-400">
+          In Progress
+        </Badge>
+      );
+  }
+};

 <div>{renderStatusBadge(deployment.status)}</div>
🧹 Nitpick comments (8)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (1)

37-63: Prefer fetching policy data dynamically.
Currently, environmentPolicy is hard-coded. For production use, consider fetching policy data from an API or configuration source so it remains accurate and maintainable over time.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx (1)

13-15: Consider using a more specific type for the deployment prop.

The component uses any for the deployment prop type, which loses type safety. Consider creating a proper interface or type for the deployment object.

+interface Deployment {
+  id: string;
+  name: string;
+  status: "success" | "failed" | "pending" | "running";
+  version: string;
+  deployedAt: Date;
+  duration: number;
+  resources: number;
+  initiatedBy: string;
+  successRate: number | null;
+}

-export const DeploymentDetail: React.FC<{
-  deployment: any;
-  onClose: () => void;
-}> = ({ deployment, onClose }) => {
+export const DeploymentDetail: React.FC<{
+  deployment: Deployment;
+  onClose: () => void;
+}> = ({ deployment, onClose }) => {
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (1)

39-45: Consider refactoring the tab change handler for better maintainability.

The onTabChange function uses sequential if statements to handle each tab value. This approach works, but could be refactored to be more maintainable.

-  const onTabChange = (value: string) => {
-    if (value === "overview") router.push(overviewUrl);
-    if (value === "deployments") router.push(deploymentsUrl);
-    if (value === "resources") router.push(resourcesUrl);
-    if (value === "policies") router.push(policiesUrl);
-    setActiveTab(value);
-  };
+  const onTabChange = (value: string) => {
+    const urlMap: Record<string, string> = {
+      overview: overviewUrl,
+      deployments: deploymentsUrl,
+      resources: resourcesUrl,
+      policies: policiesUrl,
+    };
+    
+    router.push(urlMap[value] || overviewUrl);
+    setActiveTab(value);
+  };
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx (5)

821-826: Avoid direct DOM manipulation in React components.

Using document.getElementById is not recommended in React. Use React refs instead for accessing DOM elements.

- const typeSelect = document.getElementById(
-   "condition-type",
- ) as HTMLSelectElement;
- const valueSelect = document.getElementById(
-   "condition-value",
- ) as HTMLSelectElement;
+ // At the top of your component:
+ const typeSelectRef = useRef<HTMLSelectElement>(null);
+ const valueSelectRef = useRef<HTMLSelectElement>(null);
+
+ // And in the JSX where you render these elements:
+ <select
+   ref={typeSelectRef}
+   className="w-full rounded-md border border-neutral-700 bg-neutral-900 px-3 py-2 text-sm"
+   defaultValue="kind"
+   id="condition-type"
+ >
+
+ <select
+   ref={valueSelectRef}
+   className="w-full rounded-md border border-neutral-700 bg-neutral-900 px-3 py-2 text-sm"
+   id="condition-value"
+ >
+
+ // Then in your click handler:
+ const typeSelect = typeSelectRef.current;
+ const valueSelect = valueSelectRef.current;

38-39: Remove ESLint disable comment by fixing the dependency issue.

The ESLint disable comment for react-hooks/exhaustive-deps suggests there might be a dependency issue in a hook. Consider properly addressing the dependency issue rather than disabling the lint rule.

-  // eslint-disable-next-line react-hooks/exhaustive-deps
-  const resources = [
+  // Use useMemo to properly handle this value if it needs to be memoized
+  const resources = useMemo(() => [
   // ...resource array definition
-  ];
+  ], []); // Empty dependency array since this is static data

1-915: Consider breaking down this large component into smaller ones.

At over 900 lines, this component is quite large and could be challenging to maintain. Consider extracting logical sections into smaller, focused components:

  1. ResourceSummaryCards
  2. ResourceFilters
  3. ResourceGrid
  4. ResourceTable
  5. FilterEditorModal

This refactoring would improve maintainability and reusability of the individual parts.


277-291: Add type definitions for conditions to improve code safety.

The filter conditions are using any type which can lead to type safety issues. Define proper interfaces for your filter conditions.

// Define these interfaces at the top of your file
interface FilterCondition {
  type: 'kind' | 'provider' | 'status' | 'component';
  operator: 'equals';
  not: boolean;
  value: string;
}

interface ResourceFilter {
  type: 'comparison';
  operator: 'and' | 'or';
  not: boolean;
  conditions: FilterCondition[];
}

// Then update your useState to use these types:
const [resourceFilter, setResourceFilter] = useState<ResourceFilter>({
  // ...existing implementation
});

// And update the any types in your filter functions:
resourceFilter.conditions.forEach((condition: FilterCondition) => {
  // ...
});

// And in your OR filter:
resourceFilter.conditions.some((condition: FilterCondition) => {
  // ...
});

Also applies to: 296-310


322-390: Extract renderResourceCard to a separate component for better reusability.

The renderResourceCard function is quite complex and would be better as a standalone component. This would improve separation of concerns and reusability.

// Create a separate component
const ResourceCard: React.FC<{ resource: Resource }> = ({ resource }) => {
  const statusColor = {
    healthy: "bg-green-500",
    degraded: "bg-amber-500",
    failed: "bg-red-500",
    updating: "bg-blue-500",
    unknown: "bg-neutral-500",
  };

  return (
    <div
      className="rounded-lg border border-neutral-800 bg-neutral-900/60 p-4 transition-all hover:border-neutral-700 hover:bg-neutral-900"
    >
      {/* Card content */}
    </div>
  );
};

// Then in your main component, replace the render call with:
{filteredResources.map((resource) => (
  <ResourceCard key={resource.id} resource={resource} />
))}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7a24e39 and 0d0b57c.

📒 Files selected for processing (15)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_deployments/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_policies/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx (1 hunks)
  • apps/webservice/src/app/urls.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/webservice/src/app/urls.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_deployments/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_policies/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/page.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)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx
🧬 Code Definitions (4)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/page.tsx (2)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_deployments/page.tsx (1) (1)
  • DeploymentsPage (3-13)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx (1) (1)
  • EnvironmentDeploymentsPageContent (77-381)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (1)
apps/webservice/src/app/urls.ts (1) (1)
  • urls (196-196)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx (2)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_resources/page.tsx (1) (1)
  • ResourcesPage (6-21)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx (1) (1)
  • ResourcesPageContent (18-915)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (2)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_policies/page.tsx (1) (1)
  • PoliciesPage (3-13)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (1) (1)
  • PoliciesPageContent (34-495)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (12)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (1)

3-7: Ensure parameter resolution reliability.
Returning <PoliciesPageContent environmentId={environmentId} /> works well. However, consider whether you need any fallback or error handling if props.params fails to resolve (e.g., in edge cases). Otherwise, this is straightforward and looks good.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (1)

458-476: Logic mismatch with "Release Interval".
This code displays environmentPolicy.releaseWindows.length under "Release Interval." However, the tooltip indicates it should show the minimum delay between releases, stored in minimumReleaseInterval. This is an existing issue that still applies.

Apply this diff to align the label and value correctly:

- {environmentPolicy.releaseWindows.length}
+ {formatDurationText(environmentPolicy.minimumReleaseInterval)}
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/page.tsx (3)

1-9: Good use of UI component imports.

The card components from the UI library are properly imported and utilized to create a structured layout.


16-24: Well-structured card implementation.

The card layout implementation provides a clear visual hierarchy with appropriate header and content sections.


22-22: Proper component composition with environment ID prop.

The EnvironmentDeploymentsPageContent component correctly receives the environmentId prop extracted from the async params.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx (2)

273-399: Consider using a real logging library or API endpoint.

The deployment logs section uses hardcoded mock data with calculated timestamps. In a real implementation, these should be fetched from an API.


514-642: Good implementation of contextual actions.

The action buttons in the footer dynamically adapt based on the deployment status, which is a good UX pattern. Different actions are available for different states (failed, success, pending).

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (1)

1-58: LGTM! Well-implemented tab navigation component.

The EnvironmentTabs component is well-structured and implements a clean tab-based navigation system. The URL construction is nicely centralized through the urls utility, and the tab initialization logic is clear and straightforward.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/page.tsx (2)

11-13: Function signature update improves simplicity.

The updated signature only requires environmentId which aligns with the component's actual needs. This is a good simplification.


14-25: Clean and concise component structure.

The Card-based UI provides a clear and consistent layout for the resources page. Separation of concerns is well-implemented by extracting the content to a dedicated component.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/resources/ResourcesPageContent.tsx (2)

18-35: Well-structured component initialization with useState hooks.

The component's state management is well-organized with clear initial values for view selection, filter editor visibility, and resource filtering.


269-316: Well-implemented filtering logic with useMemo for performance.

The filtering logic is well-structured and uses useMemo to prevent unnecessary recalculations on each render.

Comment on lines +37 to +261
events: [
{
type: "error",
timestamp: new Date("2024-03-15T08:12:00"),
message: "Container failed to start: OOMKilled",
},
{
type: "warning",
timestamp: new Date("2024-03-15T08:11:30"),
message: "Memory usage exceeded limit",
},
],
},
{
id: "res-5",
name: "monitoring-server",
kind: "VM",
provider: "gcp",
region: "us-west-1",
status: "healthy",
version: "n/a",
lastUpdated: new Date("2024-03-10T15:30:00"),
component: "Monitoring",
healthScore: 96,
metrics: {
cpu: 15,
memory: 40,
disk: 30,
},
events: [
{
type: "normal",
timestamp: new Date("2024-03-10T15:30:00"),
message: "VM started successfully",
},
],
},
{
id: "res-6",
name: "backend-pod-1",
kind: "Pod",
provider: "kubernetes",
region: "us-west-2",
status: "healthy",
version: "backend:4.1.0",
lastUpdated: new Date("2024-03-10T11:45:00"),
component: "Backend",
healthScore: 99,
metrics: {
cpu: 45,
memory: 38,
},
events: [
{
type: "normal",
timestamp: new Date("2024-03-10T11:45:00"),
message: "Pod started successfully",
},
],
},
{
id: "res-7",
name: "backend-pod-2",
kind: "Pod",
provider: "kubernetes",
region: "us-west-2",
status: "healthy",
version: "backend:4.1.0",
lastUpdated: new Date("2024-03-10T11:45:00"),
component: "Backend",
healthScore: 97,
metrics: {
cpu: 49,
memory: 42,
},
},
{
id: "res-8",
name: "analytics-queue",
kind: "Service",
provider: "aws",
region: "us-west-2",
status: "updating",
version: "n/a",
lastUpdated: new Date("2024-03-15T14:22:00"),
component: "Analytics",
healthScore: 90,
metrics: {
cpu: 28,
memory: 35,
},
events: [
{
type: "normal",
timestamp: new Date("2024-03-15T14:22:00"),
message: "Service configuration update in progress",
},
],
},
];
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Replace hardcoded sample data with API calls in production.

The hardcoded resource data is fine during development, but should be replaced with proper API calls before going to production.


🏁 Script executed:

#!/bin/bash
# Check for API client imports and environment API calls

# Look for a service or API client that would handle environment resources
echo "Searching for environment resource API client or service:"
rg -A 3 "environment.*resource|resource.*environment" --glob "*.{ts,tsx}"

# Look for existing API calls to retrieve resources
echo -e "\nLooking for existing API calls that fetch resources:"
rg "api\.(environment|resource)" --glob "*.{ts,tsx}"

Length of output: 66795


Action Required: Replace hardcoded sample data with dynamic API calls

The static resource array defined between lines 37–261 is acceptable for development/demo purposes, but it needs to be replaced by a proper API call before shipping to production. Based on our codebase review, there are several instances (e.g., the various usages of api.resource.byWorkspaceId.list.useQuery, api.resource.byId.useQuery, and related calls) that fetch resource data dynamically. Please update this file to integrate similar hooks so that resource data is fetched at runtime with proper loading and error handling.

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 (5)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/CopyEnvIdButton.tsx (1)

1-33: Add error handling for clipboard operations.

The component works well for copying environment IDs to the clipboard. However, clipboard operations can fail in some browsers or contexts (e.g., permissions not granted).

Consider adding error handling:

 const copyEnvironmentId = () => {
-  navigator.clipboard.writeText(environmentId);
-  toast.success("Environment ID copied", {
-    description: environmentId,
-    duration: 2000,
-  });
+  navigator.clipboard.writeText(environmentId)
+    .then(() => {
+      toast.success("Environment ID copied", {
+        description: environmentId,
+        duration: 2000,
+      });
+    })
+    .catch((error) => {
+      console.error("Failed to copy: ", error);
+      toast.error("Failed to copy environment ID", {
+        duration: 2000,
+      });
+    });
 };
packages/api/src/router/environment-page/overview.ts (4)

1-3: Consider narrower imports from lodash for tree-shaking.

Currently importing the entire _ object might increase bundle size. If you only need specific methods (e.g., sumBy), importing them individually can help reduce final bundle size.


34-40: Clarify naming for deployedStatuses.

Including failure statuses under deployedStatuses can be confusing. Consider renaming to something more descriptive like relevantStatuses or trackedStatuses to better reflect its broader usage.


91-108: Consider indexing for better performance on status queries.

Since the query aggregates by job.status and orders by job.createdAt, adding a composite index on (status, createdAt) in the job table could improve performance when dealing with large volumes of deployments or resources.


169-189: Consider pagination for large deployments.

If an environment has a large number of deployments, mapping and aggregating each can become expensive. Consider adding pagination or limiting query scope to enhance performance and responsiveness.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0d0b57c and 9b8b938.

📒 Files selected for processing (6)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/CopyEnvIdButton.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx (1 hunks)
  • packages/api/src/router/environment-page/environment-page.ts (1 hunks)
  • packages/api/src/router/environment-page/overview.ts (1 hunks)
  • packages/api/src/router/environment.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_components/EnvironmentTabs.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.

  • packages/api/src/router/environment-page/environment-page.ts
  • packages/api/src/router/environment.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/CopyEnvIdButton.tsx
  • packages/api/src/router/environment-page/overview.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx
🧬 Code Definitions (1)
packages/api/src/router/environment-page/environment-page.ts (1)
packages/api/src/router/environment-page/overview.ts (1) (1)
  • overviewRouter (124-191)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (6)
packages/api/src/router/environment-page/environment-page.ts (1)

1-6: Clean router organization structure.

The code follows a modular approach by creating a dedicated router for environment page functionality. The modular organization enhances maintainability by separating concerns.

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

42-42: Good integration of the new page router.

The environment router is properly extended with the new page router, maintaining a clean hierarchy and organization of API endpoints.

Also applies to: 49-49

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx (1)

37-40: Guard against division by zero is implemented properly.

The code includes a fallback to prevent division by zero by using (stats.deployments.total || 1) as the denominator. This is a good practice.

packages/api/src/router/environment-page/overview.ts (3)

140-144: Verify correct scope for deployments.

The code fetches deployments by systemId instead of restricting them directly by environmentId. If your intention is to retrieve only the deployments tied to this environment, consider filtering on the environment’s own ID or verifying it’s correct to use system-level deployments here.


59-90:

❓ Verification inconclusive

Validate ordering logic in subquery.

The selectDistinctOn(...) approach relies on the ordering by job.createdAt and deploymentVersion.createdAt. Confirm that this ordering always yields the desired “latest” job for each resource. If job creation time ties are possible, you may need additional sorting columns or constraints.


🏁 Script executed:

#!/bin/bash
# Description: Check if the DB enforces a unique or consistent ordering for job creation times.

# Example approach: Inspect constraints or indexes on the job and deploymentVersion tables
# (within your DB migrations). If no constraints exist, consider adding tie-breakers.
fd "migrations" --type f --exec cat {} | rg "constraint|index|job|deploymentVersion"

Length of output: 86


Ordering Logic Verification for Latest Job Query

The current subquery orders by job.createdAt and deploymentVersion.createdAt to pick the latest job per resource. However, our verification script did not reveal any database-level constraints or indexes to ensure these timestamps are unique. Please manually verify that, under tie conditions (e.g., identical creation times), this ordering always returns the desired result. If tie scenarios are possible, consider adding additional sorting criteria (such as a unique identifier) to consistently determine the "latest" entry.


109-112:

❓ Verification inconclusive

Double-check negative case for notDeployed.

notDeployed = numResources - successful - failed - inProgress - pending could become negative if unexpected counts arise (e.g., if a resource had multiple statuses in edge cases). While current logic might avoid double-counting, ensure that partial or invalid job statuses cannot cause an underflow.


🏁 Script executed:

#!/bin/bash
# Description: Look for potential edge cases where multiple statuses could be assigned to the same resource.

cat <<EOF > check_multi_status.py
resources_with_multi_status = []
# Hypothetical check: We'll parse the job statuses for each resource on the same deployment
# to see if they have more than one final status. This is just an example outline.
print("No actual code to run here, but you can adapt for verifying multi-status edge cases.")
EOF
python check_multi_status.py

Length of output: 121


Review Action: Verify Underflow Handling in notDeployed Calculation

The current subtraction:

const notDeployed = numResources - successful - failed - inProgress - pending;

could yield a negative value if some resources are accidentally assigned multiple statuses. Although our initial check (using a placeholder script) didn't reveal a concrete edge case, it's important to confirm that:

  • Multiple statuses per resource are not possible. If they are, the calculation may underflow.
  • A safeguard is in place. For example, consider using:
    const notDeployed = Math.max(0, numResources - (successful + failed + inProgress + pending));
    or add a similar guard to ensure that notDeployed never falls below zero.

Please verify this logic against your domain's constraints and update the logic accordingly if overlapping statuses are possible.

Comment on lines 133 to 139
.query(async ({ ctx, input }) => {
const environment = await ctx.db
.select()
.from(SCHEMA.environment)
.where(eq(SCHEMA.environment.id, input))
.then(takeFirst);

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 case when environment is not found.

If no matching environment is found in the DB, environment might be undefined, and subsequent property accesses (e.g. environment.systemId) can cause runtime errors. Consider early-returning a 404 or error message if environment is null.

+ if (!environment) {
+   throw new TRPCError({ code: 'NOT_FOUND', message: 'Environment not found' });
+ }
📝 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
.query(async ({ ctx, input }) => {
const environment = await ctx.db
.select()
.from(SCHEMA.environment)
.where(eq(SCHEMA.environment.id, input))
.then(takeFirst);
.query(async ({ ctx, input }) => {
const environment = await ctx.db
.select()
.from(SCHEMA.environment)
.where(eq(SCHEMA.environment.id, input))
.then(takeFirst);
if (!environment) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Environment not found' });
}

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

🧹 Nitpick comments (8)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/CopyEnvIdButton.tsx (3)

12-18: Consider adding error handling for clipboard operations.

The navigator.clipboard.writeText() returns a Promise that should be handled to catch potential errors that might occur during clipboard operations, especially in environments with restricted permissions.

-  const copyEnvironmentId = () => {
-    navigator.clipboard.writeText(environmentId);
-    toast.success("Environment ID copied", {
-      description: environmentId,
-      duration: 2000,
-    });
-  };
+  const copyEnvironmentId = () => {
+    navigator.clipboard.writeText(environmentId)
+      .then(() => {
+        toast.success("Environment ID copied", {
+          description: environmentId,
+          duration: 2000,
+        });
+      })
+      .catch(err => {
+        toast.error("Failed to copy to clipboard", {
+          description: "Please try again or copy manually",
+          duration: 2000,
+        });
+      });
+  };

19-32: Button implementation is good, consider enhancing accessibility.

The button implementation prevents default behavior and has a title attribute for tooltips. However, adding an aria-label would improve accessibility for screen readers.

    <Button
      variant="ghost"
      size="icon"
      className="h-5 w-5 rounded-full hover:bg-neutral-800/50"
      onClick={(e) => {
        e.preventDefault();
        copyEnvironmentId();
      }}
      title="Copy environment ID"
+     aria-label="Copy environment ID"
    >

1-33: Consider adding a browser compatibility check for the Clipboard API.

The Clipboard API might not be supported in all browsers. A fallback mechanism would improve reliability.

"use client";

import React from "react";
import { IconCopy } from "@tabler/icons-react";

import { Button } from "@ctrlplane/ui/button";
import { toast } from "@ctrlplane/ui/toast";

export const CopyEnvIdButton: React.FC<{
  environmentId: string;
}> = ({ environmentId }) => {
+  const isClipboardSupported = () => {
+    return !!navigator.clipboard;
+  };
+
+  const fallbackCopyToClipboard = (text: string) => {
+    // Create a temporary textarea element
+    const textArea = document.createElement("textarea");
+    textArea.value = text;
+    
+    // Make it invisible
+    textArea.style.position = "fixed";
+    textArea.style.opacity = "0";
+    
+    document.body.appendChild(textArea);
+    textArea.select();
+    
+    try {
+      // Execute the copy command
+      const successful = document.execCommand("copy");
+      if (successful) {
+        toast.success("Environment ID copied", {
+          description: environmentId,
+          duration: 2000,
+        });
+      } else {
+        throw new Error("Copy command failed");
+      }
+    } catch (err) {
+      toast.error("Failed to copy to clipboard", {
+        description: "Please try again or copy manually",
+        duration: 2000,
+      });
+    } finally {
+      document.body.removeChild(textArea);
+    }
+  };

  const copyEnvironmentId = () => {
+    if (isClipboardSupported()) {
      navigator.clipboard.writeText(environmentId)
+        .then(() => {
+          toast.success("Environment ID copied", {
+            description: environmentId,
+            duration: 2000,
+          });
+        })
+        .catch(() => {
+          fallbackCopyToClipboard(environmentId);
+        });
+    } else {
+      fallbackCopyToClipboard(environmentId);
+    }
-    toast.success("Environment ID copied", {
-      description: environmentId,
-      duration: 2000,
-    });
  };
  
  // Rest of the component remains the same

This implementation provides a fallback using the older document.execCommand("copy") method which has broader browser support.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx (5)

19-25: Consider defining colors with semantic meaning

The list of colors is currently defined without context. Consider using semantic naming to improve readability and maintainability.

-const colors = [
-  "bg-green-500",
-  "bg-blue-500",
-  "bg-red-500",
-  "bg-purple-500",
-  "bg-amber-500",
-];
+const versionColors = {
+  current: "bg-green-500",
+  previous: "bg-blue-500",
+  outdated: "bg-red-500",
+  experimental: "bg-purple-500",
+  unknown: "bg-amber-500",
+};
+
+// When version type isn't known, fall back to this array
+const fallbackColors = Object.values(versionColors);

40-46: Consider improving key usage in map function

Using index as a key in React's map function can lead to rendering issues when the list changes. Try to use a more stable identifier if available.

-{Object.values(versionDistro).map(({ percentage }, index) => (
+{Object.entries(versionDistro).map(([version, { percentage }], index) => (
  <div
-   key={index}
+   key={version}
    className={`h-full ${colors[index % colors.length]}`}
    style={{ width: `${percentage * 100}%` }}
  />
))}

117-149: Add empty state handling for deployments list

The component doesn't handle the case when no deployments are available. Consider adding an empty state message.

export const DeploymentTelemetryTable: React.FC<{
  deployments: SCHEMA.Deployment[];
}> = ({ deployments }) => {
  return (
    <div>
      <h4 className="mb-3 text-sm font-medium text-neutral-300">
        Deployment Versions
      </h4>
+     {deployments.length === 0 ? (
+       <div className="flex h-32 items-center justify-center rounded-lg border border-neutral-800/50 bg-neutral-900/50">
+         <p className="text-sm text-neutral-400">No deployments found</p>
+       </div>
+     ) : (
      <div className="overflow-hidden rounded-lg border border-neutral-800/50">
        <Table>
          <TableHeader>
            <TableRow className="border-b border-neutral-800/50 hover:bg-transparent">
              <TableHead className="w-[200px] py-3 font-medium text-neutral-400">
                Deployments
              </TableHead>
              <TableHead className="w-[300px] py-3 font-medium text-neutral-400">
                Current Distribution
              </TableHead>
              <TableHead className="w-[150px] py-3 font-medium text-neutral-400">
                Desired Version
              </TableHead>
              <TableHead className="py-3 text-right font-medium text-neutral-400">
                Deployment Status
              </TableHead>
            </TableRow>
          </TableHeader>
          <TableBody>
            {deployments.map((deployment) => (
              <DeploymentRow key={deployment.id} deployment={deployment} />
            ))}
          </TableBody>
        </Table>
      </div>
+     )}
    </div>
  );
};

67-73: Consider making deployment status indicator dynamic

The green indicator dot appears to be hardcoded, but should likely reflect the actual deployment status.

<div className="flex items-center gap-2">
-  <div className="h-2 w-2 rounded-full bg-green-500"></div>
+  <div className={`h-2 w-2 rounded-full ${
+    deployment.status === "deployed" ? "bg-green-500" : 
+    deployment.status === "pending" ? "bg-amber-500" : 
+    deployment.status === "failed" ? "bg-red-500" : 
+    "bg-neutral-500"
+  }`}></div>
  <span className="text-sm text-neutral-200">{deployment.name}</span>
  <span className="text-xs text-neutral-400">
    ({isLoading ? "-" : resourceCount})
  </span>
</div>

54-63: Consider extracting telemetry data transformation to a utility function

To improve code maintainability, consider extracting the telemetry data transformation logic to a separate utility function.

For example, create a utility function:

const processTelemetryData = (telemetry: any | undefined, isLoading: boolean) => {
  return {
    resourceCount: telemetry?.resourceCount ?? 0,
    versionDistro: telemetry?.versionDistro ?? {},
    isLoading,
    isEmpty: Object.entries(telemetry?.versionDistro ?? {}).length === 0
  };
};

Then use it in your component:

- const resourceCount = telemetry?.resourceCount ?? 0;
- const versionDistro = telemetry?.versionDistro ?? {};
+ const { resourceCount, versionDistro, isEmpty } = processTelemetryData(telemetry, isLoading);

This makes the component more readable and the data transformation logic more testable.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9b8b938 and 5b1ade0.

📒 Files selected for processing (7)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/CopyEnvIdButton.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx (1 hunks)
  • packages/api/src/router/environment-page/environment-page.ts (1 hunks)
  • packages/api/src/router/environment-page/overview/get-deployment-stats.ts (1 hunks)
  • packages/api/src/router/environment-page/overview/get-version-distro.ts (1 hunks)
  • packages/api/src/router/environment-page/overview/router.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/api/src/router/environment-page/environment-page.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.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)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/CopyEnvIdButton.tsx
  • packages/api/src/router/environment-page/overview/get-deployment-stats.ts
  • packages/api/src/router/environment-page/overview/get-version-distro.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx
  • packages/api/src/router/environment-page/overview/router.ts
🧬 Code Definitions (1)
packages/api/src/router/environment-page/overview/router.ts (2)
packages/api/src/router/environment-page/overview/get-deployment-stats.ts (1) (1)
  • getDeploymentStats (27-108)
packages/api/src/router/environment-page/overview/get-version-distro.ts (1) (1)
  • getVersionDistro (8-59)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
🔇 Additional comments (8)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/CopyEnvIdButton.tsx (2)

1-8: Imports and dependencies look good!

The component correctly uses "use client" directive and imports necessary dependencies.


9-11: Props type definition is clear and appropriate.

The component accepts a single string prop for the environment ID, which is properly typed.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx (1)

27-49: Well-implemented visualization component with proper state handling

The DistroBar component handles loading and empty states appropriately, providing a clean visual representation of version distribution.

packages/api/src/router/environment-page/overview/get-deployment-stats.ts (2)

8-25: Status arrays look appropriate.

The definitions of failureStatuses, pendingStatuses, and the combined deployedStatuses array are clear and typed. They provide a concise way to track relevant job states.


95-107: Overall structure is well-organized.

The final return object is straightforward, clearly partitioning job status counts and the number of non-deployed resources. Nice approach in giving a holistic view of deployment stats.

packages/api/src/router/environment-page/overview/get-version-distro.ts (1)

14-35: Good distinct-on pattern for retrieving latest versions.

Using .selectDistinctOn provides a clean approach to filtering the latest version tag per resource based on job creation time. This subquery construction is efficient for pulling the most recent data.

packages/api/src/router/environment-page/overview/router.ts (2)

39-51: Graceful short-circuit for null resource filter.

Returning default values when environment.resourceFilter is null avoids unnecessary computation. This is a neat early exit pattern.


120-120: Duplicate environment check needed for telemetry path.

Similar to the check above, ensure environment is not undefined prior to referencing environment.resourceFilter. Otherwise, a missing environment record may cause a runtime error here as well.

Comment on lines +55 to +59
const { data: telemetry, isLoading } =
api.environment.page.overview.telemetry.byDeploymentId.useQuery(
{ environmentId, deploymentId: deployment.id },
{ refetchInterval: 60_000 },
);
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

Add error handling for API query

The current implementation doesn't handle API errors. Consider adding error handling to improve user experience.

- const { data: telemetry, isLoading } =
+ const { data: telemetry, isLoading, error } =
    api.environment.page.overview.telemetry.byDeploymentId.useQuery(
      { environmentId, deploymentId: deployment.id },
      { refetchInterval: 60_000 },
    );

+ // Add after line 63
+ const hasError = !!error;

Then update the UI to show an error state:

// At line 81-83
{isLoading
  ? "Loading distribution..."
- : "No resources deployed"}
+ : hasError 
+   ? "Failed to load data" 
+   : "No resources deployed"}
📝 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 { data: telemetry, isLoading } =
api.environment.page.overview.telemetry.byDeploymentId.useQuery(
{ environmentId, deploymentId: deployment.id },
{ refetchInterval: 60_000 },
);
const { data: telemetry, isLoading, error } =
api.environment.page.overview.telemetry.byDeploymentId.useQuery(
{ environmentId, deploymentId: deployment.id },
{ refetchInterval: 60_000 },
);
// ... (other code)
// Add after line 63
const hasError = !!error;
// ... (other UI code)
{isLoading
? "Loading distribution..."
: hasError
? "Failed to load data"
: "No resources deployed"}

Comment on lines 77 to 93
const statsByJobStatus = await db
.select({
successful: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} = ${JobStatus.Successful} THEN 1 ELSE NULL END`,
),
inProgress: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} = ${JobStatus.InProgress} THEN 1 ELSE NULL END`,
),
pending: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} IN (${sql.raw(pendingStatuses.map((s) => `'${s}'`).join(", "))}) THEN 1 ELSE NULL END`,
),
failed: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} IN (${sql.raw(failureStatuses.map((s) => `'${s}'`).join(", "))}) THEN 1 ELSE NULL END`,
),
})
.from(latestJobsPerResourceAndDeploymentSubquery)
.then(takeFirst);
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

Prevent potential runtime error if no rows are returned.

When using .then(takeFirst), if the subquery returns zero rows, statsByJobStatus will be undefined. The subsequent object destructuring could throw an error. Consider applying a fallback or guard statement to handle the case when no rows exist:

-    .then(takeFirst);
+    .then((row) => row ?? { successful: 0, inProgress: 0, pending: 0, failed: 0 });
📝 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 statsByJobStatus = await db
.select({
successful: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} = ${JobStatus.Successful} THEN 1 ELSE NULL END`,
),
inProgress: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} = ${JobStatus.InProgress} THEN 1 ELSE NULL END`,
),
pending: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} IN (${sql.raw(pendingStatuses.map((s) => `'${s}'`).join(", "))}) THEN 1 ELSE NULL END`,
),
failed: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} IN (${sql.raw(failureStatuses.map((s) => `'${s}'`).join(", "))}) THEN 1 ELSE NULL END`,
),
})
.from(latestJobsPerResourceAndDeploymentSubquery)
.then(takeFirst);
const statsByJobStatus = await db
.select({
successful: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} = ${JobStatus.Successful} THEN 1 ELSE NULL END`,
),
inProgress: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} = ${JobStatus.InProgress} THEN 1 ELSE NULL END`,
),
pending: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} IN (${sql.raw(pendingStatuses.map((s) => `'${s}'`).join(", "))}) THEN 1 ELSE NULL END`,
),
failed: count(
sql`CASE WHEN ${latestJobsPerResourceAndDeploymentSubquery.status} IN (${sql.raw(failureStatuses.map((s) => `'${s}'`).join(", "))}) THEN 1 ELSE NULL END`,
),
})
.from(latestJobsPerResourceAndDeploymentSubquery)
.then((row) => row ?? { successful: 0, inProgress: 0, pending: 0, failed: 0 });

Comment on lines 51 to 59
const total = _.sumBy(versionCounts, (v) => v.count);

return Object.fromEntries(
versionCounts.map((v) => [
v.tag,
{ count: v.count, percentage: v.count / total },
]),
);
};
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

Avoid division by zero when total is empty.

If no version rows are found, total becomes zero, resulting in a potential NaN or Infinity when calculating (v.count / total). Safeguard this calculation with a conditional check:

-    versionCounts.map((v) => [
-      v.tag,
-      { count: v.count, percentage: v.count / total },
-    ]),
+    versionCounts.map((v) => {
+      const percentage = total > 0 ? v.count / total : 0;
+      return [
+        v.tag,
+        { count: v.count, percentage },
+      ];
+    }),
📝 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 total = _.sumBy(versionCounts, (v) => v.count);
return Object.fromEntries(
versionCounts.map((v) => [
v.tag,
{ count: v.count, percentage: v.count / total },
]),
);
};
const total = _.sumBy(versionCounts, (v) => v.count);
return Object.fromEntries(
- versionCounts.map((v) => [
- v.tag,
- { count: v.count, percentage: v.count / total },
- ]),
+ versionCounts.map((v) => {
+ const percentage = total > 0 ? v.count / total : 0;
+ return [
+ v.tag,
+ { count: v.count, percentage },
+ ];
+ }),
);
};

Comment on lines +28 to +33
const environment = await ctx.db
.select()
.from(SCHEMA.environment)
.where(eq(SCHEMA.environment.id, input))
.then(takeFirst);

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

Add check for a non-existent environment.

If the ctx.db query returns no rows, environment will be undefined. Accessing environment.systemId or environment.resourceFilter would throw an error. Ensure the code verifies that environment is defined before proceeding:

- .then(takeFirst);
+ .then((env) => {
+   if (!env) {
+     throw new Error(`Environment with id: ${input} not found`);
+   }
+   return env;
+ });
📝 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 environment = await ctx.db
.select()
.from(SCHEMA.environment)
.where(eq(SCHEMA.environment.id, input))
.then(takeFirst);
const environment = await ctx.db
.select()
.from(SCHEMA.environment)
.where(eq(SCHEMA.environment.id, input))
.then((env) => {
if (!env) {
throw new Error(`Environment with id: ${input} not found`);
}
return env;
});

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

♻️ Duplicate comments (2)
packages/api/src/router/environment-page/overview/router.ts (2)

28-33: ⚠️ Potential issue

Add check for a non-existent environment.

If the ctx.db query returns no rows, environment will be undefined. Accessing environment.systemId or environment.resourceFilter would throw an error. Ensure the code verifies that environment is defined before proceeding.

- .then(takeFirst);
+ .then((env) => {
+   if (!env) {
+     throw new Error(`Environment with id: ${input} not found`);
+   }
+   return env;
+ });

103-108: ⚠️ Potential issue

Add validation for environment existence.

Similar to the previous issue, ensure that the environment exists before accessing its properties.

- .then(takeFirst);
+ .then((env) => {
+   if (!env) {
+     throw new Error(`Environment with id: ${environmentId} not found`);
+   }
+   return env;
+ });
🧹 Nitpick comments (1)
packages/api/src/router/environment-page/overview/version-distro.ts (1)

16-67: Consider handling potential division by zero.

When calculating percentages, verify that the total is not zero to prevent potential division by zero errors.

  const total = _.sumBy(versionCounts, (v) => v.count);

  return Object.fromEntries(
    versionCounts.map((v) => [
      v.tag,
-     { count: v.count, percentage: v.count / total },
+     { count: v.count, percentage: total > 0 ? v.count / total : 0 },
    ]),
  );
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5b1ade0 and 6ff784e.

📒 Files selected for processing (5)
  • packages/api/src/router/environment-page/overview/deployment-stats.ts (1 hunks)
  • packages/api/src/router/environment-page/overview/router.ts (1 hunks)
  • packages/api/src/router/environment-page/overview/version-distro.ts (1 hunks)
  • packages/api/src/router/environment-page/router.ts (1 hunks)
  • packages/api/src/router/environment.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/router/environment.ts
🧰 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.

  • packages/api/src/router/environment-page/router.ts
  • packages/api/src/router/environment-page/overview/version-distro.ts
  • packages/api/src/router/environment-page/overview/deployment-stats.ts
  • packages/api/src/router/environment-page/overview/router.ts
🧬 Code Definitions (1)
packages/api/src/router/environment-page/overview/router.ts (2)
packages/api/src/router/environment-page/overview/deployment-stats.ts (1) (1)
  • getDeploymentStats (36-117)
packages/api/src/router/environment-page/overview/version-distro.ts (1) (1)
  • getVersionDistro (16-67)
🔇 Additional comments (4)
packages/api/src/router/environment-page/router.ts (1)

1-6: LGTM! Clear and well-structured router implementation.

The implementation creates a clean hierarchical router structure by incorporating the overviewRouter as a sub-route, following good modularization practices.

packages/api/src/router/environment-page/overview/deployment-stats.ts (1)

36-117: LGTM! Well-structured query implementation with comprehensive statistics.

The implementation efficiently retrieves deployment statistics with appropriate filtering and aggregation. The calculation of various deployment status counts is done in a clean and maintainable way.

packages/api/src/router/environment-page/overview/router.ts (2)

120-130: LGTM! Good conditional handling and resource filtering.

The implementation appropriately handles the case where resourceFilter is null and creates a well-structured resource selector by combining environment and deployment filters.


131-150: LGTM! Effective resource querying and version distribution calculation.

The implementation efficiently retrieves resources matching the specified criteria and properly calculates version distribution for the resources.

Comment on lines +109 to +114
const deploymentPromise = ctx.db
.select()
.from(SCHEMA.deployment)
.where(eq(SCHEMA.deployment.id, deploymentId))
.then(takeFirst);

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

Validate deployment existence.

Check that the deployment exists before accessing its properties to prevent runtime errors.

- .then(takeFirst);
+ .then((deployment) => {
+   if (!deployment) {
+     throw new Error(`Deployment with id: ${deploymentId} not found`);
+   }
+   return deployment;
+ });
📝 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 deploymentPromise = ctx.db
.select()
.from(SCHEMA.deployment)
.where(eq(SCHEMA.deployment.id, deploymentId))
.then(takeFirst);
const deploymentPromise = ctx.db
.select()
.from(SCHEMA.deployment)
.where(eq(SCHEMA.deployment.id, deploymentId))
.then((deployment) => {
if (!deployment) {
throw new Error(`Deployment with id: ${deploymentId} not found`);
}
return deployment;
});

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

🔭 Outside diff range comments (1)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy/drawer/EnvironmentPolicyDrawer.tsx (1)

73-87: 💡 Verification agent

🧩 Analysis chain

Enhanced URL parameter management

Allowing the setEnvironmentPolicyId function to also update the tab parameter in the URL is a good improvement. Before merging, confirm that the tab value is always valid (i.e., it matches the enum). A simple validation check can ensure the app won’t redirect to an invalid state.

You can verify usage with:

Then inspect each call site to confirm it passes a valid EnvironmentPolicyDrawerTab.


---

🏁 Script executed:

```shell
#!/bin/bash
rg "setEnvironmentPolicyId"

Length of output: 1289


Action Required: Validate tab Value in URL Parameter Updates

Great job integrating the URL parameter management improvements. However, before merging, please ensure that the tab parameter is validated against the allowed values (i.e., the EnvironmentPolicyDrawerTab enum). This will prevent the application from redirecting to an invalid state if an unexpected value is provided. Here are the key points:

  • In apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy/drawer/EnvironmentPolicyDrawer.tsx (lines 73–87), consider adding an explicit check such as:
    if (tab != null && !Object.values(EnvironmentPolicyDrawerTab).includes(tab)) {
      console.error('Invalid tab value:', tab);
      return;
    }
  • The grep output confirms that all call sites (including those in EnvFlowBuilder.tsx and PoliciesPageContent.tsx) pass a value or leave the tab parameter undefined. However, an extra guard in setEnvironmentPolicyId will help ensure stability even if an invalid value is inadvertently passed or introduced in the future.

Please address this validation before merging.

🧹 Nitpick comments (2)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment/drawer/EnvironmentDrawer.tsx (1)

101-101: Potential naming confusion

Currently, the variable name isUsingOverridePolicy is being assigned to environment?.policy.isDefaultPolicy. This feels inverted and may cause confusion, as "override policy" usually suggests the opposite of "default policy." Consider renaming the variable to something like isUsingDefaultPolicy for clarity and consistency.

- const isUsingOverridePolicy = environment?.policy.isDefaultPolicy ?? false;
+ const isUsingDefaultPolicy = environment?.policy.isDefaultPolicy ?? false;
apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy/drawer/EnvironmentPolicyDrawer.tsx (1)

66-66: Minor improvement: Provide a more descriptive export name

Exporting param is helpful for reusability. However, for better clarity, consider renaming it to environmentPolicyParam to be self-documenting.

- export const param = "environment_policy_id";
+ export const environmentPolicyParam = "environment_policy_id";
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff784e and aed94c0.

📒 Files selected for processing (6)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment/drawer/EnvironmentDrawer.tsx (2 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy/drawer/EnvironmentPolicyDrawer.tsx (2 hunks)
  • apps/webservice/src/app/urls.ts (2 hunks)
  • packages/api/src/router/environment.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/webservice/src/app/urls.ts
  • packages/api/src/router/environment.ts
🧰 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/environment/drawer/EnvironmentDrawer.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy/drawer/EnvironmentPolicyDrawer.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx
🧠 Learnings (1)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy/drawer/EnvironmentPolicyDrawer.tsx (1)
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#188
File: apps/webservice/src/app/[workspaceSlug]/_components/environment-drawer/EnvironmentDrawer.tsx:139-154
Timestamp: 2025-03-19T18:38:39.246Z
Learning: In `EnvironmentDrawer.tsx`, for the `EnvironmentDrawer` component, when there are only a few `TabButton` instances (e.g., 3), it's acceptable to define them individually without extracting them into a configuration to reduce repetition.
🧬 Code Definitions (2)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (2)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/_policies/page.tsx (1) (1)
  • PoliciesPage (3-13)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (1) (1)
  • PoliciesPageContent (44-522)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (2)
apps/webservice/src/app/urls.ts (1) (1)
  • urls (197-197)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy/drawer/EnvironmentPolicyDrawer.tsx (2) (2)
  • param (66-66)
  • useEnvironmentPolicyDrawer (67-98)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
🔇 Additional comments (4)
apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment/drawer/EnvironmentDrawer.tsx (1)

227-234:

❓ Verification inconclusive

Double-check override UI condition

Here, the <UpdateOverridePolicy> component is conditionally rendered using environment.policy.isDefaultPolicy. If the component is meant to configure overrides, ensure that showing it only for the "defaultPolicy" state is correct and aligns with the intended logic.

Please verify that the logic correctly matches the new policy model (i.e., that "defaultPolicy" indeed controls when the override UX is displayed) by searching the codebase for references to UpdateOverridePolicy:


🏁 Script executed:

#!/bin/bash
rg "UpdateOverridePolicy" -A 5

Length of output: 5911


Double-check override UI condition – Verify Policy Trigger

The <UpdateOverridePolicy> component is rendered only when environment.policy.isDefaultPolicy is true. Please confirm that this condition aligns with the new policy model: specifically, that the override UI should only appear when the environment’s policy is in its default state. Ensure that this logic is consistent with the intended behavior of configuring policy overrides.

  • Confirm that the default policy state is the correct trigger for displaying override options.
  • Verify that any related changes in the new policy model don’t require additional conditions.
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/page.tsx (1)

1-12: Looks good and more explicit

Switching from a redirect-based approach to a notFound() response if the environment does not exist is a more explicit and user-friendly pattern. This ensures clarity about valid and invalid resources.

apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/policies/PoliciesPageContent.tsx (2)

483-500: Logic mismatch for "Release Interval"

This logic still displays environmentPolicy.releaseWindows.length under "Release Interval," which may be misleading if the intent is to show an interval of time rather than just how many release windows exist. This comment was also raised in a past review.


66-69:

❓ Verification inconclusive

Verify navigation logic for environment policy editing

Here, depending on hasParentPolicy, you either push a new URL or set the local environmentPolicyId. Confirm that this fully covers expected scenarios (e.g., does the system handle cases when environmentPolicy.id is invalid or missing?). Also ensure that end-users are routed to the correct policy editing experience.

Run the following to check references of onConfigurePolicyClick and see how it’s used throughout:


🏁 Script executed:

#!/bin/bash
rg "onConfigurePolicyClick" -A 5

Length of output: 6871


Double-check navigation behavior for policy editing flows

The function currently handles navigation by:

  • Pushing an external URL with a tab parameter when hasParentPolicy is true.
  • Invoking setEnvironmentPolicyId(environmentPolicy.id, tab) when hasParentPolicy is false.

Please verify the following:

  • Validation of environmentPolicy.id: Ensure that when there is no parent policy, environmentPolicy.id is always valid. If this ID might be missing or invalid, consider adding proper error handling or a fallback mechanism.
  • Consistent navigation: Confirm that appending the tab value to externalPolicyUrl correctly routes users to the designated policy editing experience across all UI triggers (e.g., Approval, Concurrency, Management, DeploymentVersionChannels, Rollout).

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

🧹 Nitpick comments (6)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx (6)

41-47: Consider using a more semantic key for list items

Using an array index as a key can cause issues with component identity when the list changes. Consider using a more stable identifier if available:

{Object.values(versionDistro).map(({ percentage }, index) => (
  <div
-   key={index}
+   key={`version-distro-${index}`}
    className={`h-full ${colors[index % colors.length]}`}
    style={{ width: `${percentage * 100}%` }}
  />
))}

71-72: Improve status indicator consistency

The green dot indicator is hardcoded, but it should likely reflect the deployment's actual status for consistency with the status shown in the deployment status column.

<div className="flex items-center gap-2">
-  <div className="h-2 w-2 rounded-full bg-green-500"></div>
+  <div className={cn("h-2 w-2 rounded-full", {
+    "bg-green-500": telemetry?.desiredVersion?.status === "Deployed",
+    "bg-blue-500": telemetry?.desiredVersion?.status === "Deploying",
+    "bg-red-500": telemetry?.desiredVersion?.status === "Failed",
+    "bg-amber-500": telemetry?.desiredVersion?.status === "Pending Approval",
+    "bg-neutral-500": !telemetry?.desiredVersion?.status
+  })}></div>
  <span className="text-sm text-neutral-200">{deployment.name}</span>

114-131: Refactor duplicated status styling logic

The status styling logic is duplicated between the background and the dot indicator. Consider extracting this into a utility function or variable to avoid duplication:

+ const getStatusStyles = (status?: string) => ({
+   background: {
+     "bg-green-500/10 text-green-400": status === "Deployed",
+     "bg-blue-500/10 text-blue-400": status === "Deploying",
+     "bg-red-500/10 text-red-400": status === "Failed",
+     "bg-amber-500/10 text-amber-400": status === "Pending Approval",
+   },
+   dot: {
+     "bg-green-500": status === "Deployed",
+     "bg-blue-500": status === "Deploying",
+     "bg-red-500": status === "Failed",
+     "bg-amber-500": status === "Pending Approval",
+   }
+ });
+
+ const statusStyles = getStatusStyles(desiredVersion?.status);

// Then use it in the JSX
<span
  className={cn(
    "inline-flex items-center gap-1.5 rounded px-2 py-1 text-xs font-medium",
-   {
-     "bg-green-500/10 text-green-400":
-       desiredVersion.status === "Deployed",
-     "bg-blue-500/10 text-blue-400":
-       desiredVersion.status === "Deploying",
-     "bg-red-500/10 text-red-400":
-       desiredVersion.status === "Failed",
-     "bg-amber-500/10 text-amber-400":
-       desiredVersion.status === "Pending Approval",
-   },
+   statusStyles.background,
  )}
>
  <div
    className={cn("h-1.5 w-1.5 rounded-full", 
-   {
-     "bg-green-500": desiredVersion.status === "Deployed",
-     "bg-blue-500": desiredVersion.status === "Deploying",
-     "bg-red-500": desiredVersion.status === "Failed",
-     "bg-amber-500": desiredVersion.status === "Pending Approval",
-   }
+   statusStyles.dot
    )}
  />

146-175: Add accessibility attributes to the table

Improve accessibility by adding appropriate ARIA attributes to the table:

<h4 className="mb-3 text-sm font-medium text-neutral-300">
  Deployment Versions
</h4>
<div className="overflow-hidden rounded-lg border border-neutral-800/50">
-  <Table>
+  <Table aria-label="Deployment telemetry information">
    <TableHeader>
      <TableRow className="border-b border-neutral-800/50 hover:bg-transparent">
        <TableHead className="w-[200px] py-3 font-medium text-neutral-400">
          Deployments
        </TableHead>
        {/* ... other headers ... */}
      </TableRow>
    </TableHeader>
    <TableBody>
      {deployments.length === 0 ? (
+        <TableRow>
+          <TableCell colSpan={4} className="py-4 text-center text-neutral-400">
+            No deployments available
+          </TableCell>
+        </TableRow>
+      ) : (
        deployments.map((deployment) => (
          <DeploymentRow key={deployment.id} deployment={deployment} />
        ))
+      )}
    </TableBody>
  </Table>
</div>

89-99: Improve version distribution layout

The current layout might have issues displaying multiple versions properly. Consider adding a separator and ensuring proper spacing:

{Object.entries(versionDistro).map(([version, { percentage }], index) => {
  return (
    <div
      key={version}
-     className="flex items-center gap-1"
+     className="flex items-center gap-1 mr-2"
      style={{ width: `${percentage * 100}%` }}
    >
-     {version}
+     <span className="truncate">{version}</span>
+     <span className="whitespace-nowrap">{(percentage * 100).toFixed(1)}%</span>
+     {index < Object.entries(versionDistro).length - 1 && <span className="mx-1 text-neutral-600">|</span>}
    </div>
  );
})}

59-60:

❓ Verification inconclusive

Consider optimizing data fetching

The current approach fetches data for each deployment row separately. For better performance with many deployments, consider fetching all telemetry data at once in the parent component and passing it down:

// In DeploymentTelemetryTable
+ const { environmentId } = useParams<{ environmentId: string }>();
+ const { data: telemetryMap, isLoading: telemetryLoading, error: telemetryError } =
+   api.environment.page.overview.telemetry.byDeployments.useQuery(
+     { environmentId, deploymentIds: deployments.map(d => d.id) },
+     { refetchInterval: 60_000 },
+   );

// Then pass to DeploymentRow
<DeploymentRow 
  key={deployment.id} 
  deployment={deployment} 
+ telemetry={telemetryMap?.[deployment.id]} 
+ isLoading={telemetryLoading} 
+ hasError={!!telemetryError}
/>

🏁 Script executed:

#!/bin/bash
# Check if the required API endpoint exists
rg "byDeployments" -A 5 -B 5

Length of output: 28


Optimize Telemetry Data Fetching – Verify API Endpoint Availability

It appears that the current implementation triggers individual data fetches for each deployment row. To improve performance—especially when handling many deployments—consider consolidating telemetry data fetching at the parent component level and then passing the relevant data down to each DeploymentRow.

Before proceeding with this refactoring, please ensure that the expected API endpoint exists and functions as required. In particular, verify that the call to:

api.environment.page.overview.telemetry.byDeployments.useQuery

is available. If confirmed, you can refactor as follows:

// In DeploymentTelemetryTable.tsx
+ const { environmentId } = useParams<{ environmentId: string }>();
+ const {
+   data: telemetryMap,
+   isLoading: telemetryLoading,
+   error: telemetryError,
+ } = api.environment.page.overview.telemetry.byDeployments.useQuery(
+   { environmentId, deploymentIds: deployments.map(d => d.id) },
+   { refetchInterval: 60_000 },
+ );

<DeploymentRow 
  key={deployment.id} 
  deployment={deployment}
+ telemetry={telemetryMap?.[deployment.id]} 
+ isLoading={telemetryLoading} 
+ hasError={!!telemetryError}
/>

Action Items:

  • Verify API Endpoint: Confirm that the endpoint api.environment.page.overview.telemetry.byDeployments.useQuery exists and is implemented as expected.
  • Optimize Data Fetching: If the endpoint is available, proceed with the refactoring to fetch all telemetry data once in the parent.

Please manually verify the existence and behavior of this API endpoint before moving forward.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between aed94c0 and 869bd9b.

📒 Files selected for processing (3)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx (1 hunks)
  • packages/api/src/router/environment-page/overview/desired-version.ts (1 hunks)
  • packages/api/src/router/environment-page/overview/router.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.

  • packages/api/src/router/environment-page/overview/desired-version.ts
  • packages/api/src/router/environment-page/overview/router.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx
🧬 Code Definitions (1)
packages/api/src/router/environment-page/overview/router.ts (3)
packages/api/src/router/environment-page/overview/deployment-stats.ts (1) (1)
  • getDeploymentStats (36-117)
packages/api/src/router/environment-page/overview/version-distro.ts (1) (1)
  • getVersionDistro (16-67)
packages/api/src/router/environment-page/overview/desired-version.ts (1) (1)
  • getDesiredVersion (7-104)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (9)
apps/webservice/src/app/[workspaceSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx (1)

56-60: Add error handling for API query

The current implementation doesn't handle API errors. Consider adding error handling to improve user experience.

- const { data: telemetry, isLoading } =
+ const { data: telemetry, isLoading, error } =
    api.environment.page.overview.telemetry.byDeploymentId.useQuery(
      { environmentId, deploymentId: deployment.id },
      { refetchInterval: 60_000 },
    );

+ // Add after line 61
+ const hasError = !!error;

Then update the UI to show an error state:

// At line 84-86
{isLoading
  ? "Loading distribution..."
- : "No resources deployed"}
+ : hasError 
+   ? "Failed to load data" 
+   : "No resources deployed"}
packages/api/src/router/environment-page/overview/desired-version.ts (3)

13-36: Handle the null case for versionSelector.

If versionSelector is null, the where clause in the subsequent query might return unintended results or no rows. Verify that the upstream logic and callers can handle this scenario without errors.


53-55: Validate null or undefined versionSelector in matching condition.

SCHEMA.deploymentVersionMatchesCondition(db, versionChannelSelector) should gracefully handle null or undefined to avoid query failures. Confirm that it checks for these cases before constructing SQL conditions.


88-101: Confirm coverage for partial or mixed status scenarios.

When some resources have not triggered any jobs, but others have, the logic defaults to "Deploying". Make sure the calling code interprets the "Deploying" status correctly in partial/mixed scenarios where some resources have no associated jobs.

packages/api/src/router/environment-page/overview/router.ts (5)

29-34: Validate environment existence.

Ensure the code checks for a non-existent environment return value to prevent runtime errors when accessing environment.systemId later.


40-52: Good fallback for environments without filters.

Returning default deployment statistics when environment.resourceFilter is null helps avoid errors. This approach keeps the code simple and handles edge cases gracefully.


58-63: Efficiently exclude deleted resources.

Using isNull(SCHEMA.resource.deletedAt) prevents pseudo-deleted resources from affecting the statistics. This helps maintain accuracy in reporting.


110-115: Check for deployment existence.

Validate that the fetched deployment is defined and handle the potential null return to avoid accessing deployment.systemId if undefined.


143-149: Parallelize slow queries for performance.

Running getVersionDistro and getDesiredVersion in parallel via Promise.all is a good approach. It reduces the overall response time.

Comment on lines +168 to +171
{deployments.map((deployment) => (
<DeploymentRow key={deployment.id} deployment={deployment} />
))}
</TableBody>
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

Add empty state handling for deployments

Add handling for when there are no deployments to display:

<TableBody>
+ {deployments.length === 0 ? (
+   <TableRow>
+     <TableCell colSpan={4} className="text-center py-6 text-neutral-400">
+       No deployments available
+     </TableCell>
+   </TableRow>
+ ) : (
    {deployments.map((deployment) => (
      <DeploymentRow key={deployment.id} deployment={deployment} />
    ))}
+ )}
</TableBody>
📝 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
{deployments.map((deployment) => (
<DeploymentRow key={deployment.id} deployment={deployment} />
))}
</TableBody>
<TableBody>
{deployments.length === 0 ? (
<TableRow>
<TableCell colSpan={4} className="text-center py-6 text-neutral-400">
No deployments available
</TableCell>
</TableRow>
) : (
deployments.map((deployment) => (
<DeploymentRow key={deployment.id} deployment={deployment} />
))
)}
</TableBody>

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