Skip to content

Support for github enterprise server(self hosted github) #646

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

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

Conversation

thesujai
Copy link
Contributor

@thesujai thesujai commented Apr 18, 2025

Linked Issue(s)

Fixes #619

Acceptance Criteria fulfillment

Proposed changes (including videos or screenshots)

Further comments

This exactly replicates the custom url implementation for Gitlab

Summary by CodeRabbit

  • New Features

    • Added support for connecting to GitHub Enterprise or custom GitHub API domains throughout the application.
    • Users can now specify a custom domain when configuring GitHub integrations.
  • Enhancements

    • API requests to GitHub dynamically use the appropriate base URL based on user or organization settings.
    • The configuration modal for GitHub now includes an optional input field for a custom domain.
  • Bug Fixes

    • Improved handling of custom domains to ensure API calls target the correct endpoints.

Copy link

coderabbitai bot commented Apr 18, 2025

Walkthrough

The changes introduce support for specifying a custom GitHub API domain, enabling integration with GitHub Enterprise or self-hosted GitHub instances. This is achieved by updating backend service constructors and internal logic to accept and propagate an optional domain parameter, which determines the API base URL. Helper functions are added to retrieve and construct the appropriate API URLs based on organization-specific integration metadata. On the frontend, UI components and utility functions are enhanced to allow users to input a custom domain and ensure API requests use the correct endpoint. No changes are made to core business logic or error handling.

Changes

File(s) Change Summary
backend/analytics_server/mhq/exapi/github.py Modified GithubApiService to accept an optional domain parameter, set the base URL accordingly, and added a helper method for URL construction.
backend/analytics_server/mhq/service/code/sync/etl_github_handler.py
.../etl_github_actions_handler.py
Added nested helper functions to retrieve a custom GitHub domain from integration metadata and updated instantiation of GithubApiService to use this domain.
backend/analytics_server/mhq/service/external_integrations_service.py Updated methods to pass an optional custom domain when creating GithubApiService instances.
web-server/pages/api/internal/[org_id]/utils.ts Introduced helper functions to fetch and construct GitHub API URLs dynamically based on a custom domain from the database; updated API calls to use these helpers.
web-server/src/content/Dashboards/ConfigureGithubModalBody.tsx Enhanced UI to support an optional "Custom Domain" input, updated state management and submission logic to include the custom domain in validation and linking flows.
web-server/src/utils/auth.ts Updated checkGitHubValidity and getMissingPATScopes to accept an optional customDomain parameter and construct API URLs accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as ConfigureGithubModalBody
    participant AuthUtils as auth.ts
    participant BackendAPI as backend services

    User->>UI: Enter GitHub token and optional custom domain
    UI->>AuthUtils: checkGitHubValidity(token, customDomain)
    AuthUtils->>BackendAPI: Request with baseUrl (customDomain or default)
    BackendAPI-->>AuthUtils: Respond with validity
    AuthUtils-->>UI: Return result
    UI->>BackendAPI: linkProvider(token, customDomain)
    BackendAPI->>BackendAPI: Use custom domain for API calls
    BackendAPI-->>UI: Confirmation
Loading

Assessment against linked issues

