feat: gitLab Integration Implementation / github refactor / overal improvements (#1963)
* Add GitLab integration components Introduced PushToGitLabDialog and GitlabConnection components to handle GitLab project connections and push functionality. Includes user authentication, project handling, and UI for seamless integration with GitLab. * Add components for GitLab connection and push dialog Introduce `GitlabConnection` and `PushToGitLabDialog` components to handle GitLab integration. These components allow users to connect their GitLab account, manage recent projects, and push code to a GitLab repository with detailed configurations and feedback. * Fix GitLab personal access tokens link to use correct URL * Update GitHub push call to use new pushToRepository method * Enhance GitLab integration with performance improvements - Add comprehensive caching system for repositories and user data - Implement pagination and search/filter functionality with debouncing - Add skeleton loaders and improved loading states - Implement retry logic for API calls with exponential backoff - Add background refresh capabilities - Improve error handling and user feedback - Optimize API calls to reduce loading times * feat: implement GitLab integration with connection management and repository handling - Add GitLab connection UI components - Implement GitLab API service for repository operations - Add GitLab connection store for state management - Update existing connection components (Vercel, Netlify) - Add repository listing and statistics display - Refactor GitLab components into organized folder structure * fix: resolve GitLab deployment issues and improve user experience - Fix DialogTitle accessibility warnings for screen readers - Remove CORS-problematic attributes from avatar images to prevent loading errors - Enhance GitLab API error handling with detailed error messages - Fix project creation settings to prevent initial commit conflicts - Add automatic GitLab connection state initialization on app startup - Improve deployment dialog UI with better error handling and user feedback - Add GitLab deployment source type to action runner system - Clean up deprecated push dialog files and consolidate deployment components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: implement GitHub clone repository dialog functionality This commit fixes the missing GitHub repository selection dialog in the "Clone a repo" feature by implementing the same elegant interface pattern used by GitLab. Key Changes: - Added onCloneRepository prop support to GitHubConnection component - Updated RepositoryCard to generate proper GitHub clone URLs (https://github.com/{full_name}.git) - Implemented full GitHub repository selection dialog in GitCloneButton.tsx - Added proper dialog close handling after successful clone operations - Maintained existing GitHub connection settings page functionality Technical Details: - Follows same component patterns as GitLab implementation - Uses proper TypeScript interfaces for clone URL handling - Includes professional dialog styling with loading states - Supports repository search, pagination, and authentication flow The GitHub clone experience now matches GitLab's functionality, providing users with a unified and intuitive repository selection interface across both providers. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Clean up unused connection components - Remove ConnectionForm.tsx (unused GitHub form component) - Remove CreateBranchDialog.tsx (unused branch creation dialog) - Remove RepositoryDialogContext.tsx (unused context provider) - Remove empty components/ directory These files were not referenced anywhere in the codebase and were leftover from development. * Remove environment variables info section from ConnectionsTab - Remove collapsible environment variables section - Clean up unused state and imports - Simplify the connections tab UI * Reorganize connections folder structure - Create netlify/ folder and move NetlifyConnection.tsx - Create vercel/ folder and move VercelConnection.tsx - Add index.ts files for both netlify and vercel folders - Update imports in ConnectionsTab.tsx to use new folder structure - All connection components now follow consistent folder organization --------- Co-authored-by: Hayat Bourgi <hayat.bourgi@montyholding.com> Co-authored-by: Hayat55 <53140162+Hayat55@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
167
app/utils/githubStats.ts
Normal file
167
app/utils/githubStats.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
import type { GitHubStats, GitHubLanguageStats } from '~/types/GitHub';
|
||||
|
||||
export interface GitHubStatsSummary {
|
||||
totalRepositories: number;
|
||||
totalStars: number;
|
||||
totalForks: number;
|
||||
publicRepositories: number;
|
||||
privateRepositories: number;
|
||||
followers: number;
|
||||
publicGists: number;
|
||||
topLanguages: Array<{ name: string; count: number; percentage: number }>;
|
||||
recentActivityCount: number;
|
||||
lastUpdated?: string;
|
||||
}
|
||||
|
||||
export function calculateStatsSummary(stats: GitHubStats): GitHubStatsSummary {
|
||||
// Calculate total repositories
|
||||
const totalRepositories = stats.repos?.length || stats.publicRepos || 0;
|
||||
|
||||
// Calculate language statistics
|
||||
const topLanguages = calculateTopLanguages(stats.languages || {});
|
||||
|
||||
return {
|
||||
totalRepositories,
|
||||
totalStars: stats.totalStars || stats.stars || 0,
|
||||
totalForks: stats.totalForks || stats.forks || 0,
|
||||
publicRepositories: stats.publicRepos || 0,
|
||||
privateRepositories: stats.privateRepos || 0,
|
||||
followers: stats.followers || 0,
|
||||
publicGists: stats.totalGists || stats.publicGists || 0,
|
||||
topLanguages,
|
||||
recentActivityCount: stats.recentActivity?.length || 0,
|
||||
lastUpdated: stats.lastUpdated,
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateTopLanguages(languages: GitHubLanguageStats): Array<{
|
||||
name: string;
|
||||
count: number;
|
||||
percentage: number;
|
||||
}> {
|
||||
if (!languages || Object.keys(languages).length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const totalCount = Object.values(languages).reduce((sum, count) => sum + count, 0);
|
||||
|
||||
if (totalCount === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Object.entries(languages)
|
||||
.map(([name, count]) => ({
|
||||
name,
|
||||
count,
|
||||
percentage: Math.round((count / totalCount) * 100),
|
||||
}))
|
||||
.sort((a, b) => b.count - a.count)
|
||||
.slice(0, 10); // Top 10 languages
|
||||
}
|
||||
|
||||
export function formatRepositoryStats(stats: GitHubStats) {
|
||||
const repositories = stats.repos || [];
|
||||
|
||||
// Sort repositories by stars (descending)
|
||||
const topStarredRepos = repositories
|
||||
.filter((repo) => repo.stargazers_count > 0)
|
||||
.sort((a, b) => b.stargazers_count - a.stargazers_count)
|
||||
.slice(0, 5);
|
||||
|
||||
// Sort repositories by forks (descending)
|
||||
const topForkedRepos = repositories
|
||||
.filter((repo) => repo.forks_count > 0)
|
||||
.sort((a, b) => b.forks_count - a.forks_count)
|
||||
.slice(0, 5);
|
||||
|
||||
// Recent repositories (by update date)
|
||||
const recentRepos = repositories
|
||||
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
|
||||
.slice(0, 10);
|
||||
|
||||
return {
|
||||
total: repositories.length,
|
||||
topStarredRepos,
|
||||
topForkedRepos,
|
||||
recentRepos,
|
||||
totalStars: repositories.reduce((sum, repo) => sum + repo.stargazers_count, 0),
|
||||
totalForks: repositories.reduce((sum, repo) => sum + repo.forks_count, 0),
|
||||
};
|
||||
}
|
||||
|
||||
export function formatActivitySummary(stats: GitHubStats) {
|
||||
const activity = stats.recentActivity || [];
|
||||
|
||||
// Group activities by type
|
||||
const activityByType = activity.reduce(
|
||||
(acc, event) => {
|
||||
acc[event.type] = (acc[event.type] || 0) + 1;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, number>,
|
||||
);
|
||||
|
||||
// Format activity types for display
|
||||
const formattedActivity = Object.entries(activityByType)
|
||||
.map(([type, count]) => ({
|
||||
type: formatActivityType(type),
|
||||
count,
|
||||
}))
|
||||
.sort((a, b) => b.count - a.count);
|
||||
|
||||
return {
|
||||
total: activity.length,
|
||||
byType: formattedActivity,
|
||||
recent: activity.slice(0, 5),
|
||||
};
|
||||
}
|
||||
|
||||
function formatActivityType(type: string): string {
|
||||
const typeMap: Record<string, string> = {
|
||||
PushEvent: 'Pushes',
|
||||
CreateEvent: 'Created',
|
||||
DeleteEvent: 'Deleted',
|
||||
ForkEvent: 'Forks',
|
||||
WatchEvent: 'Stars',
|
||||
IssuesEvent: 'Issues',
|
||||
PullRequestEvent: 'Pull Requests',
|
||||
ReleaseEvent: 'Releases',
|
||||
PublicEvent: 'Made Public',
|
||||
};
|
||||
|
||||
return typeMap[type] || type.replace('Event', '');
|
||||
}
|
||||
|
||||
export function calculateGrowthMetrics(currentStats: GitHubStats, previousStats?: GitHubStats) {
|
||||
if (!previousStats) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const starsDiff = (currentStats.totalStars || 0) - (previousStats.totalStars || 0);
|
||||
const forksDiff = (currentStats.totalForks || 0) - (previousStats.totalForks || 0);
|
||||
const followersDiff = (currentStats.followers || 0) - (previousStats.followers || 0);
|
||||
const reposDiff = (currentStats.repos?.length || 0) - (previousStats.repos?.length || 0);
|
||||
|
||||
return {
|
||||
stars: {
|
||||
current: currentStats.totalStars || 0,
|
||||
change: starsDiff,
|
||||
percentage: previousStats.totalStars ? Math.round((starsDiff / previousStats.totalStars) * 100) : 0,
|
||||
},
|
||||
forks: {
|
||||
current: currentStats.totalForks || 0,
|
||||
change: forksDiff,
|
||||
percentage: previousStats.totalForks ? Math.round((forksDiff / previousStats.totalForks) * 100) : 0,
|
||||
},
|
||||
followers: {
|
||||
current: currentStats.followers || 0,
|
||||
change: followersDiff,
|
||||
percentage: previousStats.followers ? Math.round((followersDiff / previousStats.followers) * 100) : 0,
|
||||
},
|
||||
repositories: {
|
||||
current: currentStats.repos?.length || 0,
|
||||
change: reposDiff,
|
||||
percentage: previousStats.repos?.length ? Math.round((reposDiff / previousStats.repos.length) * 100) : 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
54
app/utils/gitlabStats.ts
Normal file
54
app/utils/gitlabStats.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { GitLabProjectInfo, GitLabStats } from '~/types/GitLab';
|
||||
|
||||
export function calculateProjectStats(projects: any[]): { projects: GitLabProjectInfo[] } {
|
||||
const projectStats = {
|
||||
projects: projects.map((project: any) => ({
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
path_with_namespace: project.path_with_namespace,
|
||||
description: project.description,
|
||||
http_url_to_repo: project.http_url_to_repo,
|
||||
star_count: project.star_count || 0,
|
||||
forks_count: project.forks_count || 0,
|
||||
default_branch: project.default_branch,
|
||||
updated_at: project.updated_at,
|
||||
visibility: project.visibility,
|
||||
})),
|
||||
};
|
||||
|
||||
return projectStats;
|
||||
}
|
||||
|
||||
export function calculateStatsSummary(
|
||||
projects: GitLabProjectInfo[],
|
||||
events: any[],
|
||||
groups: any[],
|
||||
snippets: any[],
|
||||
user: any,
|
||||
): GitLabStats {
|
||||
const totalStars = projects.reduce((sum, p) => sum + (p.star_count || 0), 0);
|
||||
const totalForks = projects.reduce((sum, p) => sum + (p.forks_count || 0), 0);
|
||||
const privateProjects = projects.filter((p) => p.visibility === 'private').length;
|
||||
|
||||
const recentActivity = events.slice(0, 5).map((event: any) => ({
|
||||
id: event.id,
|
||||
action_name: event.action_name,
|
||||
project_id: event.project_id,
|
||||
project: event.project,
|
||||
created_at: event.created_at,
|
||||
}));
|
||||
|
||||
return {
|
||||
projects,
|
||||
recentActivity,
|
||||
totalSnippets: snippets.length,
|
||||
publicProjects: projects.filter((p) => p.visibility === 'public').length,
|
||||
privateProjects,
|
||||
stars: totalStars,
|
||||
forks: totalForks,
|
||||
followers: user.followers || 0,
|
||||
snippets: snippets.length,
|
||||
groups,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user