Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

fix: Release channel ui cleanup #188

Merged
merged 1 commit into from
Nov 1, 2024

Conversation

adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Nov 1, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced tab management for the Environment and Environment Policy drawers, enhancing user navigation.
    • Added release filtering capabilities in the Overview component, allowing users to interactively manage release conditions.
    • New Usage component displays policies and environments, improving user interaction with related data.
  • Bug Fixes

    • Improved handling of blocked deployments, providing users with actionable insights when a deployment cannot proceed.
  • Chores

    • Removed the deprecated ReleaseFilter component to streamline the codebase.

Copy link
Contributor

coderabbitai bot commented Nov 1, 2024

Walkthrough

This pull request introduces significant updates across multiple components related to environment and release management. Key changes include the addition of enums for tab management in EnvironmentDrawer and EnvironmentPolicyDrawer, the introduction of hooks to manage tab states, and enhancements to the ReleaseChannelDrawer and Overview components. The DeploymentPageContent component has been updated to improve release channel interactions, and the database schema has been modified to better represent relationships between entities. Overall, these changes streamline functionality and improve the clarity and maintainability of the codebase.

Changes

File Change Summary
apps/webservice/src/app/[workspaceSlug]/_components/environment-drawer/EnvironmentDrawer.tsx - Added enum EnvironmentDrawerTab for tab management.
- Introduced useEnvironmentDrawerTab hook for managing tab state.
- Updated useEnvironmentDrawer to return tab state.
- Replaced local state management with new tab logic in EnvironmentDrawer.
apps/webservice/src/app/[workspaceSlug]/_components/environment-policy-drawer/EnvironmentPolicyDrawer.tsx - Added enum EnvironmentPolicyDrawerTab for tab identification.
- Introduced useEnvironmentPolicyDrawerTab hook for managing tab state.
- Updated activeTab prop in View component to use the new enum.
apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Overview.tsx - Added utility functions getFinalFilter and getReleaseFilterUrl for release filtering.
- Updated form schema to include releaseFilter field.
- Modified onSubmit to handle releaseFilter and updated reset behavior.
- Enhanced querying for releases based on filters.
apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/ReleaseChannelDrawer.tsx - Introduced activeTab state for managing tab display.
- Simplified release channel query logic.
- Updated layout to feature tabbed interface with conditional rendering for "Overview" and "Usage" tabs.
apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/ReleaseFilter.tsx - Component ReleaseFilter removed.
apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Usage.tsx - Added new component Usage for displaying policies and environments.
- Defined UsageInfo type and helper functions for URL parameter updates.
apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/releases/DeploymentPageContent.tsx - Integrated useReleaseChannelDrawer and updated logic for blocked releases.
- Enhanced rendering logic for deployment messages related to release channels.
packages/api/src/router/deployment.ts - Updated byId procedure in releaseChannelRouter to enhance query logic, including related environments and policies.
packages/api/src/router/release.ts - Enhanced blocked procedure to retrieve releaseChannelId from related channels and simplified return structure for blocked environments.
packages/db/src/schema/environment.ts - Updated environmentRelations to include new relationships to environmentPolicy and environmentReleaseChannel.
- Added new relation exports for better relational integrity.
packages/db/src/schema/release.ts - Introduced releaseChannelRelations to define relationships with environmentPolicyReleaseChannel and environmentReleaseChannel.

Possibly related PRs

Suggested reviewers

  • jsbroks

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

Documentation and Community

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Outside diff range and nitpick comments (9)
apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Usage.tsx (2)

10-15: Add JSDoc documentation for the UsageInfo type.

Consider adding JSDoc documentation to explain the purpose and structure of the UsageInfo type, which would improve maintainability and help other developers understand its usage.

+/**
+ * Represents the usage information for environments and policies
+ * @property environments - List of direct environments
+ * @property policies - List of environment policies with their associated environments
+ */
 type UsageInfo = {
   environments: SCHEMA.Environment[];
   policies: (SCHEMA.EnvironmentPolicy & {
     environments: SCHEMA.Environment[];
   })[];
 };

