-
Notifications
You must be signed in to change notification settings - Fork 5
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
base: main
Are you sure you want to change the base?
init view #395
Conversation
WalkthroughThe 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
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
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
Possibly related PRs
Poem
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this 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
📒 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
Ifstats.deployments.total
is 0, the expression(stats.deployments.successful / stats.deployments.total)
will trigger a division by zero, resulting inInfinity
orNaN
. 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 differentapprovalRequirement
values or dynamic release windows) can be easily supported. Also confirm that the static release window times from 2023 are intentional.
...eSlug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/page.tsx
Outdated
Show resolved
Hide resolved
export default function EnvironmentOverviewPage(props: { | ||
params: { | ||
workspaceSlug: string; | ||
systemSlug: string; | ||
environmentId: string; | ||
}>; | ||
}; |
There was a problem hiding this comment.
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.
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 | |
} |
There was a problem hiding this 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 likeresources/page.tsx
, this has been simplified to only includeenvironmentId
. 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 haveonClick={() => 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.
WhenresourceFilter.operator
is "and" or "or", each condition is matched by exact string comparison. Ifvalue
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
📒 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 existingbuildUrl
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 aPoliciesPageContent
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
, andTabsTrigger
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-levelPageHeader
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.
Declaringparams
as aPromise<{ ... }>
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
andformatTimeAgo
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 5Length 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 isnull
and formats 0 as"0s"
. In contrast, inDeploymentPerformance.tsx
, the version offormatDuration
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 bothformatDuration
andformatTimeAgo
.- 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.
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} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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)} |
...lug]/(app)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/layout.tsx
Show resolved
Hide resolved
Deployed {formatTimeAgo(deployment.deployedAt)} | ||
</p> | ||
</div> | ||
<div>{renderStatusBadge(deployment.status)}</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
const deploymentSuccess = Math.round( | ||
(stats.deployments.successful / stats.deployments.total) * 100, | ||
); |
There was a problem hiding this comment.
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.
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; |
There was a problem hiding this 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 asany
, 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
andformatTimeAgo
) while the importedDeploymentDetail
component usesformatDuration
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
📒 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.
).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> |
There was a problem hiding this comment.
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.
...[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx
Show resolved
Hide resolved
...[systemSlug]/(raw)/environments/[environmentId]/deployments/_components/DeploymentDetail.tsx
Show resolved
Hide resolved
export const EnvironmentDeploymentsPageContent: React.FC<{ | ||
environmentId: string; | ||
}> = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 | |
} |
...emSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx
Show resolved
Hide resolved
...emSlug]/(raw)/environments/[environmentId]/deployments/EnvironmentDeploymentsPageContent.tsx
Show resolved
Hide resolved
7a24e39
to
0d0b57c
Compare
There was a problem hiding this 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 suggestionEnhance 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 issueMissing 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 issueMissing 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 sequentialif
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:
- ResourceSummaryCards
- ResourceFilters
- ResourceGrid
- ResourceTable
- 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
📒 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 ifprops.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 displaysenvironmentPolicy.releaseWindows.length
under "Release Interval." However, the tooltip indicates it should show the minimum delay between releases, stored inminimumReleaseInterval
. 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 theenvironmentId
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 theurls
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.
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", | ||
}, | ||
], | ||
}, | ||
]; |
There was a problem hiding this comment.
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.
Co-authored-by: Justin Brooks <[email protected]>
There was a problem hiding this 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 fordeployedStatuses
.Including failure statuses under
deployedStatuses
can be confusing. Consider renaming to something more descriptive likerelevantStatuses
ortrackedStatuses
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 byjob.createdAt
, adding a composite index on(status, createdAt)
in thejob
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
📒 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 byenvironmentId
. 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 byjob.createdAt
anddeploymentVersion.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
anddeploymentVersion.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.pyLength of output: 121
Review Action: Verify Underflow Handling in
notDeployed
CalculationThe 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:
or add a similar guard to ensure thatconst notDeployed = Math.max(0, numResources - (successful + failed + inProgress + pending));notDeployed
never falls below zero.Please verify this logic against your domain's constraints and update the logic accordingly if overlapping statuses are possible.
...pp)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx
Show resolved
Hide resolved
...pp)/(deploy)/(raw)/systems/[systemSlug]/(raw)/environments/[environmentId]/overview/page.tsx
Outdated
Show resolved
Hide resolved
.query(async ({ ctx, input }) => { | ||
const environment = await ctx.db | ||
.select() | ||
.from(SCHEMA.environment) | ||
.where(eq(SCHEMA.environment.id, input)) | ||
.then(takeFirst); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
.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' }); | |
} |
There was a problem hiding this 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 sameThis 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 meaningThe 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 functionUsing 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 listThe 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 dynamicThe 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 functionTo 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
📒 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 handlingThe 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 combineddeployedStatuses
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 notundefined
prior to referencingenvironment.resourceFilter
. Otherwise, a missing environment record may cause a runtime error here as well.
...emSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx
Outdated
Show resolved
Hide resolved
const { data: telemetry, isLoading } = | ||
api.environment.page.overview.telemetry.byDeploymentId.useQuery( | ||
{ environmentId, deploymentId: deployment.id }, | ||
{ refetchInterval: 60_000 }, | ||
); |
There was a problem hiding this comment.
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.
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"} |
...emSlug]/(raw)/environments/[environmentId]/overview/_components/DeploymentTelemetryTable.tsx
Outdated
Show resolved
Hide resolved
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 }); |
const total = _.sumBy(versionCounts, (v) => v.count); | ||
|
||
return Object.fromEntries( | ||
versionCounts.map((v) => [ | ||
v.tag, | ||
{ count: v.count, percentage: v.count / total }, | ||
]), | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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 }, | |
+ ]; | |
+ }), | |
); | |
}; |
const environment = await ctx.db | ||
.select() | ||
.from(SCHEMA.environment) | ||
.where(eq(SCHEMA.environment.id, input)) | ||
.then(takeFirst); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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; | |
}); |
There was a problem hiding this 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 issueAdd check for a non-existent environment.
If the
ctx.db
query returns no rows,environment
will beundefined
. Accessingenvironment.systemId
orenvironment.resourceFilter
would throw an error. Ensure the code verifies thatenvironment
is defined before proceeding.- .then(takeFirst); + .then((env) => { + if (!env) { + throw new Error(`Environment with id: ${input} not found`); + } + return env; + });
103-108
:⚠️ Potential issueAdd 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
📒 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.
const deploymentPromise = ctx.db | ||
.select() | ||
.from(SCHEMA.deployment) | ||
.where(eq(SCHEMA.deployment.id, deploymentId)) | ||
.then(takeFirst); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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; | |
}); |
There was a problem hiding this 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 thetab
parameter in the URL is a good improvement. Before merging, confirm that thetab
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 UpdatesGreat job integrating the URL parameter management improvements. However, before merging, please ensure that the
tab
parameter is validated against the allowed values (i.e., theEnvironmentPolicyDrawerTab
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
andPoliciesPageContent.tsx
) pass a value or leave thetab
parameter undefined. However, an extra guard insetEnvironmentPolicyId
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 confusionCurrently, the variable name
isUsingOverridePolicy
is being assigned toenvironment?.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 likeisUsingDefaultPolicy
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 nameExporting
param
is helpful for reusability. However, for better clarity, consider renaming it toenvironmentPolicyParam
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
📒 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 usingenvironment.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 5Length of output: 5911
Double-check override UI condition – Verify Policy Trigger
The
<UpdateOverridePolicy>
component is rendered only whenenvironment.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 explicitSwitching 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 localenvironmentPolicyId
. Confirm that this fully covers expected scenarios (e.g., does the system handle cases whenenvironmentPolicy.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 5Length 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)
whenhasParentPolicy
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).
There was a problem hiding this 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 itemsUsing 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 consistencyThe 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 logicThe 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 tableImprove 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 layoutThe 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 5Length 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.useQueryis 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
📒 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 queryThe 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
isnull
, thewhere
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 handlenull
orundefined
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
isnull
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
andgetDesiredVersion
in parallel viaPromise.all
is a good approach. It reduces the overall response time.
{deployments.map((deployment) => ( | ||
<DeploymentRow key={deployment.id} deployment={deployment} /> | ||
))} | ||
</TableBody> |
There was a problem hiding this comment.
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.
{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> |
Summary by CodeRabbit
New Features
Refactor