Objective Addressed Explanation
Allow changing GitHub server hostname for self-hosted/enterprise instances (#619)
Support specifying the GitHub API endpoint via configuration or user input (#619)
Ensure API requests use the custom hostname when provided (#619)

Poem

A bunny hops with nimble cheer,
Now GitHub’s home is far or near!
Custom domains, we now embrace,
For Enterprise or any place.
With every hop, a hostname new—
This rabbit codes for all of you!
🐇✨

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

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.

@thesujai thesujai changed the title Supported for github enterprise server(self hosted github) Support for github enterprise server(self hosted github) Apr 18, 2025
Copy link

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

🔭 Outside diff range comments (2)
backend/analytics_server/mhq/service/code/sync/etl_github_handler.py (1)

1-399: 💡 Verification agent

🧩 Analysis chain

Fix Black formatting issues.

The file is flagged by the Black integration pipeline - please run Black to fix the formatting.


🏁 Script executed:

#!/bin/bash
# Run black to check formatting issues
black --check backend/analytics_server/mhq/service/code/sync/etl_github_handler.py

Length of output: 180


To verify formatting, let’s install Black and re-run the check:


🏁 Script executed:

#!/bin/bash
# Install Black formatter and re-check the file
pip install black
black --check backend/analytics_server/mhq/service/code/sync/etl_github_handler.py

Length of output: 1364


Let’s run Black via Python’s module path to ensure it’s found:


🏁 Script executed:

#!/bin/bash
python3 -m black --check backend/analytics_server/mhq/service/code/sync/etl_github_handler.py

Length of output: 225


Please fix Black formatting issues

The file backend/analytics_server/mhq/service/code/sync/etl_github_handler.py is not formatted according to Black (Black reports it “would reformat” this file). Please run Black locally to apply the required changes:

python3 -m black backend/analytics_server/mhq/service/code/sync/etl_github_handler.py

After formatting, commit the updated file.

🧰 Tools
🪛 GitHub Actions: Black Integration

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

web-server/src/content/Dashboards/ConfigureGithubModalBody.tsx (1)

103-112: 🛠️ Refactor suggestion

Update dependency array for handleSubmission

The dependency array for handleSubmission is missing customDomain.valueRef which is used in the function.

Add the missing dependency:

}, [
  dispatch,
  enqueueSnackbar,
  isLoading.false,
  isLoading.true,
  onClose,
  orgId,
  setError,
- token.value
+ token.value,
+ customDomain.valueRef
]);
🧹 Nitpick comments (5)
backend/analytics_server/mhq/exapi/github.py (1)

25-32: Fix trailing whitespace and improve formatting.

The GitHub API service constructor has been updated to support custom domains, but there are formatting issues.

-    def __init__(self, access_token: str, domain = "https://api.github.com"):
+    def __init__(self, access_token: str, domain: str = "https://api.github.com"):
     self._token = access_token
     self.base_url = self._get_api_url(domain)
     self._g = Github(
-            self._token, 
+            self._token,
             base_url=self.base_url,
             per_page=PAGE_SIZE
         )

Note the addition of the type hint for domain and removal of trailing whitespace.

web-server/pages/api/internal/[org_id]/utils.ts (1)

324-327: Apply URL sanitization to GraphQL endpoint

Unlike the REST API URL construction which handles potential double slashes, the GraphQL URL construction doesn't have this safeguard.

Add the same safeguard to the GraphQL URL:

const getGitHubGraphQLUrl = async (): Promise<string> => {
  const customDomain = await getGitHubCustomDomain();
-  return customDomain ? `https://${customDomain}/api/graphql` : GITHUB_API_URL;
+  const url = customDomain 
+    ? `https://${customDomain}/api/graphql` 
+    : GITHUB_API_URL;
+  return url.replace(/\/+/g, '/');
};
web-server/src/utils/auth.ts (2)

42-44: Add slash normalization for API URL construction

When building the API URL path, multiple slashes could be introduced, similar to what's handled in the utils.ts file.

Normalize slashes in URL construction:

-  await axios.get(`${baseUrl}/user`, {
+  await axios.get(`${baseUrl}/user`.replace(/\/+/g, '/'), {

54-55: Update function documentation and dependency array

The getMissingPATScopes function signature has changed, but the JSDoc and dependency array in the parent component are not updated.

Add JSDoc to describe the new parameter:

+ /**
+  * Gets the missing PAT scopes for a GitHub token
+  * @param pat - The personal access token to check
+  * @param customDomain - Optional custom domain for GitHub Enterprise
+  * @returns Array of missing scopes
+  */
export const getMissingPATScopes = async (pat: string, customDomain?: string) => {
web-server/src/content/Dashboards/ConfigureGithubModalBody.tsx (1)

74-77: Add documentation for the custom_domain parameter

The code sends custom_domain as part of meta data but there's no documentation about its format requirements.

Add a comment explaining the expected format:

return await linkProvider(token.value, orgId, Integration.GITHUB, {
+ // custom_domain should be a domain name with or without protocol
+ // e.g., "github.mycompany.com" or "https://github.mycompany.com"
  custom_domain: customDomain.valueRef.current
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a545c0 and 2dbf898.

📒 Files selected for processing (7)
  • backend/analytics_server/mhq/exapi/github.py (1 hunks)
  • backend/analytics_server/mhq/service/code/sync/etl_github_handler.py (2 hunks)
  • backend/analytics_server/mhq/service/external_integrations_service.py (1 hunks)
  • backend/analytics_server/mhq/service/workflows/sync/etl_github_actions_handler.py (2 hunks)
  • web-server/pages/api/internal/[org_id]/utils.ts (3 hunks)
  • web-server/src/content/Dashboards/ConfigureGithubModalBody.tsx (6 hunks)
  • web-server/src/utils/auth.ts (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
web-server/src/utils/auth.ts (1)
web-server/http-server.js (1)
  • axios (5-5)
web-server/pages/api/internal/[org_id]/utils.ts (1)
web-server/src/constants/db.ts (1)
  • Row (557-560)
🪛 GitHub Actions: Black Integration
backend/analytics_server/mhq/service/code/sync/etl_github_handler.py

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

backend/analytics_server/mhq/service/workflows/sync/etl_github_actions_handler.py

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

backend/analytics_server/mhq/exapi/github.py

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: All file linting
🔇 Additional comments (14)
backend/analytics_server/mhq/service/external_integrations_service.py (4)

28-29: Consistent custom domain parameter propagation.

The GitHub API service instance is now created with the custom domain parameter, which allows connecting to GitHub Enterprise instances.


36-37: LGTM: GitHub API service initialized with custom domain.

The GitHub API service is properly initialized with the custom domain parameter.


43-44: LGTM: GitHub API service initialized with custom domain.

The GitHub API service is properly initialized with the custom domain parameter.


50-51: LGTM: GitHub API service initialized with custom domain.

The GitHub API service is properly initialized with the custom domain parameter.

backend/analytics_server/mhq/service/code/sync/etl_github_handler.py (1)

392-394: LGTM: GitHub API service initialized with custom domain.

The GitHub API service is properly initialized with both the access token and custom domain.

backend/analytics_server/mhq/service/workflows/sync/etl_github_actions_handler.py (2)

203-204: LGTM: GitHub API service initialized with custom domain.

The GitHub API service is properly initialized with both the access token and custom domain.


1-206:

❓ Verification inconclusive

Fix Black formatting issues.

The file is flagged by the Black integration pipeline - please run Black to fix the formatting.


🏁 Script executed:

#!/bin/bash
# Run black to check formatting issues
black --check backend/analytics_server/mhq/service/workflows/sync/etl_github_actions_handler.py

Length of output: 193


Apply Black formatting
The CI indicates formatting issues in this file. Please run the following locally and commit the updated file:

black backend/analytics_server/mhq/service/workflows/sync/etl_github_actions_handler.py

Then push the reformatted file so the Black check passes.

🧰 Tools
🪛 Ruff (0.8.2)

70-73: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🪛 GitHub Actions: Black Integration

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

backend/analytics_server/mhq/exapi/github.py (6)

34-38: LGTM: API URL helper method implementation.

This helper method correctly handles the differences between GitHub.com and GitHub Enterprise Server API URLs.


52-57: LGTM: check_pat method is using the base_url properly.

The check_pat method now correctly uses the base_url when making the API request, which will work with GitHub Enterprise.

🧰 Tools
🪛 Ruff (0.8.2)

56-56: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


157-161: LGTM: get_contributors method is using the base_url properly.

The method correctly uses the base_url to construct the GitHub API URL.


209-213: LGTM: get_org_members method is using the base_url properly.

The method correctly uses the base_url to construct the GitHub API URL.


247-255: LGTM: get_workflow_runs method is using the base_url properly.

The method correctly uses the base_url to construct the GitHub API URL.


1-277:

❓ Verification inconclusive

Fix Black formatting issues.

The file is flagged by the Black integration pipeline - please run Black to fix the formatting.


🏁 Script executed:

#!/bin/bash
# Run black to check formatting issues
black --check backend/analytics_server/mhq/exapi/github.py

Length of output: 156


Install and run Black to fix formatting issues

Our sandbox couldn’t verify formatting because black isn’t available here. Please ensure Black is installed in your environment and rerun:

pip install black
black --check backend/analytics_server/mhq/exapi/github.py
black backend/analytics_server/mhq/exapi/github.py

Once formatting passes, commit the changes.

🧰 Tools
🪛 Ruff (0.8.2)

56-56: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🪛 GitHub Actions: Black Integration

[error] 1-1: Black formatting check failed. File would be reformatted. Run 'black' to fix code style issues.

web-server/pages/api/internal/[org_id]/utils.ts (1)

55-56: 🛠️ Refactor suggestion

Update the function call to use the new custom domain helper functions

This function still uses the hardcoded API URL from line 9, but should leverage the new getGitHubRestApiUrl helper function.

Update the function to use the helper:

const searchRepoWithURL = async (searchString: string) => {
  const apiUrl = await getGitHubRestApiUrl(`repos/${searchString}`);
  const response = await axios.get<GithubRepo>(apiUrl);
  const repo = response.data;
  return [
    // ... rest of code

Likely an incorrect or invalid review comment.

Comment on lines +361 to +380
def _get_custom_github_domain() -> Optional[str]:
DEFAULT_DOMAIN = "https://api.github.com"
core_repo_service = CoreRepoService()
integrations = core_repo_service.get_org_integrations_for_names(
org_id, [UserIdentityProvider.GITHUB.value]
)

github_domain = (
integrations[0].provider_meta.get("custom_domain")
if integrations[0].provider_meta
else None
)

if not github_domain:
LOG.warn(
f"Custom domain not found for intergration for org {org_id} and provider {UserIdentityProvider.GITLAB.value}"
)
return DEFAULT_DOMAIN

return github_domain
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect provider name in log message.

The warning message incorrectly refers to GitLab instead of GitHub.

-                f"Custom domain not found for intergration for org {org_id} and provider {UserIdentityProvider.GITLAB.value}"
+                f"Custom domain not found for intergration for org {org_id} and provider {UserIdentityProvider.GITHUB.value}"

Also, there's a typo in "intergration" that should be "integration".

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

Comment on lines +172 to +191
def _get_custom_github_domain() -> Optional[str]:
DEFAULT_DOMAIN = "https://api.github.com"
core_repo_service = CoreRepoService()
integrations = core_repo_service.get_org_integrations_for_names(
org_id, [UserIdentityProvider.GITHUB.value]
)

github_domain = (
integrations[0].provider_meta.get("custom_domain")
if integrations[0].provider_meta
else None
)

if not github_domain:
LOG.warn(
f"Custom domain not found for intergration for org {org_id} and provider {UserIdentityProvider.GITLAB.value}"
)
return DEFAULT_DOMAIN

return github_domain
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect provider name in log message.

The warning message incorrectly refers to GitLab instead of GitHub.

-                f"Custom domain not found for intergration for org {org_id} and provider {UserIdentityProvider.GITLAB.value}"
+                f"Custom domain not found for intergration for org {org_id} and provider {UserIdentityProvider.GITHUB.value}"

Also, there's a typo in "intergration" that should be "integration".

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

Comment on lines +310 to +316
const getGitHubCustomDomain = async (): Promise<string | null> => {
const provider_meta = await db('Integration')
.where('name', Integration.GITHUB)
.then((r: Row<'Integration'>[]) => r.map((item) => item.provider_meta));

return head(provider_meta || [])?.custom_domain || null;
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for custom domain retrieval

The getGitHubCustomDomain function lacks error handling for database queries or malformed domain values, unlike the similar replaceURL function for GitLab (lines 291-308).

Add a try-catch block to handle potential errors:

const getGitHubCustomDomain = async (): Promise<string | null> => {
+  try {
    const provider_meta = await db('Integration')
      .where('name', Integration.GITHUB)
      .then((r: Row<'Integration'>[]) => r.map((item) => item.provider_meta));

    return head(provider_meta || [])?.custom_domain || null;
+  } catch (error) {
+    console.error('Error retrieving GitHub custom domain:', error);
+    return null;
+  }
};
📝 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 getGitHubCustomDomain = async (): Promise<string | null> => {
const provider_meta = await db('Integration')
.where('name', Integration.GITHUB)
.then((r: Row<'Integration'>[]) => r.map((item) => item.provider_meta));
return head(provider_meta || [])?.custom_domain || null;
};
const getGitHubCustomDomain = async (): Promise<string | null> => {
try {
const provider_meta = await db('Integration')
.where('name', Integration.GITHUB)
.then((r: Row<'Integration'>[]) => r.map((item) => item.provider_meta));
return head(provider_meta || [])?.custom_domain || null;
} catch (error) {
console.error('Error retrieving GitHub custom domain:', error);
return null;
}
};

Comment on lines +321 to +322
return `${baseUrl}/${path}`.replace(/\/+/g, '/');
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Domain URL needs protocol verification

The code concatenates the custom domain with path segments, but doesn't ensure the custom domain includes a protocol.

Update the URL construction to ensure proper format:

const getGitHubRestApiUrl = async (path: string): Promise<string> => {
  const customDomain = await getGitHubCustomDomain();
-  const baseUrl = customDomain ? `https://${customDomain}/api/v3` : GITHUB_API_URL;
+  const baseUrl = customDomain 
+    ? (customDomain.startsWith('http') ? customDomain : `https://${customDomain}`) + '/api/v3' 
+    : GITHUB_API_URL;
  return `${baseUrl}/${path}`.replace(/\/+/g, '/');
};

Comment on lines +324 to +327
const getGitHubGraphQLUrl = async (): Promise<string> => {
const customDomain = await getGitHubCustomDomain();
return customDomain ? `https://${customDomain}/api/graphql` : GITHUB_API_URL;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct the default GraphQL API URL

The function uses GITHUB_API_URL as the default GraphQL URL, but this constant (line 9) is set to "https://api.github.com/graphql". However, the function adds "/api/graphql" for custom domains, creating inconsistency.

Fix the inconsistency:

// Line 9
-const GITHUB_API_URL = 'https://api.github.com/graphql';
+const GITHUB_REST_API_URL = 'https://api.github.com';
+const GITHUB_GRAPHQL_API_URL = 'https://api.github.com/graphql';

// In getGitHubRestApiUrl function (line 321)
-  const baseUrl = customDomain ? `https://${customDomain}/api/v3` : GITHUB_API_URL;
+  const baseUrl = customDomain ? `https://${customDomain}/api/v3` : GITHUB_REST_API_URL;

// In getGitHubGraphQLUrl function (line 326)
-  return customDomain ? `https://${customDomain}/api/graphql` : GITHUB_API_URL;
+  return customDomain ? `https://${customDomain}/api/graphql` : GITHUB_GRAPHQL_API_URL;
📝 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 getGitHubGraphQLUrl = async (): Promise<string> => {
const customDomain = await getGitHubCustomDomain();
return customDomain ? `https://${customDomain}/api/graphql` : GITHUB_API_URL;
};
const getGitHubGraphQLUrl = async (): Promise<string> => {
const customDomain = await getGitHubCustomDomain();
return customDomain
? `https://${customDomain}/api/graphql`
: GITHUB_GRAPHQL_API_URL;
};

Comment on lines 59 to +60
try {
const response = await axios.get('https://api.github.com', {
const response = await axios.get(baseUrl, {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix similar issues with URL construction in getMissingPATScopes

The same URL validation and normalization issues exist in this function.

Apply the same improvements:

const baseUrl = customDomain
-  ? `${customDomain}/api/v3`
+  ? `${normalizeCustomDomain(customDomain)}/api/v3`
  : DEFAULT_GH_URL;
try {
-  const response = await axios.get(baseUrl, {
+  const response = await axios.get(baseUrl.replace(/\/+/g, '/'), {
📝 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
try {
const response = await axios.get('https://api.github.com', {
const response = await axios.get(baseUrl, {
// normalize customDomain and ensure no duplicate slashes when calling the API
const baseUrl = customDomain
? `${normalizeCustomDomain(customDomain)}/api/v3`
: DEFAULT_GH_URL;
try {
const response = await axios.get(
baseUrl.replace(/\/+/g, '/'),
{
// …original request options…
}
);
// …
} catch (err) {
// …
}

Comment on lines +39 to +41
const baseUrl = customDomain
? `${customDomain}/api/v3`
: DEFAULT_GH_URL;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add URL validation and normalization for custom domain

The code assumes the custom domain is properly formatted, but there's no validation or normalization.

Add validation and normalizing for the custom domain:

const baseUrl = customDomain
-  ? `${customDomain}/api/v3`
+  ? `${normalizeCustomDomain(customDomain)}/api/v3`
  : DEFAULT_GH_URL;

// Add this helper function at the bottom of the file
+ function normalizeCustomDomain(domain: string): string {
+   // Remove trailing slashes
+   domain = domain.replace(/\/+$/, '');
+   // Add protocol if missing
+   if (!domain.startsWith('http')) {
+     domain = `https://${domain}`;
+   }
+   return domain;
+ }
📝 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 baseUrl = customDomain
? `${customDomain}/api/v3`
: DEFAULT_GH_URL;
const baseUrl = customDomain
? `${normalizeCustomDomain(customDomain)}/api/v3`
: DEFAULT_GH_URL;
// …other code…
// Add this helper at the bottom of the file
function normalizeCustomDomain(domain: string): string {
// Remove trailing slashes
domain = domain.replace(/\/+$/, '');
// Add protocol if missing
if (!domain.startsWith('http')) {
domain = `https://${domain}`;
}
return domain;
}

Comment on lines +169 to +196
<FlexBox gap2 col>
<FlexBox alignBase gap1>
Custom domain
</FlexBox>
<TextField
id="github-custom-domain"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
handleSubmission();
return;
}
}}
error={!!showDomainError.value}
sx={{ width: '100%' }}
value={customDomain.value}
onChange={(e) => handleDomainChange(e.currentTarget.value)}
label={
isDomainInputFocus.value || customDomain.value
? 'Custom Domain'
: '(Optional)'
}
onFocus={isDomainInputFocus.true}
onBlur={isDomainInputFocus.false}
/>
</FlexBox>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation and helper text for custom domain input

The custom domain input lacks validation and user guidance on expected format.

Add validation and helper text:

<TextField
  id="github-custom-domain"
  onKeyDown={(e) => {
    if (e.key === 'Enter') {
      e.preventDefault();
      e.stopPropagation();
      e.nativeEvent.stopImmediatePropagation();
      handleSubmission();
      return;
    }
  }}
  error={!!showDomainError.value}
  sx={{ width: '100%' }}
  value={customDomain.value}
  onChange={(e) => handleDomainChange(e.currentTarget.value)}
  label={
    isDomainInputFocus.value || customDomain.value
      ? 'Custom Domain'
      : '(Optional)'
  }
  onFocus={isDomainInputFocus.true}
  onBlur={isDomainInputFocus.false}
+ helperText={isDomainInputFocus.value || customDomain.value ? "Example: github.mycompany.com" : ""}
+ placeholder="github.mycompany.com"
/>
📝 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
<FlexBox gap2 col>
<FlexBox alignBase gap1>
Custom domain
</FlexBox>
<TextField
id="github-custom-domain"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
handleSubmission();
return;
}
}}
error={!!showDomainError.value}
sx={{ width: '100%' }}
value={customDomain.value}
onChange={(e) => handleDomainChange(e.currentTarget.value)}
label={
isDomainInputFocus.value || customDomain.value
? 'Custom Domain'
: '(Optional)'
}
onFocus={isDomainInputFocus.true}
onBlur={isDomainInputFocus.false}
/>
</FlexBox>
<FlexBox gap2 col>
<FlexBox alignBase gap1>
Custom domain
</FlexBox>
<TextField
id="github-custom-domain"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
handleSubmission();
return;
}
}}
error={!!showDomainError.value}
sx={{ width: '100%' }}
value={customDomain.value}
onChange={(e) => handleDomainChange(e.currentTarget.value)}
label={
isDomainInputFocus.value || customDomain.value
? 'Custom Domain'
: '(Optional)'
}
onFocus={isDomainInputFocus.true}
onBlur={isDomainInputFocus.false}
helperText={isDomainInputFocus.value || customDomain.value ? "Example: github.mycompany.com" : ""}
placeholder="github.mycompany.com"
/>
</FlexBox>

Comment on lines +47 to +50
const handleDomainChange = (e: string) => {
customDomain.set(e);
showDomainError.set('');
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation function for custom domain

The handleDomainChange function should validate the domain format.

Add validation to ensure the domain is properly formatted:

const handleDomainChange = (e: string) => {
  customDomain.set(e);
  showDomainError.set('');
+  // Basic validation when a value is entered
+  if (e && !isValidDomain(e)) {
+    showDomainError.set('Please enter a valid domain');
+  }
};

+ // Helper function to validate domain format
+ const isValidDomain = (domain: string): boolean => {
+   // Allow empty value (optional field)
+   if (!domain) return true;
+   
+   // Basic domain validation - allow with or without protocol
+   const domainRegex = /^(https?:\/\/)?([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(\/.*)?$/;
+   return domainRegex.test(domain);
+ };
📝 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 handleDomainChange = (e: string) => {
customDomain.set(e);
showDomainError.set('');
};
const handleDomainChange = (e: string) => {
customDomain.set(e);
showDomainError.set('');
// Basic validation when a value is entered
if (e && !isValidDomain(e)) {
showDomainError.set('Please enter a valid domain');
}
};
// Helper function to validate domain format
const isValidDomain = (domain: string): boolean => {
// Allow empty value (optional field)
if (!domain) return true;
// Basic domain validation - allow with or without protocol
const domainRegex = /^(https?:\/\/)?([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}(\/.*)?$/;
return domainRegex.test(domain);
};

Comment on lines +113 to +119
const isDomainInputFocus = useBoolState(false);

const focusDomainInput = useCallback(() => {
if (!customDomain.value)
document.getElementById('gitlab-custom-domain')?.focus();
else handleSubmission();
}, [customDomain.value, handleSubmission]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix incorrect input ID reference and potential circular function calls

There are two issues in this code:

  1. Line 117 references an input with ID "gitlab-custom-domain" but the actual ID used is "github-custom-domain"
  2. A potential circular reference exists between focusDomainInput and handleSubmission

Fix these issues:

const isDomainInputFocus = useBoolState(false);

const focusDomainInput = useCallback(() => {
  if (!customDomain.value)
-    document.getElementById('gitlab-custom-domain')?.focus();
+    document.getElementById('github-custom-domain')?.focus();
-  else handleSubmission();
+  else {
+    // Only call handleSubmission if it wasn't called directly
+    if (!isLoading.value) {
+      handleSubmission();
+    }
+  }
}, [customDomain.value, handleSubmission, isLoading.value]);

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

Copy link
Contributor

@adnanhashmi09 adnanhashmi09 left a comment

Choose a reason for hiding this comment

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

I don't see any validation of custom domain input in the frontend. What if the user enters the url with https, what if there are paths. You should validate and normalise the input you receive from the end user.
How is this currently being done in gitlab? Do we not validate or extract the domain from the url?


if not github_domain:
LOG.warn(
f"Custom domain not found for intergration for org {org_id} and provider {UserIdentityProvider.GITLAB.value}"
Copy link
Contributor

Choose a reason for hiding this comment

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

typo and wrong enum

Suggested change
f"Custom domain not found for intergration for org {org_id} and provider {UserIdentityProvider.GITLAB.value}"
f"Custom domain not found for integration for org {org_id} and provider {UserIdentityProvider.GITHUB.value}"

@@ -22,11 +22,20 @@ class GithubRateLimitExceeded(Exception):


class GithubApiService:
def __init__(self, access_token: str):
def __init__(self, access_token: str, domain = "https://api.github.com"):
Copy link
Contributor

Choose a reason for hiding this comment

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

type annotate domain variable
This would be Optional[str], i think?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also instead of setting a default str in the argument. It would be cleaner to put it inside _get_api_url like in the following comment

Comment on lines +34 to +38
def _get_api_url(self, domain: str) -> str:
if domain == "https://api.github.com":
return domain
else:
return f"{domain}/api/v3"
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def _get_api_url(self, domain: str) -> str:
if domain == "https://api.github.com":
return domain
else:
return f"{domain}/api/v3"
def _get_api_url(self, domain: Optional[str]) -> str:
if not domain:
return "https://api.github.com"
else:
return f"{domain}/api/v3"

Copy link
Contributor

Choose a reason for hiding this comment

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

This way the linter is also satisfied

Comment on lines +172 to +191
def _get_custom_github_domain() -> Optional[str]:
DEFAULT_DOMAIN = "https://api.github.com"
core_repo_service = CoreRepoService()
integrations = core_repo_service.get_org_integrations_for_names(
org_id, [UserIdentityProvider.GITHUB.value]
)

github_domain = (
integrations[0].provider_meta.get("custom_domain")
if integrations[0].provider_meta
else None
)

if not github_domain:
LOG.warn(
f"Custom domain not found for intergration for org {org_id} and provider {UserIdentityProvider.GITLAB.value}"
)
return DEFAULT_DOMAIN

return github_domain
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets define this function in some util file for github. You can create a utils.py file in the code directory and define it there once without having to redefine the same function again. and then import it wherever you want to use it

Comment on lines +310 to +316
const getGitHubCustomDomain = async (): Promise<string | null> => {
const provider_meta = await db('Integration')
.where('name', Integration.GITHUB)
.then((r: Row<'Integration'>[]) => r.map((item) => item.provider_meta));

return head(provider_meta || [])?.custom_domain || null;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets have some error handling here. If the db query fails somehow it will fail silently. At least a log should be there, so that it can get got in the system logs


const getGitHubRestApiUrl = async (path: string): Promise<string> => {
const customDomain = await getGitHubCustomDomain();
const baseUrl = customDomain ? `https://${customDomain}/api/v3` : GITHUB_API_URL;
Copy link
Contributor

Choose a reason for hiding this comment

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

This is wrong. How is the default rest API url = 'https://api.github.com/graphql'


const getGitHubGraphQLUrl = async (): Promise<string> => {
const customDomain = await getGitHubCustomDomain();
return customDomain ? `https://${customDomain}/api/graphql` : GITHUB_API_URL;
Copy link
Contributor

Choose a reason for hiding this comment

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

similarly here. GITHUB_API_URL is an ambiguous name when we have both rest api and graphql api

Comment on lines +169 to +196
<FlexBox gap2 col>
<FlexBox alignBase gap1>
Custom domain
</FlexBox>
<TextField
id="github-custom-domain"
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
handleSubmission();
return;
}
}}
error={!!showDomainError.value}
sx={{ width: '100%' }}
value={customDomain.value}
onChange={(e) => handleDomainChange(e.currentTarget.value)}
label={
isDomainInputFocus.value || customDomain.value
? 'Custom Domain'
: '(Optional)'
}
onFocus={isDomainInputFocus.true}
onBlur={isDomainInputFocus.false}
/>
</FlexBox>
Copy link
Contributor

Choose a reason for hiding this comment

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

can have a placeholder value to show what a custom domain input would look like. See this review

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.

Allow changing GitHub server hostname (for self-hosted/enterprise instances)
2 participants