-
Notifications
You must be signed in to change notification settings - Fork 2
Extract ProjectMapper class from GitHubProjectDataSource #629
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
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR extracts the repository-to-project mapping logic from GitHubProjectDataSource into a dedicated, reusable ProjectMapper class. This refactoring introduces generic type support to enable the mapper to work with different Git providers, while moving provider-specific URL generation logic into configurable URL builders. The change improves code organization by separating concerns: the data source now focuses on repository retrieval, while the mapper handles the transformation logic.
Key Changes:
- Created a new
ProjectMapperclass with generic type support for provider-agnostic repository mapping - Introduced
URLBuildersinterface for provider-specific URL generation with optional diff and PR URL support - Simplified
GitHubProjectDataSourceto delegate mapping logic toProjectMapper
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| src/features/projects/domain/ProjectMapper.ts | New file containing extracted mapping logic with generic types (RepositoryRef, RepositoryWithRefs, URLBuilders, and ProjectMapper class) and utility functions |
| src/features/projects/data/GitHubProjectDataSource.ts | Refactored to use ProjectMapper with GitHub-specific URL builders, removing ~260 lines of mapping logic |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| export function getBlobURL(owner: string, repository: string, path: string, ref: string): string { | ||
| return `/api/blob/${owner}/${repository}/${path}?ref=${ref}` |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getBlobURL function is missing path encoding that was present in the original implementation. The old code properly encoded each path segment using path.split('/').map(segment => encodeURIComponent(segment)).join('/'), but the new implementation directly uses the unencoded path. This could cause issues with files containing special characters in their names (e.g., spaces, #, ?, etc.).
Consider updating the function to:
export function getBlobURL(owner: string, repository: string, path: string, ref: string): string {
const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/')
return `/api/blob/${owner}/${repository}/${encodedPath}?ref=${ref}`
}| return `/api/blob/${owner}/${repository}/${path}?ref=${ref}` | |
| const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/') | |
| return `/api/blob/${owner}/${repository}/${encodedPath}?ref=${ref}` |
|
|
||
| const gitHubURLBuilders: URLBuilders<RepositoryWithRefs> = { | ||
| getImageRef(repository: RepositoryWithRefs): string { | ||
| return repository.defaultBranchRef.id! |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using non-null assertion operator (!) on repository.defaultBranchRef.id is unsafe. The RepositoryWithRefs type defines id as optional (readonly id?: string), but the actual GitHubRepository type from the domain layer requires it to be non-optional (readonly id: string). This mismatch could cause runtime errors if the mapper is used with other repository types that don't guarantee an id.
Consider either:
- Making the id required in
RepositoryRefandRepositoryWithRefstypes if it's always expected, or - Providing a fallback value or throwing a descriptive error when id is missing
| return repository.defaultBranchRef.id! | |
| if (!repository.defaultBranchRef.id) { | |
| throw new Error("Repository defaultBranchRef.id is missing"); | |
| } | |
| return repository.defaultBranchRef.id; |
| return repository.defaultBranchRef.id! | ||
| }, | ||
| getBlobRef(ref: RepositoryRef): string { | ||
| return ref.id! |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using non-null assertion operator (!) on ref.id is unsafe. The RepositoryRef type defines id as optional (readonly id?: string), but the actual GitHubRepositoryRef type from the domain layer requires it to be non-optional (readonly id: string). This mismatch could cause runtime errors if the mapper is used with other repository types that don't guarantee an id.
Consider either:
- Making the id required in
RepositoryReftype if it's always expected for GitHub, or - Providing a fallback value or throwing a descriptive error when id is missing
| return ref.id! | |
| if (!ref.id) { | |
| throw new Error("RepositoryRef.id is required for getBlobRef but was undefined"); | |
| } | |
| return ref.id; |
| return `https://github.com/${repository.owner}/${repository.name}/edit/${ref.name}/${encodeURIComponent(fileName)}` | ||
| }, | ||
| getDiffUrl(repository: RepositoryWithRefs, ref: RepositoryRef, fileName: string): string | undefined { | ||
| if (!ref.baseRefOid || !ref.id) { |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition !ref.id is a new addition not present in the original implementation. The old code only checked if (!baseRefOid) before returning undefined. While this additional check may be intentional for safety given the optional nature of ref.id in the new types, it changes the behavior from the original implementation and could cause diff URLs to not be generated in cases where they previously would have been. If this is intentional, it should be documented; otherwise, consider matching the original logic.
| if (!ref.baseRefOid || !ref.id) { | |
| if (!ref.baseRefOid) { |
| .sort((a, b) => a.name.localeCompare(b.name)) | ||
| } | ||
|
|
||
| mapRepositoryToProject(repository: T): Project { |
Copilot
AI
Dec 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The mapRepositoryToProject method is public, but the equivalent method in the original implementation (mapProject) was private. Consider marking this as private unless there's a specific need to expose it as part of the public API, to maintain better encapsulation.
| mapRepositoryToProject(repository: T): Project { | |
| private mapRepositoryToProject(repository: T): Project { |
d90ecdc to
3ce2d6b
Compare
3ce2d6b to
8a37284
Compare
Summary
ProjectMapperclass