From a83b362a84f776066cd2321ad064caa08f6c19a2 Mon Sep 17 00:00:00 2001 From: EwanTauran Date: Wed, 11 Mar 2026 18:26:03 -0700 Subject: [PATCH 1/2] Enhance GitHub repository name handling: update validation to accept full GitHub URLs and normalize input by stripping URL prefixes. Update related validation messages for clarity. --- backend/airweave/platform/configs/config.py | 18 ++++++++++++++++-- frontend/src/lib/validation/rules.ts | 16 ++++++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/backend/airweave/platform/configs/config.py b/backend/airweave/platform/configs/config.py index 2b184134a..31986ede4 100644 --- a/backend/airweave/platform/configs/config.py +++ b/backend/airweave/platform/configs/config.py @@ -150,9 +150,11 @@ class GitHubConfig(SourceConfig): repo_name: str = Field( title="Repository Name", - description="Repository to sync in owner/repo format (e.g., 'airweave-ai/airweave')", + description=( + "Repository to sync in owner/repo format (e.g., 'airweave-ai/airweave') " + "or full GitHub URL (e.g., 'https://github.com/airweave-ai/airweave')" + ), min_length=3, - pattern=r"^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+$", ) branch: str = Field( default="", @@ -171,6 +173,18 @@ class GitHubConfig(SourceConfig): ), ) + @field_validator("repo_name", mode="before") + @classmethod + def normalize_repo_name(cls, v: str) -> str: + """Strip GitHub URL prefix if provided, normalizing to owner/repo format.""" + if isinstance(v, str): + v = v.strip().rstrip("/") + for prefix in ("https://github.com/", "http://github.com/"): + if v.lower().startswith(prefix): + v = v[len(prefix) :] + break + return v + @field_validator("repo_name") @classmethod def validate_repo_name(cls, v: str) -> str: diff --git a/frontend/src/lib/validation/rules.ts b/frontend/src/lib/validation/rules.ts index 6c945014b..b836337cb 100644 --- a/frontend/src/lib/validation/rules.ts +++ b/frontend/src/lib/validation/rules.ts @@ -434,24 +434,32 @@ export const redirectUrlValidation: FieldValidation = { }; /** - * Repository name validation (owner/repo format) + * Repository name validation (owner/repo format or full GitHub URL) */ export const repoNameValidation: FieldValidation = { field: 'repo_name', debounceMs: 500, showOn: 'change', validate: (value: string): ValidationResult => { - const trimmed = value.trim(); + let trimmed = value.trim().replace(/\/+$/, ''); if (!trimmed) { return { isValid: true, severity: 'info' }; } + // Strip GitHub URL prefix if present + for (const prefix of ['https://github.com/', 'http://github.com/']) { + if (trimmed.toLowerCase().startsWith(prefix)) { + trimmed = trimmed.slice(prefix.length); + break; + } + } + // Must contain a slash if (!trimmed.includes('/')) { return { isValid: false, - hint: 'Repository must be in owner/repo format (e.g., airweave-ai/airweave)', + hint: 'Use owner/repo format (e.g., airweave-ai/airweave) or a full GitHub URL', severity: 'warning' }; } @@ -460,7 +468,7 @@ export const repoNameValidation: FieldValidation = { if (parts.length !== 2) { return { isValid: false, - hint: 'Repository must be in owner/repo format (e.g., airweave-ai/airweave)', + hint: 'Use owner/repo format (e.g., airweave-ai/airweave) or a full GitHub URL', severity: 'warning' }; } From c4cca3ae85f468bf3c688ca414468012ee43e11f Mon Sep 17 00:00:00 2001 From: EwanTauran Date: Wed, 11 Mar 2026 18:34:50 -0700 Subject: [PATCH 2/2] Refactor configuration validators: update method signatures to include type hints for better clarity and maintainability. Ensure consistent return types across validators. Addresses mypy issues. --- backend/airweave/platform/configs/config.py | 41 ++++++++++----------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/backend/airweave/platform/configs/config.py b/backend/airweave/platform/configs/config.py index 31986ede4..0444f697f 100644 --- a/backend/airweave/platform/configs/config.py +++ b/backend/airweave/platform/configs/config.py @@ -1,6 +1,6 @@ """Configuration classes for platform components.""" -from typing import Optional +from typing import Any, Optional from pydantic import Field, field_validator @@ -58,14 +58,13 @@ class BitbucketConfig(SourceConfig): @field_validator("file_extensions", mode="before") @classmethod - def parse_file_extensions(cls, value): + def parse_file_extensions(cls, value: Any) -> list[str]: """Convert string input to list if needed.""" if isinstance(value, str): if not value.strip(): return [] - # Split by commas and strip whitespace return [ext.strip() for ext in value.split(",") if ext.strip()] - return value + return value # type: ignore[no-any-return] class BoxConfig(SourceConfig): @@ -273,21 +272,20 @@ class GmailConfig(SourceConfig): @field_validator("included_labels", "excluded_labels", "excluded_categories", mode="before") @classmethod - def parse_list_fields(cls, value): + def parse_list_fields(cls, value: Any) -> list[str]: """Convert comma-separated string to list if needed.""" if isinstance(value, str): if not value.strip(): return [] return [item.strip() for item in value.split(",") if item.strip()] - return value + return value # type: ignore[no-any-return] @field_validator("after_date") @classmethod - def validate_date_format(cls, value): + def validate_date_format(cls, value: Optional[str]) -> Optional[str]: """Validate date format and convert to YYYY/MM/DD.""" if not value: return value - # Accept both YYYY/MM/DD and YYYY-MM-DD formats return value.replace("-", "/") @@ -328,10 +326,10 @@ class GoogleDriveConfig(SourceConfig): @field_validator("include_patterns", mode="before") @classmethod - def _parse_include_patterns(cls, value): + def _parse_include_patterns(cls, value: Any) -> list[str]: if isinstance(value, str): return [p.strip() for p in value.split(",") if p.strip()] - return value + return value # type: ignore[no-any-return] class GoogleSlidesConfig(SourceConfig): @@ -464,21 +462,20 @@ class OutlookMailConfig(SourceConfig): @field_validator("included_folders", "excluded_folders", mode="before") @classmethod - def parse_list_fields(cls, value): + def parse_list_fields(cls, value: Any) -> list[str]: """Convert comma-separated string to list if needed.""" if isinstance(value, str): if not value.strip(): return [] return [item.strip() for item in value.split(",") if item.strip()] - return value + return value # type: ignore[no-any-return] @field_validator("after_date") @classmethod - def validate_date_format(cls, value): + def validate_date_format(cls, value: Optional[str]) -> Optional[str]: """Validate date format and convert to YYYY/MM/DD.""" if not value: return value - # Accept both YYYY/MM/DD and YYYY-MM-DD formats return value.replace("-", "/") @@ -520,7 +517,7 @@ class CTTIConfig(SourceConfig): @field_validator("limit", mode="before") @classmethod - def parse_limit(cls, value): + def parse_limit(cls, value: Any) -> int: """Convert string input to integer if needed.""" if isinstance(value, str): if not value.strip(): @@ -529,11 +526,11 @@ def parse_limit(cls, value): return int(value.strip()) except ValueError as e: raise ValueError("Limit must be a valid integer") from e - return value + return value # type: ignore[no-any-return] @field_validator("skip", mode="before") @classmethod - def parse_skip(cls, value): + def parse_skip(cls, value: Any) -> int: """Convert string input to integer if needed.""" if isinstance(value, str): if not value.strip(): @@ -551,7 +548,7 @@ def parse_skip(cls, value): if value < 0: raise ValueError("Skip must be non-negative") return int(value) - return value + return value # type: ignore[no-any-return] class SharePointConfig(SourceConfig): @@ -663,14 +660,14 @@ class SalesforceConfig(SourceConfig): @field_validator("instance_url", mode="before") @classmethod - def strip_https_prefix(cls, value): + def strip_https_prefix(cls, value: Any) -> Optional[str]: """Remove https:// or http:// prefix if present.""" if isinstance(value, str): if value.startswith("https://"): return value.replace("https://", "", 1) elif value.startswith("http://"): return value.replace("http://", "", 1) - return value + return value # type: ignore[no-any-return] class TodoistConfig(SourceConfig): @@ -814,7 +811,7 @@ class StubConfig(SourceConfig): mode="before", ) @classmethod - def parse_weight(cls, value): + def parse_weight(cls, value: Any) -> int: """Convert string input to integer if needed.""" if isinstance(value, str): if not value.strip(): @@ -823,7 +820,7 @@ def parse_weight(cls, value): return int(value.strip()) except ValueError as e: raise ValueError("Weight must be a valid integer") from e - return value + return value # type: ignore[no-any-return] class IncrementalStubConfig(SourceConfig):