Skip to content
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@
## Features

- Automatically fetches your Git activity, including commits, pull requests, issues, and code reviews.
- Currently supports GitHub, with plans to expand to other platforms
- **Multi-Platform Support**: GitHub, GitLab (NEW), with Gitea coming soon
- Generates editable scrum updates based on your selected date range
- Integrates directly with compose windows in Google Groups, Gmail, Yahoo Mail, and Outlook
- Platform selector for seamless switching between GitHub and GitLab

## Roadmap

### Upcoming Features 🚀
- ✅ **GitLab Support** - Full GitLab project and merge request integration
- 🔜 **Gitea Support** - Add Gitea instance compatibility
- 📊 **Multi-SCM Analytics** - Unified dashboard for cross-platform activity
- 🔗 **Unified API Layer** - Abstract provider implementation for easier extensions
- 📈 **Advanced Analytics** - Commit trends, contribution graphs, team metrics

## How to install

Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down
4 changes: 4 additions & 0 deletions src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -432,5 +432,9 @@
"insertedInEmailNotification": {
"message": "Report inserted into email!",
"description": "Notification shown when report is successfully inserted into email."
},
"invalidDateRangeError": {
"message": "Invalid date range. Please select a valid date range.",
"description": "Error shown when start date is after end date."
}
}
27 changes: 27 additions & 0 deletions src/scripts/gitlabHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,37 @@ class GitLabHelper {
}
}

// Fetch commits from each project
let allCommits = [];
for (const project of allProjects) {
try {
const projectCommitsUrl = `${this.baseUrl}/projects/${project.id}/repository/commits?author_name=${username}&since=${startDate}T00:00:00Z&until=${endDate}T23:59:59Z&per_page=100&order_by=committed_date&sort=desc`;
Comment thread
SinghCharanjeet11 marked this conversation as resolved.
const projectCommitsRes = await fetch(projectCommitsUrl, { headers });
if (projectCommitsRes.ok) {
const projectCommits = await projectCommitsRes.json();
allCommits = allCommits.concat(
projectCommits.map((commit) => ({
...commit,
project_id: project.id,
project_name: project.name,
project_url: project.web_url,
})),
);
}
// Add small delay to avoid rate limiting
await new Promise((resolve) => setTimeout(resolve, 100));
} catch (error) {
console.error(`Error fetching commits for project ${project.name}:`, error);
// Continue with other projects
}
}

const gitlabData = {
user: users[0],
projects: allProjects,
mergeRequests: allMergeRequests, // use project-by-project response
issues: allIssues, // use project-by-project response
commits: allCommits, // commits from all projects
comments: [], // Empty array since we're not fetching comments
};
// Cache the data
Expand Down Expand Up @@ -262,6 +288,7 @@ class GitLabHelper {
const processed = {
mergeRequests: data.mergeRequests || [],
issues: data.issues || [],
commits: data.commits || [],
comments: data.comments || [],
user: data.user,
};
Expand Down
72 changes: 72 additions & 0 deletions src/scripts/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,34 @@ function getYesterday() {
return yesterday.toISOString().split('T')[0];
}

// Validate that start date is not after end date
function validateDateRange(startDate, endDate) {
if (!startDate || !endDate) {
return true; // Allow empty dates (will be handled by other validation)
}

const start = new Date(startDate);
const end = new Date(endDate);

// Check for invalid dates
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
return true; // Allow invalid dates to be handled by other validation
}

return start <= end;
}

// Show date validation error message
function showDateValidationError(message) {
// Use Materialize toast if available
if (typeof Materialize !== 'undefined' && Materialize.toast) {
Materialize.toast(message, 4000, 'red');
} else {
// Fallback to alert if Materialize is not available
alert(message);
}
}

