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: Refactor gh integration #288

Merged
merged 2 commits into from
Jan 22, 2025
Merged

fix: Refactor gh integration #288

merged 2 commits into from
Jan 22, 2025

Conversation

adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Jan 22, 2025

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced GitHub integration to support both organizations and user entities.
    • Added support for specifying a custom Git reference in job configurations.
  • Improvements

    • Refactored GitHub-related components to be more flexible and generic.
    • Updated database schema to accommodate different types of GitHub entities.
    • Improved error handling and type safety across GitHub integration components.
  • Bug Fixes

    • Restored "successful" status for job tracking.
    • Fixed styling inconsistencies in job agent configuration forms.
  • Chores

    • Renamed and restructured multiple GitHub-related components and functions.
    • Updated type definitions and API interactions for better code maintainability.

Copy link
Contributor

coderabbitai bot commented Jan 22, 2025

Walkthrough

This pull request introduces a comprehensive refactoring of GitHub-related components and workflows, transitioning from an organization-specific model to a more generalized GitHub entity model. The changes span multiple files across different applications, focusing on enhancing flexibility and modularity in handling GitHub integrations. Key modifications include renaming components, updating type definitions, and modifying database schemas to support both organization and user entities.

Changes

File Change Summary
apps/event-worker/src/job-dispatch/github.ts Added getGithubEntity helper function, updated dispatchGithubJob to use new entity retrieval logic.
apps/webservice/src/**/github/* Renamed components from "Org" to "Entity", updated type definitions and props, removed githubFormStyleConfig from several components.
packages/api/src/router/github.ts Renamed entities, added new types for workflows and repositories.
packages/db/src/schema/github.ts Introduced githubEntityType enum, replaced githubOrganization with githubEntity.
packages/db/drizzle/0061_left_jocasta.sql Renamed github_organization table to github_entity, added type column and ENUM type for entity categorization.
packages/db/drizzle/meta/_journal.json Added a new journal entry for the database migration.
packages/db/src/schema/job.ts Updated jobStatus enum to include "successful" status.
packages/validators/src/github/index.ts Added optional ref property to configSchema.

Sequence Diagram

sequenceDiagram
    participant User
    participant WebApp
    participant GitHub
    participant Database
    
    User->>WebApp: Initiate GitHub Integration
    WebApp->>GitHub: Fetch Installation Details
    GitHub-->>WebApp: Return Installation Data
    WebApp->>Database: Create GitHub Entity
    Database-->>WebApp: Confirm Entity Creation
    WebApp->>User: Display Connected Entities
Loading

Possibly related PRs

  • fix: Show job external url #154: The changes in the main PR regarding the dispatchGithubJob function are related to this PR, as both involve modifications to the same function in github.ts, specifically around job handling and status updates.
  • fix: Release dependencies refactor #165: This PR also relates to the main PR as it involves changes to job handling, particularly in the context of job triggers and their statuses, which aligns with the updates made in the dispatchGithubJob function.
  • fix: Convert completed to successful #287: The changes in this PR regarding job status updates from "completed" to "successful" are directly relevant to the main PR, as both involve modifications to job status handling.

Suggested reviewers

  • jsbroks

Poem

🐰 A Rabbit's GitHub Tale 🐰

From orgs to entities, we dance and leap,
Refactoring code with a technological sweep.
Flexibility grows, our system more bright,
GitHub connections now take flight!
Code hops forward, with elegance rare 🚀

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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 comments (1)
apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/GithubRemoveEntityDialog.tsx (1)

Line range hint 42-47: Update dialog text to reflect entity-based model.

The dialog text still refers specifically to "Github organization" which doesn't align with the new entity-based model that supports both organizations and users.

-            Are you sure you want to disconnect this Github organization?
+            Are you sure you want to disconnect this Github {githubEntity.type}?

-            Disconnecting the organization will remove the connection between
-            Ctrlplane and your Github organization for this workspace.
+            Disconnecting this {githubEntity.type} will remove the connection between
+            Ctrlplane and your Github {githubEntity.type} for this workspace.
🧹 Nitpick comments (7)
apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/SelectPreconnectedEntityDialogContent.tsx (2)

48-49: Specify mutation input fields explicitly

While using ...entity spreads all properties into mutateAsync, explicitly specifying the required fields enhances clarity and security by ensuring only intended data is sent.

Apply this diff to specify the input fields explicitly:

 await githubEntityCreate.mutateAsync({
-  ...entity,
+  installationId: entity.installationId,
+  type: entity.type,
+  slug: entity.slug,
+  avatarUrl: entity.avatarUrl,
   workspaceId,
 });

103-104: Handle undefined avatarUrl in AvatarImage

If avatarUrl can be undefined, provide a fallback image or handle the null case to prevent rendering issues in the AvatarImage component.

apps/webservice/src/components/form/job-agent/JobAgentGitHubConfig.tsx (2)

95-98: Specify the radix parameter in Number.parseInt.

When using Number.parseInt, it's recommended to specify the radix to avoid unexpected results due to different numeral systems.

Apply this diff to specify the radix:

onSelect={(currentValue) => {
  onChange({
    ...value,
-   workflowId: Number.parseInt(currentValue),
+   workflowId: Number.parseInt(currentValue, 10),
  });
  setWorkflowOpen(false);
}}

112-116: Consider adding validation for the ref input.

The ref input allows users to specify a branch or tag. Adding validation can help prevent errors due to invalid or mistyped references.

You might implement validation logic like:

<Input
  placeholder="Ref (uses default branch if not set)"
  className="w-80"
  value={value.ref ?? ""}
- onChange={(e) => onChange({ ...value, ref: e.target.value })}
+ onChange={(e) => {
+   const ref = e.target.value.trim();
+   // Simple validation example: ensure ref is not empty if provided
+   onChange({ ...value, ref: ref || undefined });
+ }}
/>

Alternatively, you could provide feedback to the user if the ref is invalid.

apps/event-worker/src/job-dispatch/github.ts (1)

154-154: Ensure sensitive data is not logged.

When logging, verify that no sensitive information, such as tokens or confidential identifiers, is included in the log output.

Review the logging statement to ensure compliance:

logger.info(`Creating workflow dispatch for job ${je.id}...`, {
  owner: parsedConfig.owner,
  repo: parsedConfig.repo,
  workflow_id: parsedConfig.workflowId,
  ref,
- inputs: { job_id: je.id },
+ // Avoid logging sensitive input data if applicable
});

If inputs contain only non-sensitive data, it's acceptable. Otherwise, consider omitting or masking sensitive details.

apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/GithubAddEntityDialog.tsx (1)

Line range hint 35-128: Consider improving UX text for entity type context.

While the implementation is correct, the dialog title and descriptions still use organization-specific language in some places. Consider updating these to reflect the entity type context.

-              <DialogTitle>Connect a new Organization</DialogTitle>
+              <DialogTitle>Connect a new GitHub Entity</DialogTitle>
-                  Install the ctrlplane Github app on an organization to connect
+                  Install the ctrlplane Github app on a GitHub organization or user to connect
-                      <strong>Connect a new organization:</strong> Install the
+                      <strong>Connect a new entity:</strong> Install the
-                      <strong>Select pre-connected:</strong> Select a
-                      pre-connected organization where the ctrlplane app is
+                      <strong>Select pre-connected:</strong> Select an already
+                      pre-connected entity where the ctrlplane app is
apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/GithubConnectedEntities.tsx (1)

97-128: Consider adding entity type indicator in the UI.

While the implementation correctly handles both types of entities, it might be helpful to visually distinguish between organizations and users in the UI.

                    <p className="font-semibold text-neutral-200">
-                      {entity.slug}
+                      {entity.slug}
+                      <span className="ml-2 text-xs text-neutral-400">
+                        ({entity.type})
+                      </span>
                    </p>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9c04ec3 and 217011a.

📒 Files selected for processing (21)
  • apps/event-worker/src/job-dispatch/github.ts (4 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/CreateDeployment.tsx (0 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/CreateHookDialog.tsx (0 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/EditHookDialog.tsx (0 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/create/CreateRunbookForm.tsx (0 hunks)
  • apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/EntityActionDropdown.tsx (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/GithubAddEntityDialog.tsx (4 hunks)
  • apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/GithubConnectedEntities.tsx (4 hunks)
  • apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/GithubRemoveEntityDialog.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/SelectPreconnectedEntityDialogContent.tsx (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/page.tsx (2 hunks)
  • apps/webservice/src/app/api/github/installation/route.ts (2 hunks)
  • apps/webservice/src/app/api/github/octokit.ts (1 hunks)
  • apps/webservice/src/components/form/job-agent/JobAgentConfig.tsx (2 hunks)
  • apps/webservice/src/components/form/job-agent/JobAgentGitHubConfig.tsx (2 hunks)
  • packages/api/src/router/github.ts (8 hunks)
  • packages/db/drizzle/0061_left_jocasta.sql (1 hunks)
  • packages/db/drizzle/meta/_journal.json (1 hunks)
  • packages/db/src/schema/github.ts (3 hunks)
  • packages/db/src/schema/job.ts (1 hunks)
  • packages/validators/src/github/index.ts (1 hunks)
💤 Files with no reviewable changes (4)
  • apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/CreateHookDialog.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/runbooks/create/CreateRunbookForm.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/hooks/EditHookDialog.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/CreateDeployment.tsx
🧰 Additional context used
📓 Path-based instructions (15)
packages/validators/src/github/index.ts (1)

Pattern **/*.{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]/settings/workspace/integrations/(integration)/github/page.tsx (1)