96-98: Improve policy name fallback handling.

The policy name fallback logic is duplicated and could be more consistent.

Consider extracting the policy name formatting:

+const formatPolicyName = (name: string) => name || "Unnamed policy";
+
 // Then use it in both places:
-{e.policyInherited.name != "" ? e.policyInherited.name : "policy"}
+{formatPolicyName(e.policyInherited.name)}
apps/webservice/src/app/[workspaceSlug]/_components/environment-drawer/EnvironmentDrawer.tsx (1)

68-73: Add TypeScript return type for improved type safety.

The hook's return type should be explicitly defined to reflect the added tab management functionality.

Consider adding this type definition:

interface UseEnvironmentDrawerReturn {
  environmentId: string | null;
  setEnvironmentId: (id: string | null) => void;
  removeEnvironmentId: () => void;
  tab: EnvironmentDrawerTab | null;
  setTab: (tab: EnvironmentDrawerTab | null) => void;
}
packages/db/src/schema/release.ts (1)

75-81: LGTM! Well-structured table relations.

The releaseChannelRelations definition properly establishes the many-to-many relationships between release channels and environments, following database schema best practices.

Consider documenting these relationships in a README or schema documentation to help other developers understand the data model, especially given the complexity of environment-to-release-channel mappings.

apps/webservice/src/app/[workspaceSlug]/_components/environment-policy-drawer/EnvironmentPolicyDrawer.tsx (1)

36-63: LGTM with a minor suggestion for error handling.