function applyI18n() {
document.querySelectorAll('[data-i18n]').forEach((el) => {
const key = el.getAttribute('data-i18n');
Expand Down Expand Up @@ -705,6 +733,24 @@ document.addEventListener('DOMContentLoaded', () => {
}

generateBtn.addEventListener('click', () => {
// Validate date range before generating report
const startDateInput = document.getElementById('startingDate');
const endDateInput = document.getElementById('endingDate');
const yesterdayRadio = document.getElementById('yesterdayContribution');

// Only validate if using custom date range (not yesterday preset)
if (!yesterdayRadio.checked && startDateInput && endDateInput) {
const startDate = startDateInput.value;
const endDate = endDateInput.value;

if (!validateDateRange(startDate, endDate)) {
const errorMessage = chrome?.i18n.getMessage('invalidDateRangeError') ||
'Invalid date range. Please select a valid date range.';
showDateValidationError(errorMessage);
return; // Stop report generation
}
}

browser.storage.local.get(['platform']).then((result) => {
const platform = result.platform || 'github';
const platformUsernameKey = `${platform}Username`;
Expand Down Expand Up @@ -989,9 +1035,35 @@ document.addEventListener('DOMContentLoaded', () => {
});
startingDateInput.addEventListener('input', () => {
browser.storage.local.set({ startingDate: startingDateInput.value });

// Real-time validation for custom date ranges
const yesterdayRadio = document.getElementById('yesterdayContribution');
if (!yesterdayRadio.checked) {
const startDate = startingDateInput.value;
const endDate = endingDateInput.value;

if (startDate && endDate && !validateDateRange(startDate, endDate)) {
const errorMessage = chrome?.i18n.getMessage('invalidDateRangeError') ||
'Invalid date range. Please select a valid date range.';
showDateValidationError(errorMessage);
}
}
});
endingDateInput.addEventListener('input', () => {
browser.storage.local.set({ endingDate: endingDateInput.value });

// Real-time validation for custom date ranges
const yesterdayRadio = document.getElementById('yesterdayContribution');
if (!yesterdayRadio.checked) {
const startDate = startingDateInput.value;
const endDate = endingDateInput.value;

if (startDate && endDate && !validateDateRange(startDate, endDate)) {
const errorMessage = chrome?.i18n.getMessage('invalidDateRangeError') ||
'Invalid date range. Please select a valid date range.';
showDateValidationError(errorMessage);
}
}
});

// Save username to storage on input
Expand Down
28 changes: 28 additions & 0 deletions src/scripts/scrumHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ function allIncluded(outputTarget = 'email') {
let nextWeekArray = [];
let reviewedPrsArray = [];
let githubIssuesData = null;
let githubCommitsData = null;
let yesterdayContribution = false;
let githubPrsReviewData = null;
let githubUserData = null;
Expand Down Expand Up @@ -274,12 +275,24 @@ function allIncluded(outputTarget = 'email') {
const mappedMRs = (data.mergeRequests || data.mrs || []).map((mr) =>
mapGitLabItem(mr, data.projects, 'mr'),
);
// Map commits to standard format for report generation
const mappedCommits = (data.commits || []).map((commit) => ({
...commit,
message: commit.message || commit.title,
html_url: commit.web_url || (commit.project_url ? `${commit.project_url}/-/commit/${commit.id}` : ''),
sha: commit.id,
project: commit.project_name,
author_name: commit.author_name,
author_email: commit.author_email,
}));
const mappedData = {
githubIssuesData: { items: mappedIssues },
githubPrsReviewData: { items: mappedMRs },
gitlabCommits: mappedCommits,
githubUserData: data.user || {},
};
githubUserData = mappedData.githubUserData;
githubCommitsData = mappedCommits;

const name =
githubUserData?.name || githubUserData?.username || platformUsernameLocal || platformUsername;
Expand Down Expand Up @@ -342,9 +355,20 @@ function allIncluded(outputTarget = 'email') {
const mappedMRs = (data.mergeRequests || data.mrs || []).map((mr) =>
mapGitLabItem(mr, data.projects, 'mr'),
);
// Map commits to standard format for report generation
const mappedCommits = (data.commits || []).map((commit) => ({
...commit,
message: commit.message || commit.title,
html_url: commit.web_url || (commit.project_url ? `${commit.project_url}/-/commit/${commit.id}` : ''),
sha: commit.id,
project: commit.project_name,
author_name: commit.author_name,
author_email: commit.author_email,
}));
const mappedData = {
githubIssuesData: { items: mappedIssues },
githubPrsReviewData: { items: mappedMRs },
gitlabCommits: mappedCommits,
githubUserData: data.user || {},
};
processGithubData(mappedData);
Expand Down Expand Up @@ -1045,6 +1069,10 @@ function allIncluded(outputTarget = 'email') {
} else if (platform === 'gitlab') {
await writeGithubIssuesPrs(githubIssuesData?.items || []);
await writeGithubIssuesPrs(githubPrsReviewData?.items || []);
// Add commits to the report for GitLab
if (githubCommitsData && githubCommitsData.length > 0) {
await writeGithubIssuesPrs(githubCommitsData);
}
}
await writeGithubPrsReviews();
log('[DEBUG] Both data processing functions completed, generating scrum body');
Expand Down
Loading