Pattern **/*.{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]/settings/workspace/integrations/(integration)/github/EntityActionDropdown.tsx (1)

Pattern **/*.{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]/settings/workspace/integrations/(integration)/github/GithubRemoveEntityDialog.tsx (1)

Pattern **/*.{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/api/github/octokit.ts (1)

Pattern **/*.{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/event-worker/src/job-dispatch/github.ts (1)

Pattern **/*.{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/db/src/schema/github.ts (1)

Pattern **/*.{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]/settings/workspace/integrations/(integration)/github/GithubAddEntityDialog.tsx (1)

Pattern **/*.{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]/settings/workspace/integrations/(integration)/github/SelectPreconnectedEntityDialogContent.tsx (1)

Pattern **/*.{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/components/form/job-agent/JobAgentConfig.tsx (1)

Pattern **/*.{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/components/form/job-agent/JobAgentGitHubConfig.tsx (1)

Pattern **/*.{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/db/src/schema/job.ts (1)

Pattern **/*.{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/api/github/installation/route.ts (1)

Pattern **/*.{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/github.ts (1)

Pattern **/*.{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]/settings/workspace/integrations/(integration)/github/GithubConnectedEntities.tsx (1)

Pattern **/*.{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.

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Typecheck
  • GitHub Check: build (linux/amd64)
  • GitHub Check: Lint
🔇 Additional comments (34)
apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/SelectPreconnectedEntityDialogContent.tsx (6)

19-25: Type definition accurately reflects the GitHub entity structure

The PreconnectedEntityComboboxProps type appropriately defines githubEntities with the necessary properties for both users and organizations.


32-34: Destructure all necessary props from PreconnectedEntityComboboxProps

The githubUser prop is defined in PreconnectedEntityComboboxProps but is not being destructured in the component's parameters. If githubUser is needed within the component, ensure it is included in the destructured props.


41-41: Mutation for GitHub entity creation is correctly initialized

The githubEntityCreate mutation is properly set up using the TRPC API.


45-46: Proper handling of missing selected entity

The code correctly checks if the selected entity exists before proceeding, preventing potential errors.


96-96: Verify avatarUrl before updating state

Ensure that avatarUrl is defined before calling setImage(avatarUrl) to prevent setting the image state to undefined.


124-124: Button disabled state is correctly managed

The disabled prop appropriately disables the button when no entity is selected or when the mutation is pending.

apps/webservice/src/app/api/github/installation/route.ts (1)

165-171: Verify that jobAgent configuration aligns with the new entity structure.

Ensure that the jobAgent is created with the correct configuration fields matching the updated githubEntity structure.

Run the following script to check the jobAgent configuration:

Replace your_database with the actual database name. This script checks that the installationId and owner in jobAgent.config match entries in github_entity.

✅ Verification successful

Configuration structure is correctly aligned

The jobAgent configuration correctly maps the entity fields:

  • config.installationId maps to entity.installationId
  • config.owner maps to entity.slug

This structure is consistently implemented across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that job agents are configured correctly with the updated githubEntity.

# Test: Retrieve all job agents and check their configurations.
psql -d your_database -c "
SELECT
  job_agent.name,
  job_agent.config->>'installationId' AS installation_id,
  job_agent.config->>'owner' AS owner
FROM job_agent
JOIN github_entity ON github_entity.installation_id::text = job_agent.config->>'installationId'
WHERE github_entity.slug = job_agent.config->>'owner';
"

Length of output: 436


Script:

#!/bin/bash
# Find type definitions and interfaces
ast-grep --pattern 'interface JobAgent {
  $$$
}'

ast-grep --pattern 'interface GithubEntity {
  $$$
}'

# Find other jobAgent insertions with github-app type
rg -A 5 'type: "github-app",' 

Length of output: 980


Script:

#!/bin/bash
# Search for schema definitions
rg -g '*.{ts,js}' -A 10 'CREATE TABLE.*job_agent'
rg -g '*.{ts,js}' -A 10 'CREATE TABLE.*github_entity'

# Search for type declarations
rg -g '*.{ts,js}' -A 5 'type JobAgentConfig = '
rg -g '*.{ts,js}' -A 5 'interface JobAgentConfig'

Length of output: 209

packages/api/src/router/github.ts (5)

11-12: Imports updated correctly.

The renamed entities githubEntity and githubEntityInsert are correctly imported from @ctrlplane/db/schema, reflecting the refactoring from githubOrganization and githubOrganizationInsert.


46-47: Type alias 'InstallationOctokitClient' defined appropriately.

Defining InstallationOctokitClient enhances type safety by specifying the return type of getOctokitInstallation, which improves code readability and maintainability.


87-89: Type 'Workflow' defined correctly for GitHub workflows.

The Workflow type accurately represents a single workflow from the GitHub API response, ensuring precise typing for workflow-related operations.


90-94: Type 'Repo' extended with 'workflows' property correctly.

The Repo type is appropriately extended to include the workflows array, combining repository data with its associated workflows for more comprehensive data handling.


354-362: Ensure 'jobAgent' is correctly configured with necessary fields.

The jobAgent insertion seems appropriate, but verify that all required fields and configurations are present and correctly set, especially if there are additional constraints or defaults in the database schema.

packages/validators/src/github/index.ts (1)

8-8: Review the necessity of both 'optional' and 'nullable' for 'ref' property.

The ref property is defined as z.string().min(1).optional().nullable(). Using both .optional() and .nullable() might be redundant. If ref can be undefined or null, ensure that downstream code correctly handles both cases. Consider if only one is sufficient for your use case.

Would you like to streamline the schema by removing one of these modifiers if appropriate?

apps/webservice/src/app/api/github/octokit.ts (1)

38-40: Exported type 'AuthedOctokitClient' enhances type safety.

Defining AuthedOctokitClient as a non-nullable return type ensures that any usage of this type is safeguarded against null values, improving robustness.

apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/EntityActionDropdown.tsx (2)

16-19: LGTM! Props type definition aligns with the entity-based model.

The type definition correctly uses the new GithubEntity type from the schema.


45-47: LGTM! Dialog integration is consistent.

The GithubRemoveEntityDialog is correctly integrated with the updated prop name.

apps/webservice/src/components/form/job-agent/JobAgentConfig.tsx (3)

25-32: LGTM! Query setup is well-structured.

The query is correctly configured with proper enablement condition and required parameters.


38-43: LGTM! Loading state improves user experience.

Good addition of a loading spinner with descriptive text.


52-52: Fix typo in placeholder text.

The placeholder text has been corrected from "Unsupport job agent" to "Unsupported job agent".

packages/db/src/schema/github.ts (3)

27-30: LGTM! Entity type enum is well-defined.

The enum correctly defines the supported GitHub entity types.


58-63: LGTM! Type exports are properly defined.

The types and insert schema are correctly defined with appropriate field omissions.


Line range hint 32-57: Verify database constraints for the entity table.

The table definition looks good with proper foreign key constraints and unique index. However, we should verify if any existing queries or migrations handle the transition from organization to entity correctly.

✅ Verification successful

Database constraints are properly maintained

The transition from github_organization to github_entity is handled correctly through migration 0061_left_jocasta.sql with proper constraint management. The new schema maintains all necessary foreign key relationships and the unique index on installation and workspace IDs.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential migration issues

# Search for direct table references
rg -A 2 "github_organization" 

# Check for queries that might need updates
rg -A 2 "FROM.*github_organization"

Length of output: 75432


Script:

#!/bin/bash
# Search for SQL queries and code references
rg -t ts -t js -A 2 "github_organization" --glob '!packages/db/drizzle/**'

# Check schema files for foreign key relationships
rg -t ts "references|foreignKey" packages/db/src/schema/

Length of output: 10776

apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/page.tsx (1)

Line range hint 89-97: LGTM! Component update aligns with the entity model refactoring.

The change from GithubConnectedOrgs to GithubConnectedEntities maintains interface compatibility while supporting the new generalized entity model.

apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/GithubAddEntityDialog.tsx (1)

22-31: LGTM! Type definition properly handles entity discrimination.

The updated type definition properly handles both user and organization entities with appropriate type discrimination and required fields.

apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/GithubConnectedEntities.tsx (1)

39-46: LGTM! API integration properly handles the entity model.

The API calls have been correctly updated to use the new entity-based endpoints while maintaining proper error handling and type safety.

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

63-63: Verify validator package update for the new job status.

The addition of "successful" status to the enum requires a corresponding update in the validators package as mentioned in the comment above.

Run the following script to verify the validator package update:

✅ Verification successful

Validator package is already updated with "successful" status

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the validators package has been updated with the new status

# Look for the job status validator definition
rg -A 10 "jobStatus.*=.*z\.enum" packages/validators/src/jobs/

Length of output: 942

packages/db/drizzle/0061_left_jocasta.sql (7)

1-5: LGTM! Well-structured ENUM type creation.

The creation of github_entity_type ENUM with proper exception handling for duplicates is a good practice.


7-8: LGTM! Good choice of naming.

The rename operations effectively generalize the schema, and using slug is a more appropriate term for the identifier.


9-11: LGTM! Proper constraint management.

Dropping constraints before recreating them with updated names is the correct approach when renaming tables.


14-18: LGTM! Well-defined foreign key constraint.

The foreign key constraint is properly defined with appropriate CASCADE/NO ACTION behaviors and exception handling.


20-24: LGTM! Consistent constraint definition.

The workspace foreign key constraint follows the same good practices as the user constraint.


13-13: Verify data migration for existing records.

The column addition looks good with appropriate default and NOT NULL constraints. However, let's verify the impact on existing records.

✅ Verification successful

Migration is correctly implemented and safe for existing records

The migration properly creates the enum type with valid values ('organization', 'user') and adds the column with appropriate constraints. The default value ensures all existing records will be properly migrated.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any NULL values in the type column and verify all records have been migrated correctly

echo "Checking type column in github_entity table..."
psql -c "SELECT COUNT(*) as total_records, 
         COUNT(CASE WHEN type IS NULL THEN 1 END) as null_types,
         COUNT(CASE WHEN type = 'organization' THEN 1 END) as org_types,
         COUNT(CASE WHEN type = 'user' THEN 1 END) as user_types 
         FROM github_entity;"

Length of output: 489


Script:

#!/bin/bash
# Check for the creation of github_entity_type enum in previous migrations
fd . "packages/db/drizzle" -e sql -x cat {} \; | grep -B 2 -A 2 "github_entity_type"

Length of output: 841


26-26: Verify the removal of branch column.

Please confirm that the branch information is either no longer needed or has been moved to a more appropriate location.

✅ Verification successful

Branch column removal is safe to proceed

The branch column was initially added with a default value of 'main', but there are no active references to it in the codebase. The github_entity table continues to be used throughout the application without requiring branch information.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any references to the branch column in the codebase

echo "Searching for references to branch column..."
rg -i "branch.*github.*organization|github.*organization.*branch" --type ts --type tsx

Length of output: 215


Script:

#!/bin/bash
# Search for branch-related patterns in migrations and TypeScript files

echo "Checking SQL migrations for branch column..."
rg -i "branch" --type sql

echo -e "\nChecking TypeScript files for branch references..."
rg -i "branch.*github" --type ts

echo -e "\nChecking for GitHub entity type definitions..."
ast-grep --pattern 'interface GithubEntity {
  $$$
}'

Length of output: 625


Script:

#!/bin/bash
# Check for any references to github_entity table in TypeScript files

echo "Checking for github_entity table usage..."
rg -i "github_entity" --type ts -A 3

echo -e "\nChecking for GitHub entity schema definitions..."
rg -i "github.*entity.*schema" --type ts -A 5

Length of output: 3004

packages/db/drizzle/meta/_journal.json (1)

432-437: LGTM! Properly formatted migration entry.

The new migration entry follows the established pattern and includes all required fields.

Comment on lines 90 to 93
{githubEntities.map(({ slug, avatarUrl }) => (
<CommandItem
key={id}
value={login}
key={slug}
value={slug}
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

Ensure unique keys in list rendering

Using slug as the key may not guarantee uniqueness across different entities. Use installationId, which is unique for each installation, to prevent potential rendering issues.

Apply this diff to fix the key:

-{githubEntities.map(({ slug, avatarUrl }) => (
+{githubEntities.map(({ installationId, slug, avatarUrl }) => (
-  key={slug}
+  key={installationId}
📝 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
{githubEntities.map(({ slug, avatarUrl }) => (
<CommandItem
key={id}
value={login}
key={slug}
value={slug}
{githubEntities.map(({ installationId, slug, avatarUrl }) => (
<CommandItem
key={installationId}
value={slug}

⚠️ Potential issue

Ensure unique values in CommandItem components

The value prop in CommandItem should be unique to each item. If slug might not be unique, consider using a combination of type and slug or installationId.

Apply this diff to ensure unique value props:

-  value={slug}
+  value={`${type}:${slug}`}

Remember to include type in the destructured properties:

-{githubEntities.map(({ installationId, slug, avatarUrl }) => (
+{githubEntities.map(({ installationId, type, slug, avatarUrl }) => (

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

Comment on lines +126 to +129
const headers = {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
};
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 22, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use Authorization: token instead of Bearer for GitHub authentication.

GitHub API expects the Authorization header to use the token scheme for personal access tokens and installation tokens, not Bearer. Using Bearer might result in authentication errors.

Apply this diff to correct the authorization header:

const headers = {
  "X-GitHub-Api-Version": "2022-11-28",
- authorization: `Bearer ${installationToken.token}`,
+ authorization: `token ${installationToken.token}`,
};
📝 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 headers = {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
};
const headers = {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `token ${installationToken.token}`,
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i'm not sure, using Bearer currently works correctly

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 You're right - I apologize for my earlier suggestion. The GitHub API accepts both Bearer and token schemes for authentication. Since you've confirmed that Bearer works correctly in your implementation, we should keep it as is.

🔗 Analysis chain

@adityachoudhari26 Thank you for the feedback. If the current implementation with Bearer scheme is working correctly, I'll retract my suggestion. Let's keep the existing code as is.



🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for GitHub's official documentation about installation token authentication
curl -s "https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api?apiVersion=2022-11-28" | grep -A 5 -B 5 "Authorization.*token"

# Search for examples in the codebase showing GitHub API authentication
rg -A 2 -B 2 "authorization.*token.*installationToken" --type ts

Length of output: 65785


Script:

#!/bin/bash
# Search GitHub's REST API documentation for installation token authentication
curl -s "https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28" | grep -A 5 -B 5 "Authorization.*token"

# Search for examples in GitHub's official Octokit documentation
curl -s "https://github.com/octokit/octokit.js/blob/master/README.md" | grep -A 5 -B 5 "authorization.*token"

# Search for more examples in the codebase
rg -A 2 -B 2 "authorization.*token" --type ts

Length of output: 131070


Script:

#!/bin/bash
# Search for GitHub API authentication examples in the codebase
rg -A 2 -B 2 "octokit.*auth.*installation" --type ts

# Search for GitHub's official examples
curl -s "https://raw.githubusercontent.com/octokit/octokit.js/master/README.md" | grep -A 5 -B 5 "createAppAuth"

Length of output: 801


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#288
File: apps/event-worker/src/job-dispatch/github.ts:126-129
Timestamp: 2025-01-22T07:38:08.672Z
Learning: GitHub API accepts both 'Bearer' and 'token' schemes for authentication with installation tokens.

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.


If you found this review helpful, would you consider giving us a shout-out on X?
Thank you for using CodeRabbit!

Comment on lines 134 to 135
.get({ ...parsedConfig, headers })
.then((r) => r.data.default_branch));
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

Handle potential errors when retrieving the default branch.

If the API call to get the repository details fails, the code may throw an unhandled exception. It's advisable to include error handling to manage such cases gracefully.

You can wrap the API call in a try-catch block:

- const ref =
-   parsedConfig.ref ??
-   (await octokit.rest.repos
-     .get({ ...parsedConfig, headers })
-     .then((r) => r.data.default_branch));
+ let ref = parsedConfig.ref;
+ if (!ref) {
+   try {
+     const repoData = await octokit.rest.repos.get({
+       owner: parsedConfig.owner,
+       repo: parsedConfig.repo,
+       headers,
+     });
+     ref = repoData.data.default_branch;
+   } catch (error) {
+     logger.error(`Failed to get ref for GitHub action job ${je.id}: ${error.message}`);
+     await db
+       .update(job)
+       .set({
+         status: JobStatus.InvalidJobAgent,
+         message: `Failed to get ref for GitHub action job: ${error.message}`,
+       })
+       .where(eq(job.id, je.id));
+     return;
+   }
+ }

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

Comment on lines +26 to +29
const orgData = await client.orgs.get({
org: String(installationTargetId),
headers: { "X-GitHub-Api-Version": "2022-11-28" },
});
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 22, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use organization login name instead of ID in client.orgs.get.

The orgs.get method requires the organization's login name (org), not the ID. Passing the ID may result in an error.

Modify the code to use the organization's login name:

const orgData = await client.orgs.get({
- org: String(installationTargetId),
+ org: installation.account.login,
  headers: { "X-GitHub-Api-Version": "2022-11-28" },
});

Ensure that installation.account.login is available and represents the organization's login name.

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.

so i thought this too but it seems the api works with passing in the installation target ID here (which is just the id of the org)

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 Thank you for the correction. I apologize for the confusion. If you've confirmed that the API works with the organization ID in this context, then the current implementation is correct.

🔗 Analysis chain
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other usages of orgs.get in the codebase
rg -A 3 -B 3 "orgs\.get\(" 

# Search for any comments or documentation about GitHub organization IDs
rg -i "organization.*id.*github"

Length of output: 4903


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#288
File: apps/webservice/src/app/api/github/installation/route.ts:26-29
Timestamp: 2025-01-22T07:43:17.724Z
Learning: In the GitHub installation flow, the `orgs.get` API endpoint accepts both organization login names and organization IDs (installation target IDs) as the `org` parameter.

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 +152 to +155
const createEntity =
installation.data.target_type === "Organization"
? createOrganizationEntity
: createUserEntity;
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 22, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle unrecognized target_type values.

Currently, the code only handles Organization and defaults to User for any other target_type. If target_type can have other values, it's important to handle them explicitly to avoid unexpected behavior.

Modify the code to include an explicit check:

const createEntity =
  installation.data.target_type === "Organization"
    ? createOrganizationEntity
-   : createUserEntity;
+   : installation.data.target_type === "User"
+   ? createUserEntity
+   : null;

if (createEntity == null) {
  return NextResponse.json(
    { error: "Unsupported installation target type" },
    { status: BAD_REQUEST },
  );
}
📝 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 createEntity =
installation.data.target_type === "Organization"
? createOrganizationEntity
: createUserEntity;
const createEntity =
installation.data.target_type === "Organization"
? createOrganizationEntity
: installation.data.target_type === "User"
? createUserEntity
: null;
if (createEntity == null) {
return NextResponse.json(
{ error: "Unsupported installation target type" },
{ status: BAD_REQUEST },
);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

those are the only two types

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 the clarification. The current implementation is correct then.

ʕ •ᴥ•ʔ


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#288
File: apps/webservice/src/app/api/github/installation/route.ts:152-155
Timestamp: 2025-01-22T07:44:33.746Z
Learning: GitHub installation target_type can only have two possible values: "Organization" and "User". No additional error handling is needed for other values.

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 +243 to +265
const getMembers = async (
page: number,
fetchedMembers: Member[],
): Promise<Member[]> => {
const { data } = await installationOctokit.orgs.listMembers({
org: installation.account?.login ?? "",
per_page: 100,
page,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});
const members = [...data, ...fetchedMembers];
if (data.length < 100) return members;
return getMembers(page + 1, members);
};

const members = await getMembers(1, []);
const isUserInGithubOrg =
members.find((m) => m.id === input.githubUserId) != null;
if (!isUserInGithubOrg) return null;

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

Eliminate manual 'authorization' header in 'getMembers' function.

In the getMembers function, manually setting the 'authorization' header is unnecessary since installationOctokit handles authentication. Removing it simplifies the code and relies on the client's internal mechanisms.

Apply this diff:

               headers: {
                 "X-GitHub-Api-Version": "2022-11-28",
-                authorization: `Bearer ${installationToken.token}`,
               },
📝 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 getMembers = async (
page: number,
fetchedMembers: Member[],
): Promise<Member[]> => {
const { data } = await installationOctokit.orgs.listMembers({
org: installation.account?.login ?? "",
per_page: 100,
page,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});
const members = [...data, ...fetchedMembers];
if (data.length < 100) return members;
return getMembers(page + 1, members);
};
const members = await getMembers(1, []);
const isUserInGithubOrg =
members.find((m) => m.id === input.githubUserId) != null;
if (!isUserInGithubOrg) return null;
const getMembers = async (
page: number,
fetchedMembers: Member[],
): Promise<Member[]> => {
const { data } = await installationOctokit.orgs.listMembers({
org: installation.account?.login ?? "",
per_page: 100,
page,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
const members = [...data, ...fetchedMembers];
if (data.length < 100) return members;
return getMembers(page + 1, members);
};
const members = await getMembers(1, []);
const isUserInGithubOrg =
members.find((m) => m.id === input.githubUserId) != null;
if (!isUserInGithubOrg) return null;

Comment on lines 180 to 205
})
.then(async ({ data: installation }) => {
const installationOctokit = getOctokitInstallation(installation.id);
const installationToken = (await installationOctokit.auth({
type: "installation",
installationId: installation.id,
})) as { token: string };

type Repo =
RestEndpointMethodTypes["repos"]["listForOrg"]["response"]["data"][number];
const repos: Repo[] = [];

const getRepos = async (page: number) => {
const { data } = await installationOctokit.repos.listForOrg({
org: input.owner,
per_page: 100,
page,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});
repos.push(...data);
if (data.length < 100) return;
return getRepos(page + 1);
};
})) ?? {};

await getRepos(1);
return repos;
}),
),
if (installation == null)
throw new TRPCError({
code: "NOT_FOUND",
message: "Installation not found",
});

workflows: createTRPCRouter({
list: protectedProcedure
.meta({
authorizationCheck: ({ canUser, input }) =>
canUser.perform(Permission.WorkspaceListIntegrations).on({
type: "workspace",
id: input.workspaceId,
}),
})
.input(
z.object({
installationId: z.number(),
owner: z.string(),
repo: z.string(),
workspaceId: z.string().uuid(),
}),
)
.query(async ({ input }) => {
const installationOctokit = getOctokitInstallation(
input.installationId,
);
const installationOctokit = getOctokitInstallation(installation.id);
const installationToken = (await installationOctokit.auth({
type: "installation",
installationId: installation.id,
})) as { token: string };

const installationToken = (await installationOctokit.auth({
type: "installation",
installationId: input.installationId,
})) as { token: string };

const workflows: RestEndpointMethodTypes["actions"]["listRepoWorkflows"]["response"]["data"]["workflows"] =
[];

const getWorkflows = async (page: number) => {
const { data } = await installationOctokit.actions.listRepoWorkflows({
...input,
page,
per_page: 100,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});
workflows.push(...data.workflows);
if (data.workflows.length < 100) return;
return getWorkflows(page + 1);
};

await getWorkflows(1);
return workflows;
}),
}),
return getRepos(
1,
[],
installationOctokit,
installationToken,
input.owner,
);
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use 'getOctokit()' to ensure the GitHub bot is configured before API calls.

Directly accessing octokit can lead to null reference errors if the GitHub bot is not configured. The getOctokit() function includes a check and throws an error if the bot is not set up, ensuring safer execution.

Apply this diff to use getOctokit():

-        (await octokit?.apps.getInstallation({
+        (await getOctokit().apps.getInstallation({
             installation_id: input.installationId,
           })) ?? {};
📝 Committable suggestion

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

Suggested change
.query(async ({ input }) => {
const { data: installation } =
(await octokit?.apps.getInstallation({
installation_id: input.installationId,
})
.then(async ({ data: installation }) => {
const installationOctokit = getOctokitInstallation(installation.id);
const installationToken = (await installationOctokit.auth({
type: "installation",
installationId: installation.id,
})) as { token: string };
type Repo =
RestEndpointMethodTypes["repos"]["listForOrg"]["response"]["data"][number];
const repos: Repo[] = [];
const getRepos = async (page: number) => {
const { data } = await installationOctokit.repos.listForOrg({
org: input.owner,
per_page: 100,
page,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});
repos.push(...data);
if (data.length < 100) return;
return getRepos(page + 1);
};
})) ?? {};
await getRepos(1);
return repos;
}),
),
if (installation == null)
throw new TRPCError({
code: "NOT_FOUND",
message: "Installation not found",
});
workflows: createTRPCRouter({
list: protectedProcedure
.meta({
authorizationCheck: ({ canUser, input }) =>
canUser.perform(Permission.WorkspaceListIntegrations).on({
type: "workspace",
id: input.workspaceId,
}),
})
.input(
z.object({
installationId: z.number(),
owner: z.string(),
repo: z.string(),
workspaceId: z.string().uuid(),
}),
)
.query(async ({ input }) => {
const installationOctokit = getOctokitInstallation(
input.installationId,
);
const installationOctokit = getOctokitInstallation(installation.id);
const installationToken = (await installationOctokit.auth({
type: "installation",
installationId: installation.id,
})) as { token: string };
const installationToken = (await installationOctokit.auth({
type: "installation",
installationId: input.installationId,
})) as { token: string };
const workflows: RestEndpointMethodTypes["actions"]["listRepoWorkflows"]["response"]["data"]["workflows"] =
[];
const getWorkflows = async (page: number) => {
const { data } = await installationOctokit.actions.listRepoWorkflows({
...input,
page,
per_page: 100,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});
workflows.push(...data.workflows);
if (data.workflows.length < 100) return;
return getWorkflows(page + 1);
};
await getWorkflows(1);
return workflows;
}),
}),
return getRepos(
1,
[],
installationOctokit,
installationToken,
input.owner,
);
}),
.query(async ({ input }) => {
const { data: installation } =
(await getOctokit().apps.getInstallation({
installation_id: input.installationId,
})) ?? {};
if (installation == null)
throw new TRPCError({
code: "NOT_FOUND",
message: "Installation not found",
});
const installationOctokit = getOctokitInstallation(installation.id);
const installationToken = (await installationOctokit.auth({
type: "installation",
installationId: installation.id,
})) as { token: string };
return getRepos(
1,
[],
installationOctokit,
installationToken,
input.owner,
);
}),

Comment on lines +123 to +162
const getRepos = async (
page: number,
fetchedRepos: Repo[],
installationOctokit: InstallationOctokitClient,
installationToken: { token: string },
owner: string,
) => {
const { data } = await installationOctokit.repos.listForOrg({
org: owner,
per_page: 100,
page,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});

const reposWithWorkflows = await Promise.all(
data.map(async (repo) => {
const workflows = await getWorkflows(
1,
[],
repo,
installationOctokit,
installationToken,
);
return { ...repo, workflows };
}),
);

const repos = [...reposWithWorkflows, ...fetchedRepos];
if (reposWithWorkflows.length < 100) return repos;
return getRepos(
page + 1,
repos,
installationOctokit,
installationToken,
owner,
);
};
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

Remove redundant 'authorization' header in 'getRepos' function.

Similar to the previous point, the installationOctokit client in getRepos is already authenticated. The manual addition of the 'authorization' header is unnecessary and should be omitted to prevent potential authentication issues.

Apply this diff to correct the headers:

       headers: {
         "X-GitHub-Api-Version": "2022-11-28",
-        authorization: `Bearer ${installationToken.token}`,
       },
📝 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 getRepos = async (
page: number,
fetchedRepos: Repo[],
installationOctokit: InstallationOctokitClient,
installationToken: { token: string },
owner: string,
) => {
const { data } = await installationOctokit.repos.listForOrg({
org: owner,
per_page: 100,
page,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});
const reposWithWorkflows = await Promise.all(
data.map(async (repo) => {
const workflows = await getWorkflows(
1,
[],
repo,
installationOctokit,
installationToken,
);
return { ...repo, workflows };
}),
);
const repos = [...reposWithWorkflows, ...fetchedRepos];
if (reposWithWorkflows.length < 100) return repos;
return getRepos(
page + 1,
repos,
installationOctokit,
installationToken,
owner,
);
};
const getRepos = async (
page: number,
fetchedRepos: Repo[],
installationOctokit: InstallationOctokitClient,
installationToken: { token: string },
owner: string,
) => {
const { data } = await installationOctokit.repos.listForOrg({
org: owner,
per_page: 100,
page,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
const reposWithWorkflows = await Promise.all(
data.map(async (repo) => {
const workflows = await getWorkflows(
1,
[],
repo,
installationOctokit,
installationToken,
);
return { ...repo, workflows };
}),
);
const repos = [...reposWithWorkflows, ...fetchedRepos];
if (reposWithWorkflows.length < 100) return repos;
return getRepos(
page + 1,
repos,
installationOctokit,
installationToken,
owner,
);
};

Comment on lines +95 to +121
const getWorkflows = async (
page: number,
fetchedWorkflows: Workflow[],
repo: RestEndpointMethodTypes["repos"]["listForOrg"]["response"]["data"][number],
installationOctokit: InstallationOctokitClient,
installationToken: { token: string },
) => {
const { data } = await installationOctokit.actions.listRepoWorkflows({
repo: repo.name,
owner: repo.owner.login,
page,
per_page: 100,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});
const workflows = [...data.workflows, ...fetchedWorkflows];
if (data.workflows.length < 100) return workflows;
return getWorkflows(
page + 1,
workflows,
repo,
installationOctokit,
installationToken,
);
};
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 22, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid manually setting the 'authorization' header in 'getWorkflows' function.

In the getWorkflows function, the installationOctokit client is already authenticated. Manually setting the 'authorization' header is unnecessary and could interfere with the client's authentication handling. The Octokit library manages authentication internally when the client is properly configured.

Apply this diff to remove the redundant header:

       headers: {
         "X-GitHub-Api-Version": "2022-11-28",
-        authorization: `Bearer ${installationToken.token}`,
       },
📝 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 getWorkflows = async (
page: number,
fetchedWorkflows: Workflow[],
repo: RestEndpointMethodTypes["repos"]["listForOrg"]["response"]["data"][number],
installationOctokit: InstallationOctokitClient,
installationToken: { token: string },
) => {
const { data } = await installationOctokit.actions.listRepoWorkflows({
repo: repo.name,
owner: repo.owner.login,
page,
per_page: 100,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
authorization: `Bearer ${installationToken.token}`,
},
});
const workflows = [...data.workflows, ...fetchedWorkflows];
if (data.workflows.length < 100) return workflows;
return getWorkflows(
page + 1,
workflows,
repo,
installationOctokit,
installationToken,
);
};
const getWorkflows = async (
page: number,
fetchedWorkflows: Workflow[],
repo: RestEndpointMethodTypes["repos"]["listForOrg"]["response"]["data"][number],
installationOctokit: InstallationOctokitClient,
installationToken: { token: string },
) => {
const { data } = await installationOctokit.actions.listRepoWorkflows({
repo: repo.name,
owner: repo.owner.login,
page,
per_page: 100,
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
});
const workflows = [...data.workflows, ...fetchedWorkflows];
if (data.workflows.length < 100) return workflows;
return getWorkflows(
page + 1,
workflows,
repo,
installationOctokit,
installationToken,
);
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not sure I understand. I am setting the token per request, each request is independent no?

Copy link
Contributor

Choose a reason for hiding this comment

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

@adityachoudhari26 I understand your concern about independent requests. However, when you create the installationOctokit instance using getOctokitInstallation(), it's already configured with the installation authentication strategy (createAppAuth). The Octokit client automatically:

  1. Manages the token lifecycle
  2. Adds the authorization header to each request
  3. Handles token refresh when needed

So when you call installationOctokit.actions.listRepoWorkflows(), it will automatically authenticate the request. Setting the token manually in headers is redundant and could potentially interfere with Octokit's internal token management.

🤔


✏️ Learnings added
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#288
File: packages/api/src/router/github.ts:95-121
Timestamp: 2025-01-22T07:46:21.457Z
Learning: When using Octokit client initialized with createAppAuth strategy, avoid manually setting authorization headers as the client automatically handles authentication for each request.

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.

@@ -37,7 +33,7 @@ export const OrgActionDropdown: React.FC<OrgActionDropdownProps> = ({
</DropdownMenuTrigger>
<DropdownMenuContent>
<Link
href={`${githubConfig.url}/organizations/${org.organizationName}/settings/installations/${org.installationId}`}
href={`${githubConfig.url}/organizations/${entity.slug}/settings/installations/${entity.installationId}`}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

URL pattern needs to be adjusted for user installations

The current URL pattern only works for organization entities but not for user entities. Based on GitHub's URL structure:

  • For organizations: /organizations/${entity.slug}/settings/installations/${entity.installationId}
  • For users: /${entity.slug}/settings/installations/${entity.installationId}

The URL construction should be conditional based on entity.type.

🔗 Analysis chain

Verify the URL pattern for GitHub settings.

Please ensure that the URL pattern ${githubConfig.url}/organizations/${entity.slug}/settings/installations/${entity.installationId} works for both organization and user entities.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the URL pattern is valid for both organization and user entities

# Search for any similar URL patterns in the codebase
rg -A 2 "organizations/.*?/settings/installations" 

# Check GitHub API documentation or similar URL constructions
curl -s https://api.github.com/meta | jq '.installations_url'

Length of output: 651


Script:

#!/bin/bash
# Search for installationId usage and type definitions
echo "=== Searching for installationId usage ==="
rg -A 3 "installationId" 

echo -e "\n=== Looking for entity type definitions ==="
ast-grep --pattern 'interface $_ {
  $$$
  installationId: $_
  $$$
}'

echo -e "\n=== Searching for GitHub URL construction ==="
rg -A 2 "githubConfig.url"

Length of output: 12752

</Popover>

<Input
placeholder="Ref (uses default branch if not set)"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
placeholder="Ref (uses default branch if not set)"
placeholder="Git reference (uses repositories default if not set)"

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/SelectPreconnectedEntityDialogContent.tsx (1)

Line range hint 41-54: Consider enhancing error handling in the save handler.

While the null checks are good, consider adding error handling to provide feedback to the user when the mutation fails.

 const handleSave = async () => {
   if (value == null) return;
   const entity = githubEntities.find((e) => e.slug === value);
   if (entity == null) return;

-  await githubEntityCreate.mutateAsync({
-    ...entity,
-    workspaceId,
-  });
+  try {
+    await githubEntityCreate.mutateAsync({
+      ...entity,
+      workspaceId,
+    });
+    onSave();
+    router.refresh();
+    utils.job.agent.byWorkspaceId.invalidate(workspaceId);
+  } catch (error) {
+    // Handle error appropriately (e.g., show toast notification)
+    console.error('Failed to create GitHub entity:', error);
+  }
-  onSave();
-  router.refresh();
-  utils.job.agent.byWorkspaceId.invalidate(workspaceId);
 };
apps/event-worker/src/job-dispatch/github.ts (1)

131-141: Consider adding retry logic for ref retrieval.

The current implementation handles errors but doesn't attempt to retry the operation. GitHub API calls can sometimes fail transiently.

Consider implementing exponential backoff:

-  const ref =
-    parsedConfig.ref ??
-    (await octokit.rest.repos
-      .get({ ...parsedConfig, headers })
-      .then((r) => r.data.default_branch)
-      .catch((e) => {
-        logger.error(`Failed to get ref for github action job ${je.id}`, {
-          error: e,
-        });
-        return null;
-      }));
+  const getRefWithRetry = async (attempt = 1, maxAttempts = 3) => {
+    try {
+      const response = await octokit.rest.repos.get({ ...parsedConfig, headers });
+      return response.data.default_branch;
+    } catch (e) {
+      if (attempt === maxAttempts) {
+        logger.error(`Failed to get ref for github action job ${je.id}`, {
+          error: e,
+          attempt,
+        });
+        return null;
+      }
+      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
+      return getRefWithRetry(attempt + 1, maxAttempts);
+    }
+  };
+
+  const ref = parsedConfig.ref ?? await getRefWithRetry();
packages/api/src/router/github.ts (2)

95-121: Consider implementing cursor-based pagination.

The current implementation uses page-based pagination which can lead to missing or duplicate items if the underlying data changes between requests.

Consider using cursor-based pagination if supported by the GitHub API:

const getWorkflows = async (
  cursor: string | null,
  fetchedWorkflows: Workflow[],
  repo: Repo,
  installationOctokit: InstallationOctokitClient,
  installationToken: { token: string },
) => {
  const { data } = await installationOctokit.actions.listRepoWorkflows({
    repo: repo.name,
    owner: repo.owner.login,
    per_page: 100,
    after: cursor,
    headers: {
      "X-GitHub-Api-Version": "2022-11-28",
      authorization: `Bearer ${installationToken.token}`,
    },
  });
  
  const workflows = [...fetchedWorkflows, ...data.workflows];
  if (!data.pagination?.hasNextPage) return workflows;
  
  return getWorkflows(
    data.pagination.endCursor,
    workflows,
    repo,
    installationOctokit,
    installationToken,
  );
};

236-252: Consider implementing rate limiting protection.

The recursive member fetching could hit GitHub API rate limits. Consider implementing rate limit checking and backoff.

Add rate limit protection:

const getMembers = async (
  page: number,
  fetchedMembers: Member[],
): Promise<Member[]> => {
  const { headers } = await installationOctokit.rateLimit.get();
  const remaining = parseInt(headers['x-ratelimit-remaining'] ?? '0');
  
  if (remaining < 10) {
    const resetTime = parseInt(headers['x-ratelimit-reset'] ?? '0') * 1000;
    const waitTime = resetTime - Date.now();
    if (waitTime > 0) {
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }
  }
  
  const { data } = await installationOctokit.orgs.listMembers({
    org: installation.account?.login ?? "",
    per_page: 100,
    page,
    headers: {
      "X-GitHub-Api-Version": "2022-11-28",
      authorization: `Bearer ${installationToken.token}`,
    },
  });
  
  const members = [...data, ...fetchedMembers];
  if (data.length < 100) return members;
  return getMembers(page + 1, members);
};
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 217011a and 9fcf3ca.

📒 Files selected for processing (6)
  • apps/event-worker/src/job-dispatch/github.ts (4 hunks)
  • apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/EntityActionDropdown.tsx (2 hunks)
  • apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/SelectPreconnectedEntityDialogContent.tsx (3 hunks)
  • apps/webservice/src/components/form/job-agent/JobAgentGitHubConfig.tsx (2 hunks)
  • packages/api/src/router/github.ts (8 hunks)
  • packages/db/drizzle/meta/_journal.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/db/drizzle/meta/_journal.json
🧰 Additional context used
📓 Path-based instructions (5)
apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/EntityActionDropdown.tsx (1)

Pattern **/*.{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/components/form/job-agent/JobAgentGitHubConfig.tsx (1)

Pattern **/*.{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]/settings/workspace/integrations/(integration)/github/SelectPreconnectedEntityDialogContent.tsx (1)

Pattern **/*.{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/event-worker/src/job-dispatch/github.ts (1)

Pattern **/*.{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/github.ts (1)

Pattern **/*.{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.

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Typecheck
  • GitHub Check: Lint
  • GitHub Check: build (linux/amd64)
🔇 Additional comments (10)
apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/SelectPreconnectedEntityDialogContent.tsx (4)

19-30: LGTM! Well-structured type definition.

The PreconnectedEntityComboboxProps type is well-defined with proper typing for GitHub entities, including a clear discriminator field for entity types.


32-34: LGTM! Clean component declaration.

The component declaration follows React best practices with proper type annotations.


90-106: Previous key uniqueness concerns have been addressed.

The implementation now uses installationId as the key, which ensures uniqueness across different entities.


124-124: LGTM! Proper button state management.

The button's disabled state correctly handles both the unselected state and pending mutation state.

apps/webservice/src/app/[workspaceSlug]/settings/workspace/integrations/(integration)/github/EntityActionDropdown.tsx (2)

16-19: LGTM! Props type definition is well-structured.

The type definition clearly specifies the required props and uses appropriate types from the schema.


25-29: LGTM! URL construction handles both organization and user entities.

The conditional URL construction correctly handles both organization and user installation URLs based on the entity type.

apps/webservice/src/components/form/job-agent/JobAgentGitHubConfig.tsx (3)

34-70: LGTM! Repository selection UI is well-implemented.

The implementation includes:

  • Proper accessibility attributes
  • Clear visual feedback
  • Efficient state management

72-110: LGTM! Workflow selection UI follows consistent patterns.

The workflow selection follows the same pattern as repository selection, maintaining consistency in the UI.


112-117: LGTM! Git reference input is properly implemented.

The input field for git reference includes:

  • Clear placeholder text
  • Proper value handling
  • Appropriate width constraint
apps/event-worker/src/job-dispatch/github.ts (1)

Line range hint 21-69: LGTM! Well-structured helper function for entity retrieval.

The getGithubEntity function efficiently:

  • Handles both release and runbook job triggers
  • Uses Promise.all for parallel execution
  • Returns the first matching entity

@adityachoudhari26 adityachoudhari26 merged commit 49ef30c into main Jan 22, 2025
11 checks passed
@adityachoudhari26 adityachoudhari26 deleted the refactor-gh branch January 22, 2025 07:55
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