The enum and hook implementation look good, providing type safety and proper URL synchronization. Consider adding validation for unknown tab values in the hook to handle malformed URLs gracefully.

 const useEnvironmentPolicyDrawerTab = () => {
   const router = useRouter();
   const params = useSearchParams();
-  const tab = params.get(tabParam) as EnvironmentPolicyDrawerTab | null;
+  const rawTab = params.get(tabParam);
+  const tab = rawTab && Object.values(EnvironmentPolicyDrawerTab).includes(rawTab as EnvironmentPolicyDrawerTab)
+    ? (rawTab as EnvironmentPolicyDrawerTab)
+    : null;
apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/releases/DeploymentPageContent.tsx (1)

209-214: Consider adding null checks and improving variable naming

While the logic is correct, consider these improvements for better robustness and readability:

-const blockedEnv = blockedEnvByRelease.data?.find(
-  (be) =>
-    be.releaseId === release.id &&
-    be.environmentId === env.id,
-);
-const isBlockedByReleaseChannel = blockedEnv != null;
+const blockedEnvironment = blockedEnvByRelease.data?.find(
+  (blockedEntry) =>
+    blockedEntry?.releaseId === release.id &&
+    blockedEntry?.environmentId === env.id,
+) ?? null;
+const isBlockedByReleaseChannel = blockedEnvironment !== null;
packages/api/src/router/release.ts (1)

Line range hint 371-394: Consider refactoring duplicate subquery logic.

While the subqueries are well-structured, there's significant duplication in the field selection and join logic. Consider extracting the common parts into a reusable function.

Here's a suggested approach to reduce duplication:

const createReleaseChannelSubquery = (
  sourceTable: typeof environmentReleaseChannel | typeof environmentPolicyReleaseChannel,
  joinField: string,
  alias: string
) => {
  return db
    .select({
      releaseChannelId: releaseChannel.id,
      [joinField]: sourceTable[joinField],
      releaseChannelDeploymentId: releaseChannel.deploymentId,
      releaseChannelFilter: releaseChannel.releaseFilter,
    })
    .from(sourceTable)
    .innerJoin(
      releaseChannel,
      eq(sourceTable.channelId, releaseChannel.id),
    )
    .as(alias);
};

const envRCSubquery = createReleaseChannelSubquery(
  environmentReleaseChannel,
  'releaseChannelEnvId',
  'envRCSubquery'
);

const policyRCSubquery = createReleaseChannelSubquery(
  environmentPolicyReleaseChannel,
  'releaseChannelPolicyId',
  'policyRCSubquery'
);
apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Overview.tsx (2)

46-47: Include workspaceSlug in null checks

While workspaceSlug is typed as a string, adding a null or undefined check enhances robustness, ensuring that all required parameters are valid before constructing the URL.

Modify the condition to include workspaceSlug:

-  if (filter == null || systemSlug == null || deploymentSlug == null)
+  if (!workspaceSlug || !systemSlug || !deploymentSlug || !filter)
     return null;

37-39: Clarify the return value of getFinalFilter

Returning undefined or null can have different implications in JavaScript/TypeScript. Ensure consistency in how absence of a value is represented throughout the codebase.

Consider returning null instead of undefined if that aligns better with how null values are handled elsewhere:

- const getFinalFilter = (filter: ReleaseCondition | null) =>
-   filter && !isEmptyCondition(filter) ? filter : undefined;
+ const getFinalFilter = (filter: ReleaseCondition | null) =>
+   filter && !isEmptyCondition(filter) ? filter : null;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed209f and 78c7a8c.

📒 Files selected for processing (11)
  • apps/webservice/src/app/[workspaceSlug]/_components/environment-drawer/EnvironmentDrawer.tsx (4 hunks)
  • apps/webservice/src/app/[workspaceSlug]/_components/environment-policy-drawer/EnvironmentPolicyDrawer.tsx (6 hunks)
  • apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Overview.tsx (4 hunks)
  • apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/ReleaseChannelDrawer.tsx (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/ReleaseFilter.tsx (0 hunks)
  • apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Usage.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/releases/DeploymentPageContent.tsx (4 hunks)
  • packages/api/src/router/deployment.ts (1 hunks)
  • packages/api/src/router/release.ts (4 hunks)
  • packages/db/src/schema/environment.ts (4 hunks)
  • packages/db/src/schema/release.ts (3 hunks)
💤 Files with no reviewable changes (1)
  • apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/ReleaseFilter.tsx
🧰 Additional context used
📓 Learnings (1)
packages/api/src/router/deployment.ts (1)
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#181
File: packages/api/src/router/deployment.ts:116-131
Timestamp: 2024-10-29T02:04:50.312Z
Learning: In `packages/api/src/router/deployment.ts`, the `list.byDeploymentId` procedure requires multiple database queries due to limitations of the `releaseMatchesCondition` function.
🔇 Additional comments (20)
apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/ReleaseChannelDrawer.tsx (2)

4-10: LGTM! Import statements are well-organized.

The new imports are properly structured and follow the project's conventions.

Also applies to: 16-16, 19-19


34-34: Verify loading state coverage.

The loading state has been simplified to only depend on releaseChannelQ.isLoading. Please ensure this doesn't miss any edge cases where we might need to show loading states for other async operations.

✅ Verification successful

The previous command failed because the file type wasn't recognized. Let's try with more generic patterns and file extensions to find loading states and query hooks in the codebase.


Let's try searching in a specific directory path since the file type flags aren't working. We'll look for both query and mutation hooks that might affect loading states.


Based on the search results, I can now generate the final response. The codebase search shows that the loading state in the ReleaseChannelDrawer component only depends on releaseChannelQ.isLoading because it's the only query being used in this component, and the loading state is appropriately used to show a loading spinner while fetching the release channel data. The component's functionality is focused solely on displaying release channel details, so no other loading states are necessary.

Loading state implementation is correct

The simplified loading state that only depends on releaseChannelQ.isLoading is appropriate because:

  • The component only fetches and displays release channel data
  • Loading spinner is shown during the data fetch
  • No other async operations are happening in this component that would require additional loading states
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other loading states or queries in related components
rg -A 3 "isLoading" --type tsx

Length of output: 58


Script:

#!/bin/bash
# Search for loading states in TypeScript/React files using a more generic pattern
rg -A 3 "isLoading" --type ts --type tsx --type jsx

# Also search for any query hooks that might have loading states
rg -A 3 "useQuery" --type ts --type tsx --type jsx

# Look specifically in the release channel drawer component and related files
fd "ReleaseChannelDrawer" -e tsx -e ts | xargs cat

Length of output: 4112


Script:

#!/bin/bash
# Try with ripgrep without file type specification
rg -A 3 "isLoading|useQuery" apps/webservice/src/app/

# Also look for any mutation hooks that might affect loading states
rg -A 3 "useMutation" apps/webservice/src/app/

Length of output: 115554

apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Usage.tsx (1)

1-8: LGTM! Imports are well-organized.

The imports are properly structured and all are being utilized in the component.

apps/webservice/src/app/[workspaceSlug]/_components/environment-drawer/EnvironmentDrawer.tsx (2)

24-30: LGTM! Well-structured enum and constant declarations.

The enum values are descriptive and follow consistent naming conventions.


77-78: LGTM! Clean prop destructuring.

The destructuring is well-organized and includes all necessary props.

packages/db/src/schema/release.ts (1)

20-20: LGTM! Clean import organization.

The new imports are well-organized and properly grouped by their source modules.

Also applies to: 49-53

apps/webservice/src/app/[workspaceSlug]/_components/environment-policy-drawer/EnvironmentPolicyDrawer.tsx (4)

70-70: Clean hook integration.

The tab management integration into useEnvironmentPolicyDrawer is well-structured and maintains good separation of concerns.

Also applies to: 89-90


192-225: Well-structured tab button implementation.

The tab button implementation is type-safe and consistent, with appropriate default tab handling and semantic icon choices.


234-236: Clean default tab handling.

The View rendering properly handles the default tab case with appropriate null coalescing.


99-99: Consider handling the ReleaseChannels edge case more explicitly.

While the tab mapping is well-structured, the ReleaseChannels case has an implicit null check that could be made more explicit to prevent potential rendering issues.

   [EnvironmentPolicyDrawerTab.ReleaseChannels]: deployments != null && (
-    <ReleaseChannels policy={environmentPolicy} deployments={deployments} />
+    <ReleaseChannels 
+      policy={environmentPolicy} 
+      deployments={deployments} 
+    />
+  ) || (
+    <div>Loading deployments...</div>

Also applies to: 107-122

apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/releases/DeploymentPageContent.tsx (2)

23-23: LGTM: Clean import addition

The new hook import is properly organized and follows the project's import structure.


46-46: LGTM: Proper hook usage

The hook is correctly integrated at the component's top level, following React hooks best practices.

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

454-457: LGTM! Clean and well-structured changes.

The modifications improve the return structure by:

  1. Including the releaseChannelId in a type-safe manner
  2. Using nullish coalescing operator appropriately
  3. Simplifying the return structure to a filtered array

Also applies to: 470-474, 478-478

apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Overview.tsx (3)

139-161: Ensure accurate loading states and data display

The loading spinner and release count in the form label depend on the releasesQ query. Verify that the loading state transitions correctly and that the release count updates appropriately.

Testing the component should confirm that users receive accurate feedback during data fetching.


162-177: Conditional rendering of the "View releases" button

The button to view releases is correctly conditioned on releaseFilterUrl not being null, preventing potential errors from navigating to an invalid URL.


65-69: Verify that route parameters are always available

The useParams hook assumes that workspaceSlug, systemSlug, and deploymentSlug are provided. Missing parameters could lead to runtime errors.

Run the following script to confirm that these parameters are consistently supplied in routes:

packages/db/src/schema/environment.ts (4)

59-64: Good addition of relations to environmentPolicy and environmentReleaseChannel

The new relations enhance the environment schema by establishing clear links to environmentPolicy and environmentReleaseChannel, which will improve data integrity and query capabilities.


124-130: Properly defined environmentPolicyRelations

The added relations correctly map environmentPolicy to its associated environmentPolicyReleaseChannel and environment, facilitating comprehensive access to related data.


238-250: Well-structured environmentPolicyReleaseChannelRelations

The relations accurately connect environmentPolicyReleaseChannel with environmentPolicy and releaseChannel, ensuring consistency in the relational mappings.


276-288: Correctly established environmentReleaseChannelRelations

The new relations link environmentReleaseChannel to environment and releaseChannel effectively, enhancing the schema's relational coherence.


return (
<Drawer open={isOpen} onOpenChange={setIsOpen}>
<DrawerContent
showBar={false}
className="scrollbar-thin scrollbar-thumb-neutral-800 scrollbar-track-neutral-900 left-auto right-0 top-0 mt-0 h-screen w-1/3 overflow-auto rounded-none focus-visible:outline-none"
className="scrollbar-thin scrollbar-thumb-neutral-800 scrollbar-track-neutral-900 left-auto right-0 top-0 mt-0 h-screen w-2/3 max-w-6xl overflow-auto rounded-none focus-visible:outline-none"
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 1, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider responsive design for smaller screens.

The drawer width is set to w-2/3 which might be too wide on smaller screens. Consider adding responsive width classes or a mobile-first approach.

-className="scrollbar-thin scrollbar-thumb-neutral-800 scrollbar-track-neutral-900 left-auto right-0 top-0 mt-0 h-screen w-2/3 max-w-6xl overflow-auto rounded-none focus-visible:outline-none"
+className="scrollbar-thin scrollbar-thumb-neutral-800 scrollbar-track-neutral-900 left-auto right-0 top-0 mt-0 h-screen w-full md:w-3/4 lg:w-2/3 max-w-6xl overflow-auto rounded-none focus-visible:outline-none"
📝 Committable suggestion

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

Suggested change
className="scrollbar-thin scrollbar-thumb-neutral-800 scrollbar-track-neutral-900 left-auto right-0 top-0 mt-0 h-screen w-2/3 max-w-6xl overflow-auto rounded-none focus-visible:outline-none"
className="scrollbar-thin scrollbar-thumb-neutral-800 scrollbar-track-neutral-900 left-auto right-0 top-0 mt-0 h-screen w-full md:w-3/4 lg:w-2/3 max-w-6xl overflow-auto rounded-none focus-visible:outline-none"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we are setting a max width on the component

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 Understood. Since a max width is set on the component, the current width settings are appropriate. Thank you for clarifying.


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#188
File: apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/ReleaseChannelDrawer.tsx:42-42
Timestamp: 2024-11-01T02:33:07.360Z
Learning: When a max-width is set on a component, using a fixed width class without additional responsive width classes is acceptable.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


const loading = releaseChannelQ.isLoading || releasesQ.isLoading;
const [activeTab, setActiveTab] = useState("overview");
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 1, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using TypeScript enum for tab values.

Using string literals for tab values could lead to maintenance issues. Consider defining an enum for better type safety and maintainability.

+enum TabType {
+  OVERVIEW = "overview",
+  USAGE = "usage"
+}

-const [activeTab, setActiveTab] = useState("overview");
+const [activeTab, setActiveTab] = useState<TabType>(TabType.OVERVIEW);

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

for now since there are two tabs we can keep it simple

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26, understood. Keeping it simple for now makes sense.

Comment on lines +42 to +44
const inheritedEnvironments = policies
.flatMap((p) => p.environments.map((e) => ({ ...e, policyInherited: p })))
.filter((env) => !environments.includes(env));
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 1, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize inherited environments filtering logic.

The current implementation might be inefficient for large datasets as it creates a new array with all environments before filtering.

Consider using a Set for more efficient filtering:

-  const inheritedEnvironments = policies
-    .flatMap((p) => p.environments.map((e) => ({ ...e, policyInherited: p })))
-    .filter((env) => !environments.includes(env));
+  const directEnvironmentIds = new Set(environments.map(e => e.id));
+  const inheritedEnvironments = policies.flatMap((p) => 
+    p.environments
+      .filter(e => !directEnvironmentIds.has(e.id))
+      .map(e => ({ ...e, policyInherited: p }))
+  );

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the array will likely never be sufficiently large enough to cause perf issues

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26, understood.


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#188
File: apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Usage.tsx:42-44
Timestamp: 2024-11-01T02:35:06.728Z
Learning: In `apps/webservice/src/app/[workspaceSlug]/_components/release-channel-drawer/Usage.tsx`, within the `Usage` component, the arrays used for filtering inherited environments are expected to remain small. Therefore, performance optimizations for this filtering logic are not necessary.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +31 to +48
const useEnvironmentDrawerTab = () => {
const router = useRouter();
const params = useSearchParams();
const tab = params.get(tabParam);

const setTab = (tab: EnvironmentDrawerTab | null) => {
const url = new URL(window.location.href);
if (tab === null) {
url.searchParams.delete(tabParam);
router.replace(`${url.pathname}?${url.searchParams.toString()}`);
return;
}
url.searchParams.set(tabParam, tab);
router.replace(`${url.pathname}?${url.searchParams.toString()}`);
};

return { tab, setTab };
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding validation for tab parameter values.

The hook handles tab state well, but it might benefit from validating that the tab value from URL params matches the EnvironmentDrawerTab enum values.

Consider this improvement:

 const useEnvironmentDrawerTab = () => {
   const router = useRouter();
   const params = useSearchParams();
   const tab = params.get(tabParam);
+  const isValidTab = (value: string | null): value is EnvironmentDrawerTab => {
+    return value != null && Object.values(EnvironmentDrawerTab).includes(value as EnvironmentDrawerTab);
+  };
+  const validTab = isValidTab(tab) ? tab : null;

   const setTab = (tab: EnvironmentDrawerTab | null) => {
     // ... existing code ...
   };

-  return { tab, setTab };
+  return { tab: validTab, setTab };
 };
📝 Committable suggestion

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

Suggested change
const useEnvironmentDrawerTab = () => {
const router = useRouter();
const params = useSearchParams();
const tab = params.get(tabParam);
const setTab = (tab: EnvironmentDrawerTab | null) => {
const url = new URL(window.location.href);
if (tab === null) {
url.searchParams.delete(tabParam);
router.replace(`${url.pathname}?${url.searchParams.toString()}`);
return;
}
url.searchParams.set(tabParam, tab);
router.replace(`${url.pathname}?${url.searchParams.toString()}`);
};
return { tab, setTab };
};
const useEnvironmentDrawerTab = () => {
const router = useRouter();
const params = useSearchParams();
const tab = params.get(tabParam);
const isValidTab = (value: string | null): value is EnvironmentDrawerTab => {
return value != null && Object.values(EnvironmentDrawerTab).includes(value as EnvironmentDrawerTab);
};
const validTab = isValidTab(tab) ? tab : null;
const setTab = (tab: EnvironmentDrawerTab | null) => {
const url = new URL(window.location.href);
if (tab === null) {
url.searchParams.delete(tabParam);
router.replace(`${url.pathname}?${url.searchParams.toString()}`);
return;
}
url.searchParams.set(tabParam, tab);
router.replace(`${url.pathname}?${url.searchParams.toString()}`);
};
return { tab: validTab, setTab };
};

Comment on lines +262 to +280
{isBlockedByReleaseChannel ? (
<span>
Blocked by{" "}
<Button
variant="link"
size="sm"
onClick={() =>
setReleaseChannelId(
blockedEnv.releaseChannelId ?? null,
)
}
className="px-0 text-muted-foreground/70"
>
release channel
</Button>
</span>
) : (
"No job agent"
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider enhancing the release channel interaction UX

The interactive button is a good addition, but consider these UX improvements:

  1. Add loading state while the release channel drawer is opening
  2. Handle potential errors if the release channel data is unavailable
  3. Add a tooltip explaining what happens when clicking the button
 <Button
   variant="link"
   size="sm"
+  disabled={isLoading}
+  title="Click to view release channel details"
   onClick={() =>
-    setReleaseChannelId(blockedEnv.releaseChannelId ?? null)
+    setReleaseChannelId(blockedEnv?.releaseChannelId ?? null).catch((error) => {
+      console.error('Failed to open release channel:', error);
+      // Add error handling UI feedback here
+    })
   }
   className="px-0 text-muted-foreground/70"
 >
-  release channel
+  {isLoading ? 'Opening...' : 'release channel'}
 </Button>

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

Comment on lines +81 to +90
const onSubmit = form.handleSubmit((data) => {
const releaseFilter = getFinalFilter(data.releaseFilter);
updateReleaseChannel
.mutateAsync({ id: releaseChannel.id, data })
.then(() => form.reset(data))
.mutateAsync({ id: releaseChannel.id, data: { ...data, releaseFilter } })
.then(() => form.reset({ ...data, releaseFilter }))
.then(() =>
utils.deployment.releaseChannel.byId.invalidate(releaseChannel.id),
)
.then(() => router.refresh()),
.then(() => router.refresh());
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling to the onSubmit function

Currently, the onSubmit function lacks error handling for the mutation. It's important to handle potential errors to provide feedback to the user in case the update fails.

Consider updating the function to include error handling:

-  const onSubmit = form.handleSubmit((data) => {
+  const onSubmit = form.handleSubmit(async (data) => {
     const releaseFilter = getFinalFilter(data.releaseFilter);
-    updateReleaseChannel
-      .mutateAsync({ id: releaseChannel.id, data: { ...data, releaseFilter } })
-      .then(() => form.reset({ ...data, releaseFilter }))
-      .then(() =>
-        utils.deployment.releaseChannel.byId.invalidate(releaseChannel.id),
-      )
-      .then(() => router.refresh());
+    try {
+      await updateReleaseChannel.mutateAsync({
+        id: releaseChannel.id,
+        data: { ...data, releaseFilter },
+      });
+      form.reset({ ...data, releaseFilter });
+      await utils.deployment.releaseChannel.byId.invalidate(releaseChannel.id);
+      router.refresh();
+    } catch (error) {
+      // Handle the error, e.g., display a notification to the user
+      console.error("Failed to update the release channel:", error);
+    }
   });
📝 Committable suggestion

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

Suggested change
const onSubmit = form.handleSubmit((data) => {
const releaseFilter = getFinalFilter(data.releaseFilter);
updateReleaseChannel
.mutateAsync({ id: releaseChannel.id, data })
.then(() => form.reset(data))
.mutateAsync({ id: releaseChannel.id, data: { ...data, releaseFilter } })
.then(() => form.reset({ ...data, releaseFilter }))
.then(() =>
utils.deployment.releaseChannel.byId.invalidate(releaseChannel.id),
)
.then(() => router.refresh()),
.then(() => router.refresh());
});
const onSubmit = form.handleSubmit(async (data) => {
const releaseFilter = getFinalFilter(data.releaseFilter);
try {
await updateReleaseChannel.mutateAsync({
id: releaseChannel.id,
data: { ...data, releaseFilter },
});
form.reset({ ...data, releaseFilter });
await utils.deployment.releaseChannel.byId.invalidate(releaseChannel.id);
router.refresh();
} catch (error) {
// Handle the error, e.g., display a notification to the user
console.error("Failed to update the release channel:", error);
}
});

Comment on lines +144 to +161
const rc = await ctx.db.query.releaseChannel.findFirst({
where: eq(releaseChannel.id, input),
with: {
environmentReleaseChannels: { with: { environment: true } },
environmentPolicyReleaseChannels: {
with: { environmentPolicy: true },
},
},
});
if (rc == null) return null;
const policyIds = rc.environmentPolicyReleaseChannels.map(
(eprc) => eprc.environmentPolicy.id,
);

const envs = await ctx.db
.select()
.from(releaseChannel)
.where(eq(releaseChannel.id, input))
.orderBy(releaseChannel.name)
.then(takeFirst),
),
.from(environment)
.where(inArray(environment.policyId, policyIds));
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 1, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider combining database queries to optimize performance

The current implementation makes two separate database queries: one to retrieve the releaseChannel and its related data (rc), and another to fetch environments (envs) based on policy IDs. You can optimize performance by combining these queries into a single query using eager loading of nested relationships.

Apply this diff to combine the queries:

          const rc = await ctx.db.query.releaseChannel.findFirst({
            where: eq(releaseChannel.id, input),
            with: {
              environmentReleaseChannels: { with: { environment: true } },
              environmentPolicyReleaseChannels: {
                with: {
                  environmentPolicy: {
+                   with: { environments: true },
                  },
                },
              },
            },
          });
          if (rc == null) return null;

-         const policyIds = rc.environmentPolicyReleaseChannels.map(
-           (eprc) => eprc.environmentPolicy.id,
-         );

-         const envs = await ctx.db
-           .select()
-           .from(environment)
-           .where(inArray(environment.policyId, policyIds));

          return {
            ...rc,
            usage: {
              environments: rc.environmentReleaseChannels.map(
                (erc) => erc.environment,
              ),
              policies: rc.environmentPolicyReleaseChannels.map((eprc) => ({
                ...eprc.environmentPolicy,
-               environments: envs.filter(
-                 (e) => e.policyId === eprc.environmentPolicy.id,
-               ),
+               environments: eprc.environmentPolicy.environments,
              })),
            },
          };
📝 Committable suggestion

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

Suggested change
const rc = await ctx.db.query.releaseChannel.findFirst({
where: eq(releaseChannel.id, input),
with: {
environmentReleaseChannels: { with: { environment: true } },
environmentPolicyReleaseChannels: {
with: { environmentPolicy: true },
},
},
});
if (rc == null) return null;
const policyIds = rc.environmentPolicyReleaseChannels.map(
(eprc) => eprc.environmentPolicy.id,
);
const envs = await ctx.db
.select()
.from(releaseChannel)
.where(eq(releaseChannel.id, input))
.orderBy(releaseChannel.name)
.then(takeFirst),
),
.from(environment)
.where(inArray(environment.policyId, policyIds));
const rc = await ctx.db.query.releaseChannel.findFirst({
where: eq(releaseChannel.id, input),
with: {
environmentReleaseChannels: { with: { environment: true } },
environmentPolicyReleaseChannels: {
with: {
environmentPolicy: {
with: { environments: true },
},
},
},
},
});
if (rc == null) return null;
return {
...rc,
usage: {
environments: rc.environmentReleaseChannels.map(
(erc) => erc.environment,
),
policies: rc.environmentPolicyReleaseChannels.map((eprc) => ({
...eprc.environmentPolicy,
environments: eprc.environmentPolicy.environments,
})),
},
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

due to a limitation with drizzle (where I reference the same table twice in a rbq) I need to pull it out. but the majority of the work is done by the main query, so this shouldn't be too bad perf-wise

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26, understood. Thank you for explaining the limitation with Drizzle when referencing the same table twice in an rbq. I've noted this information for future reviews.


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#188
File: packages/api/src/router/deployment.ts:144-161
Timestamp: 2024-11-01T02:37:25.340Z
Learning: In `packages/api/src/router/deployment.ts`, when using Drizzle ORM, there is a limitation when referencing the same table twice in a relational builder query (rbq), requiring separate queries to avoid issues.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants