diff --git a/.github/labeler.yml b/.github/labeler.yml index 07e26c8..3e001e1 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,51 +1,51 @@ # Frontend changes frontend: - - 'src/**/*.html' - - 'src/**/*.css' + - "src/**/*.html" + - "src/**/*.css" -# JavaScript changes +# JavaScript changes javascript: - - 'src/scripts/**/*.js' - - 'src/**/*.js' + - "src/scripts/**/*.js" + - "src/**/*.js" # Core functionality core: - - 'src/scripts/scrumHelper.js' - - 'src/scripts/emailClientAdapter.js' + - "src/scripts/scrumHelper.js" + - "src/scripts/emailClientAdapter.js" # Documentation documentation: - - '**/*.md' - - 'docs/**' - - 'LICENSE' - - 'README.md' + - "**/*.md" + - "docs/**" + - "LICENSE" + - "README.md" # Configuration files config: - - '.github/**/*' - - '*.json' - - '*.yml' - - '*.yaml' - - 'package.json' - - 'package-lock.json' + - ".github/**/*" + - "*.json" + - "*.yml" + - "*.yaml" + - "package.json" + - "package-lock.json" # Browser extension specific extension: - - 'manifest.json' - - 'src/popup.html' - - 'src/scripts/main.js' + - "manifest.json" + - "src/popup.html" + - "src/scripts/main.js" # Testing testing: - - 'tests/**' - - '**/*.test.js' - - '**/*.spec.js' + - "tests/**" + - "**/*.test.js" + - "**/*.spec.js" # Dependencies dependencies: - - 'package.json' - - 'package-lock.json' - - 'yarn.lock' + - "package.json" + - "package-lock.json" + - "yarn.lock" # Additional labels (manually applied, no patterns) bug: [] diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 0aeb42b..b23a56f 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -3,7 +3,9 @@ name: Sync Forks with Upstream on: schedule: # Run daily at 2:00 AM UTC + - cron: '0 2 * * *' + workflow_dispatch: # Allow manual triggering @@ -12,7 +14,9 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + + steps: - name: Sync forks with upstream uses: actions/github-script@v7 @@ -20,6 +24,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { Octokit } = require('@octokit/rest'); + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN @@ -31,7 +36,7 @@ jobs: let syncedCount = 0; let skippedCount = 0; let errorCount = 0; - + try { // Get all forks console.log(`Getting forks of ${UPSTREAM_OWNER}/${UPSTREAM_REPO}...`); @@ -93,4 +98,6 @@ jobs: } catch (error) { console.error('Workflow failed:', error.message); throw error; - } \ No newline at end of file + + } + diff --git a/README.md b/README.md index a91c33e..11d757b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - # Scrum Helper **Scrum Helper** is a Chrome extension that simplifies writing development reports by auto-filling content based on your Git activity. Just enter your GitHub username, select a date range, and choose your preferences, the extension automatically fetches your commits, pull requests, issues, and code reviews via the GitHub API and generates a pre-filled report that you can edit as needed. While currently focused on Git-based workflows, Scrum Helper is designed to expand to other platforms in the future. @@ -72,6 +71,7 @@ * *Requires a GitHub token.* ### Usage Standalone + - Click on `GENERATE` button to generate the scrum preview. - Edit it in the window. - Copy the rich HTML using the `COPY` button. @@ -99,38 +99,46 @@ $ npm install 1. **Install the Extension** + * For Chrome: Load it into your browser through [Chrome Extension Developer Mode](https://developer.chrome.com/docs/extensions/mv3/getstarted/). + + 2. **Build the Extension** * For Chrome: Rebuild or reload the extension in your browser (`chrome://extensions` → Refresh your extension). 3. **How to Obtain a GitHub Personal Access Token** + - To use Scrum Helper with authenticated requests (for higher rate limits and private repositories), you need a GitHub personal access token. #### Steps to Generate a Token 1. **Go to GitHub Developer Settings:** - Visit [https://github.com/settings/tokens](https://github.com/settings/tokens) while logged in to your GitHub account. + Visit [https://github.com/settings/tokens](https://github.com/settings/tokens) while logged in to your GitHub account. - 2. **Choose Token Type:** - - Select **"Personal access tokens (classic)"**. + 2. **Choose Token Type:** - 3. **Generate a New Token:** - - Click **"Generate new token"**. - - Give your token a descriptive name (e.g., "Scrum Helper Extension"). - - Set an expiration date if desired. + - Select **"Personal access tokens (classic)"**. - 4. **Create and Copy the Token:** - - Click **"Generate token"** at the bottom. - - **Copy the token** and save it securely. You will not be able to see it again! + 3. **Generate a New Token:** - 5. **Paste the Token in Scrum Helper:** - - Open the Scrum Helper extension popup. - - Paste your token into the "GitHub Token" field. + - Click **"Generate new token"**. + - Give your token a descriptive name (e.g., "Scrum Helper Extension"). + - Set an expiration date if desired. + + 4. **Create and Copy the Token:** + + - Click **"Generate token"** at the bottom. + - **Copy the token** and save it securely. You will not be able to see it again! + + 5. **Paste the Token in Scrum Helper:** + + - Open the Scrum Helper extension popup. + - Paste your token into the "GitHub Token" field. > **Keep your token secret!** Never share it or commit it to public repositories. @@ -138,6 +146,8 @@ $ npm install GitHub tokens allow the extension to make authenticated requests, increasing your API rate limit and enabling access to private repositories if you grant those permissions. + + ## About contributing - Follow the Issues and PRs templates as far as possible. diff --git a/docs/images/icon128x128.svg b/docs/images/icon128x128.svg index 27d0671..b4406c5 100644 --- a/docs/images/icon128x128.svg +++ b/docs/images/icon128x128.svg @@ -1,65 +1,65 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/index.css b/src/index.css index 2c65ed2..0555c50 100644 --- a/src/index.css +++ b/src/index.css @@ -688,4 +688,34 @@ hr, color: #2563eb; font-weight: 500; text-decoration: underline; +} + +.dark-mode #platformDropdownBtn { + background-color: #404040 !important; + border-color: #505050 !important; + color: #ffffff !important; +} + +.dark-mode #platformDropdownBtn:focus { + outline: 2px solid #2563eb; +} + +#platformDropdownList { + min-width: unset; + width: unset !important; + z-index: 1000 !important; +} + +.dark-mode #platformDropdownList { + min-width: unset; + width: unset !important; + z-index: 1000 !important; +} + +.dark-mode #platformDropdownList li { + color: #ffffff !important; +} + +.dark-mode #platformDropdownList li:hover { + background-color: #374151 !important; } \ No newline at end of file diff --git a/src/manifest.json b/src/manifest.json index 69eaa16..08f9aa4 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -20,25 +20,33 @@ "*://groups.google.com/forum/*", "*://groups.google.com/g/*", "*://groups.google.com/u/*/g/*", - "*://mail.google.com/*", - "*://outlook.live.com/*", - "*://outlook.office.com/*", + "*://mail.google.com/*", + "*://outlook.live.com/*", + "*://outlook.office.com/*", "*://mail.yahoo.com/*" ], - "js": ["scripts/jquery-3.2.1.min.js", "scripts/emailClientAdapter.js", "scripts/scrumHelper.js"] + "js": [ + "scripts/jquery-3.2.1.min.js", + "scripts/emailClientAdapter.js", + "scripts/gitlabHelper.js", + "scripts/scrumHelper.js" + ] } ], - "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self';" }, - "web_accessible_resources": [{ - "resources": [ - "/icons/night-mode.png", - "icons/night-mode.png" - ], - "matches": [""] - }], + "web_accessible_resources": [ + { + "resources": [ + "/icons/night-mode.png", + "icons/night-mode.png" + ], + "matches": [ + "" + ] + } + ], "permissions": [ "tabs", "storage", @@ -50,6 +58,7 @@ "*://*.outlook.live.com/*", "*://*.office.com/*", "*://*.yahoo.com/*", - "https://api.github.com/*" + "https://api.github.com/*", + "https://gitlab.com/*" ] } \ No newline at end of file diff --git a/src/popup.html b/src/popup.html index 8cfc142..5eae063 100644 --- a/src/popup.html +++ b/src/popup.html @@ -17,6 +17,26 @@ overflow-y: scroll; background: #eae4e4; } + + #platformSelect option { + font-family: 'FontAwesome', 'Arial', sans-serif; + } + + #customPlatformDropdown { + position: relative; + } + + #platformDropdownList { + display: none; + } + + #customPlatformDropdown.open #platformDropdownList { + display: block; + } + + #platformDropdownBtn:focus { + outline: 2px solid #2563eb; + } @@ -48,8 +68,7 @@

Scrum @@ -60,26 +79,61 @@

Scrum
-

Your Project Name + +

Platform +

+
+ + + +
+
+
+

Your Project Name + This is the name that appears in the subject line of your email, followed by timestamp. -

+ +

+ + + + class="w-full border-2 border-gray-200 bg-gray-200 rounded-xl text-gray-800 p-2 my-2" + placeholder="Enter your project name" style="margin-top: 0.3rem; margin-bottom: 0.8rem;">
-

Your GitHub Username

- + +

Your Username

+
-

Fetch your contributions between:

+

Fetch your contributions between:

+
@@ -94,18 +148,16 @@

Scrum
- +
- +

-
+

@@ -114,13 +166,12 @@

Scrum

-
+
-
-
+
No repositories selected (all will be included) @@ -269,7 +319,7 @@
Scrum Report
- +

Enter cache TTL (in minutes) @@ -350,7 +400,9 @@

Note:

+ + diff --git a/src/scripts/fontawesome.js b/src/scripts/fontawesome.js index e758655..69105eb 100644 --- a/src/scripts/fontawesome.js +++ b/src/scripts/fontawesome.js @@ -565,4 +565,4 @@ window.FontAwesomeCdnConfig = { }); } } catch (s) {} -})(); +})(); \ No newline at end of file diff --git a/src/scripts/gitlabHelper.js b/src/scripts/gitlabHelper.js new file mode 100644 index 0000000..5ff9698 --- /dev/null +++ b/src/scripts/gitlabHelper.js @@ -0,0 +1,275 @@ +// GitLab API Helper for Scrum Helper Extension +class GitLabHelper { + constructor() { + this.baseUrl = 'https://gitlab.com/api/v4'; + this.cache = { + data: null, + cacheKey: null, + timestamp: 0, + ttl: 10 * 60 * 1000, // 10 minutes + fetching: false, + queue: [] + }; + } + + async getCacheTTL() { + return new Promise((resolve) => { + chrome.storage.local.get(['cacheInput'], (items) => { + const ttl = items.cacheInput ? parseInt(items.cacheInput) * 60 * 1000 : 10 * 60 * 1000; + resolve(ttl); + }); + }); + } + + async saveToStorage(data) { + return new Promise((resolve) => { + chrome.storage.local.set({ + gitlabCache: { + data: data, + cacheKey: this.cache.cacheKey, + timestamp: this.cache.timestamp + } + }, resolve); + }); + } + + async loadFromStorage() { + return new Promise((resolve) => { + chrome.storage.local.get(['gitlabCache'], (items) => { + if (items.gitlabCache) { + this.cache.data = items.gitlabCache.data; + this.cache.cacheKey = items.gitlabCache.cacheKey; + this.cache.timestamp = items.gitlabCache.timestamp; + console.log('Restored GitLab cache from storage'); + } + resolve(); + }); + }); + } + + async fetchGitLabData(username, startDate, endDate) { + const cacheKey = `${username}-${startDate}-${endDate}`; + + if (this.cache.fetching || (this.cache.cacheKey === cacheKey && this.cache.data)) { + console.log('GitLab fetch already in progress or data already fetched. Skipping fetch.'); + return this.cache.data; + } + + console.log('Fetching GitLab data:', { + username: username, + startDate: startDate, + endDate: endDate, + }); + + // Check if we need to load from storage + if (!this.cache.data && !this.cache.fetching) { + await this.loadFromStorage(); + } + + const currentTTL = await this.getCacheTTL(); + this.cache.ttl = currentTTL; + console.log(`GitLab caching for ${currentTTL / (60 * 1000)} minutes`); + + const now = Date.now(); + const isCacheFresh = (now - this.cache.timestamp) < this.cache.ttl; + const isCacheKeyMatch = this.cache.cacheKey === cacheKey; + + if (this.cache.data && isCacheFresh && isCacheKeyMatch) { + console.log('Using cached GitLab data - cache is fresh and key matches'); + return this.cache.data; + } + + if (!isCacheKeyMatch) { + console.log('GitLab cache key mismatch - fetching new data'); + this.cache.data = null; + } else if (!isCacheFresh) { + console.log('GitLab cache is stale - fetching new data'); + } + + if (this.cache.fetching) { + console.log('GitLab fetch in progress, queuing requests'); + return new Promise((resolve, reject) => { + this.cache.queue.push({ resolve, reject }); + }); + } + + this.cache.fetching = true; + this.cache.cacheKey = cacheKey; + + try { + // Throttling 500ms to avoid burst + await new Promise(res => setTimeout(res, 500)); + + // Get user info first + const userUrl = `${this.baseUrl}/users?username=${username}`; + const userRes = await fetch(userUrl); + if (!userRes.ok) { + throw new Error(`Error fetching GitLab user: ${userRes.status} ${userRes.statusText}`); + } + const users = await userRes.json(); + if (users.length === 0) { + throw new Error(`GitLab user '${username}' not found`); + } + const userId = users[0].id; + + // Fetch all projects the user is a member of (including group projects) + const membershipProjectsUrl = `${this.baseUrl}/users/${userId}/projects?membership=true&per_page=100&order_by=updated_at&sort=desc`; + const membershipProjectsRes = await fetch(membershipProjectsUrl); + if (!membershipProjectsRes.ok) { + throw new Error(`Error fetching GitLab membership projects: ${membershipProjectsRes.status} ${membershipProjectsRes.statusText}`); + } + const membershipProjects = await membershipProjectsRes.json(); + + // Fetch all projects the user has contributed to (public, group, etc.) + const contributedProjectsUrl = `${this.baseUrl}/users/${userId}/contributed_projects?per_page=100&order_by=updated_at&sort=desc`; + const contributedProjectsRes = await fetch(contributedProjectsUrl); + if (!contributedProjectsRes.ok) { + throw new Error(`Error fetching GitLab contributed projects: ${contributedProjectsRes.status} ${contributedProjectsRes.statusText}`); + } + const contributedProjects = await contributedProjectsRes.json(); + + // Merge and deduplicate projects by project id + const allProjectsMap = new Map(); + for (const p of [...membershipProjects, ...contributedProjects]) { + allProjectsMap.set(p.id, p); + } + const allProjects = Array.from(allProjectsMap.values()); + + // Fetch merge requests from each project (works without auth for public projects) + let allMergeRequests = []; + for (const project of allProjects) { + try { + const projectMRsUrl = `${this.baseUrl}/projects/${project.id}/merge_requests?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; + const projectMRsRes = await fetch(projectMRsUrl); + if (projectMRsRes.ok) { + const projectMRs = await projectMRsRes.json(); + allMergeRequests = allMergeRequests.concat(projectMRs); + } + // Add small delay to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 100)); + } catch (error) { + console.error(`Error fetching MRs for project ${project.name}:`, error); + // Continue with other projects + } + } + + // Fetch issues from each project (works without auth for public projects) + let allIssues = []; + for (const project of allProjects) { + try { + const projectIssuesUrl = `${this.baseUrl}/projects/${project.id}/issues?author_id=${userId}&created_after=${startDate}T00:00:00Z&created_before=${endDate}T23:59:59Z&per_page=100&order_by=updated_at&sort=desc`; + const projectIssuesRes = await fetch(projectIssuesUrl); + if (projectIssuesRes.ok) { + const projectIssues = await projectIssuesRes.json(); + allIssues = allIssues.concat(projectIssues); + } + // Add small delay to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 100)); + } catch (error) { + console.error(`Error fetching issues 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 + comments: [] // Empty array since we're not fetching comments + }; + // Cache the data + this.cache.data = gitlabData; + this.cache.timestamp = Date.now(); + + await this.saveToStorage(gitlabData); + + // Resolve queued calls + this.cache.queue.forEach(({ resolve }) => resolve(gitlabData)); + this.cache.queue = []; + + return gitlabData; + + } catch (err) { + console.error('GitLab Fetch Failed:', err); + // Reject queued calls on error + this.cache.queue.forEach(({ reject }) => reject(err)); + this.cache.queue = []; + throw err; + } finally { + this.cache.fetching = false; + } + } + + async getDetailedMergeRequests(mergeRequests) { + const detailed = []; + for (const mr of mergeRequests) { + try { + const url = `${this.baseUrl}/projects/${mr.project_id}/merge_requests/${mr.iid}`; + const res = await fetch(url); + if (res.ok) { + const detailedMr = await res.json(); + detailed.push(detailedMr); + } + // Add small delay to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 100)); + } catch (error) { + console.error(`[GITLAB-DEBUG] Error fetching detailed MR ${mr.iid}:`, error); + detailed.push(mr); // Use basic data if detailed fetch fails + } + } + return detailed; + } + + async getDetailedIssues(issues) { + const detailed = []; + for (const issue of issues) { + try { + const url = `${this.baseUrl}/projects/${issue.project_id}/issues/${issue.iid}`; + const res = await fetch(url); + if (res.ok) { + const detailedIssue = await res.json(); + detailed.push(detailedIssue); + } + // Add small delay to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 100)); + } catch (error) { + console.error(`[GITLAB-DEBUG] Error fetching detailed issue ${issue.iid}:`, error); + detailed.push(issue); // Use basic data if detailed fetch fails + } + } + return detailed; + } + + formatDate(dateString) { + const date = new Date(dateString); + const options = { day: '2-digit', month: 'short', year: 'numeric' }; + return date.toLocaleDateString('en-US', options); + } + + processGitLabData(data) { + const processed = { + mergeRequests: data.mergeRequests || [], + issues: data.issues || [], + comments: data.comments || [], + user: data.user + }; + console.log('[GITLAB-DEBUG] processGitLabData input:', data); + console.log('[GITLAB-DEBUG] processGitLabData output:', processed); + console.log('GitLab data processed:', { + mergeRequests: processed.mergeRequests.length, + issues: processed.issues.length, + comments: processed.comments.length, + user: processed.user?.username + }); + + return processed; + } +} + +// Export for use in other scripts +if (typeof module !== 'undefined' && module.exports) { + module.exports = GitLabHelper; +} else { + window.GitLabHelper = GitLabHelper; +} \ No newline at end of file diff --git a/src/scripts/main.js b/src/scripts/main.js index c76cd40..b131bf0 100644 --- a/src/scripts/main.js +++ b/src/scripts/main.js @@ -1,5 +1,5 @@ let enableToggleElement = document.getElementById('enable'); -let githubUsernameElement = document.getElementById('githubUsername'); +let platformUsernameElement = document.getElementById('platformUsername'); let githubTokenElement = document.getElementById('githubToken'); let cacheInputElement = document.getElementById('cacheInput'); let projectNameElement = document.getElementById('projectName'); @@ -14,7 +14,7 @@ let showCommitsElement = document.getElementById('showCommits'); function handleBodyOnLoad() { chrome.storage.local.get( [ - 'githubUsername', + 'platformUsername', 'projectName', 'enableToggle', 'startingDate', @@ -28,8 +28,8 @@ function handleBodyOnLoad() { 'showCommits', ], (items) => { - if (items.githubUsername) { - githubUsernameElement.value = items.githubUsername; + if (items.platformUsername) { + platformUsernameElement.value = items.platformUsername; } if (items.githubToken) { githubTokenElement.value = items.githubToken; @@ -212,9 +212,9 @@ function getToday() { return WeekDisplayPadded; } -function handleGithubUsernameChange() { - let value = githubUsernameElement.value; - chrome.storage.local.set({ githubUsername: value }); +function handlePlatformUsernameChange() { + let value = platformUsernameElement.value; + chrome.storage.local.set({ platformUsername: value }); } function handleGithubTokenChange() { let value = githubTokenElement.value; @@ -251,8 +251,10 @@ function handleShowCommitsChange() { } enableToggleElement.addEventListener('change', handleEnableChange); -githubUsernameElement.addEventListener('keyup', handleGithubUsernameChange); -githubTokenElement.addEventListener('keyup', handleGithubTokenChange); +platformUsernameElement.addEventListener('keyup', handlePlatformUsernameChange); +if (githubTokenElement) { + githubTokenElement.addEventListener('keyup', handleGithubTokenChange); +} cacheInputElement.addEventListener('keyup', handleCacheInputChange); projectNameElement.addEventListener('keyup', handleProjectNameChange); startingDateElement.addEventListener('change', handleStartingDateChange); diff --git a/src/scripts/popup.js b/src/scripts/popup.js index ba365d5..fcd7d51 100644 --- a/src/scripts/popup.js +++ b/src/scripts/popup.js @@ -69,13 +69,20 @@ document.addEventListener('DOMContentLoaded', function () { const orgInput = document.getElementById('orgInput'); const setOrgBtn = document.getElementById('setOrgBtn'); + + const platformSelect = document.getElementById('platformSelect'); + const usernameLabel = document.getElementById('usernameLabel'); + const platformUsername = document.getElementById('platformUsername'); + function checkTokenForFilter() { const useRepoFilter = document.getElementById('useRepoFilter'); const githubTokenInput = document.getElementById('githubToken'); const tokenWarning = document.getElementById('tokenWarningForFilter'); const repoFilterContainer = document.getElementById('repoFilterContainer'); + if (!useRepoFilter || !githubTokenInput || !tokenWarning || !repoFilterContainer) { + return; } const isFilterEnabled = useRepoFilter.checked; @@ -97,6 +104,7 @@ document.addEventListener('DOMContentLoaded', function () { } + chrome.storage.local.get(['darkMode'], function (result) { if (result.darkMode) { body.classList.add('dark-mode'); @@ -153,6 +161,7 @@ document.addEventListener('DOMContentLoaded', function () { } function updateContentState(enableToggle) { + console.log('[DEBUG] updateContentState called with:', enableToggle); const elementsToToggle = [ 'startingDate', 'endingDate', @@ -160,12 +169,15 @@ document.addEventListener('DOMContentLoaded', function () { 'copyReport', 'refreshCache', 'showOpenLabel', + 'showCommits', 'scrumReport', 'githubUsername', 'githubToken', 'projectName', + 'platformUsername', + 'orgInput', + 'cacheInput', 'settingsToggle', - ]; const radios = document.querySelectorAll('input[name="timeframe"]'); @@ -225,21 +237,36 @@ document.addEventListener('DOMContentLoaded', function () { } chrome.storage.local.get(['enableToggle'], (items) => { + console.log('[DEBUG] Storage items received:', items); const enableToggle = items.enableToggle !== false; + console.log('[DEBUG] enableToggle calculated:', enableToggle); + + // If enableToggle is undefined (first install), set it to true by default + if (typeof items.enableToggle === 'undefined') { + console.log('[DEBUG] Setting default enableToggle to true'); + chrome.storage.local.set({ enableToggle: true }); + } + + console.log('[DEBUG] Calling updateContentState with:', enableToggle); updateContentState(enableToggle); if (!enableToggle) { + console.log('[DEBUG] Extension disabled, returning early'); return; } + console.log('[DEBUG] Extension enabled, initializing popup'); initializePopup(); checkTokenForFilter(); }) chrome.storage.onChanged.addListener((changes, namespace) => { + console.log('[DEBUG] Storage changed:', changes, namespace); if (namespace === 'local' && changes.enableToggle) { + console.log('[DEBUG] enableToggle changed to:', changes.enableToggle.newValue); updateContentState(changes.enableToggle.newValue); if (changes.enableToggle.newValue) { // re-initialize if enabled + console.log('[DEBUG] Re-initializing popup due to enable toggle change'); initializePopup(); } } @@ -255,19 +282,66 @@ document.addEventListener('DOMContentLoaded', function () { }); function initializePopup() { + // Restore all persistent fields immediately on DOMContentLoaded + const projectNameInput = document.getElementById('projectName'); + const orgInput = document.getElementById('orgInput'); + const userReasonInput = document.getElementById('userReason'); + const showOpenLabelCheckbox = document.getElementById('showOpenLabel'); + const showCommitsCheckbox = document.getElementById('showCommits'); + const githubTokenInput = document.getElementById('githubToken'); + const cacheInput = document.getElementById('cacheInput'); + const enableToggleSwitch = document.getElementById('enable'); + const lastWeekRadio = document.getElementById('lastWeekContribution'); + const yesterdayRadio = document.getElementById('yesterdayContribution'); + const startingDateInput = document.getElementById('startingDate'); + const endingDateInput = document.getElementById('endingDate'); + const platformUsername = document.getElementById('platformUsername'); + + chrome.storage.local.get([ + 'projectName', 'orgName', 'userReason', 'showOpenLabel', 'showCommits', 'githubToken', 'cacheInput', + 'enableToggle', 'lastWeekContribution', 'yesterdayContribution', 'startingDate', 'endingDate', 'selectedTimeframe', 'platform', 'platformUsername' + ], function (result) { + if (result.projectName) projectNameInput.value = result.projectName; + if (result.orgName) orgInput.value = result.orgName; + if (result.userReason) userReasonInput.value = result.userReason; + if (typeof result.showOpenLabel !== 'undefined') showOpenLabelCheckbox.checked = result.showOpenLabel; + if (typeof result.showCommits !== 'undefined') showCommitsCheckbox.checked = result.showCommits; + if (result.githubToken) githubTokenInput.value = result.githubToken; + if (result.cacheInput) cacheInput.value = result.cacheInput; + if (enableToggleSwitch) { + if (typeof result.enableToggle !== 'undefined') { + enableToggleSwitch.checked = result.enableToggle; + } else { + enableToggleSwitch.checked = true; // Default to enabled + } + } + if (typeof result.lastWeekContribution !== 'undefined') lastWeekRadio.checked = result.lastWeekContribution; + if (typeof result.yesterdayContribution !== 'undefined') yesterdayRadio.checked = result.yesterdayContribution; + if (result.startingDate) startingDateInput.value = result.startingDate; + if (result.endingDate) endingDateInput.value = result.endingDate; + platformUsername.value = result.platformUsername || ''; + }); // Button setup const generateBtn = document.getElementById('generateReport'); const copyBtn = document.getElementById('copyReport'); generateBtn.addEventListener('click', function () { - // Check org input value before generating report - let org = orgInput.value.trim().toLowerCase(); - // Allow empty org to fetch all GitHub activities - chrome.storage.local.set({ orgName: org }, () => { - generateBtn.innerHTML = ' Generating...'; - generateBtn.disabled = true; - window.generateScrumReport(); + chrome.storage.local.set({ + platform: platformSelect.value, + platformUsername: platformUsername.value + }, () => { + let org = orgInput.value.trim().toLowerCase(); + chrome.storage.local.set({ orgName: org }, () => { + // Reload platform from storage before generating report + chrome.storage.local.get(['platform'], function (res) { + platformSelect.value = res.platform || 'github'; + updatePlatformUI(platformSelect.value); + generateBtn.innerHTML = ' Generating...'; + generateBtn.disabled = true; + window.generateScrumReport && window.generateScrumReport(); + }); + }); }); }); @@ -378,6 +452,55 @@ document.addEventListener('DOMContentLoaded', function () { }); } }); + + // Save all fields to storage on input/change + projectNameInput.addEventListener('input', function () { + chrome.storage.local.set({ projectName: projectNameInput.value }); + }); + orgInput.addEventListener('input', function () { + chrome.storage.local.set({ orgName: orgInput.value.trim().toLowerCase() }); + }); + userReasonInput.addEventListener('input', function () { + chrome.storage.local.set({ userReason: userReasonInput.value }); + }); + showOpenLabelCheckbox.addEventListener('change', function () { + chrome.storage.local.set({ showOpenLabel: showOpenLabelCheckbox.checked }); + }); + showCommitsCheckbox.addEventListener('change', function () { + chrome.storage.local.set({ showCommits: showCommitsCheckbox.checked }); + }); + githubTokenInput.addEventListener('input', function () { + chrome.storage.local.set({ githubToken: githubTokenInput.value }); + }); + cacheInput.addEventListener('input', function () { + chrome.storage.local.set({ cacheInput: cacheInput.value }); + }); + if (enableToggleSwitch) { + console.log('[DEBUG] Setting up enable toggle switch event listener'); + enableToggleSwitch.addEventListener('change', function () { + console.log('[DEBUG] Enable toggle changed to:', enableToggleSwitch.checked); + chrome.storage.local.set({ enableToggle: enableToggleSwitch.checked }); + }); + } + lastWeekRadio.addEventListener('change', function () { + chrome.storage.local.set({ lastWeekContribution: lastWeekRadio.checked }); + }); + yesterdayRadio.addEventListener('change', function () { + chrome.storage.local.set({ yesterdayContribution: yesterdayRadio.checked }); + }); + startingDateInput.addEventListener('input', function () { + chrome.storage.local.set({ startingDate: startingDateInput.value }); + }); + endingDateInput.addEventListener('input', function () { + chrome.storage.local.set({ endingDate: endingDateInput.value }); + }); + + // Save username to storage on input + platformUsername.addEventListener('input', function () { + chrome.storage.local.set({ platformUsername: platformUsername.value }); + }); + + } function showReportView() { @@ -385,7 +508,7 @@ document.addEventListener('DOMContentLoaded', function () { reportSection.classList.remove('hidden'); settingsSection.classList.add('hidden'); settingsToggle.classList.remove('active'); - console.log('Switched to report view'); + } function showSettingsView() { @@ -393,7 +516,7 @@ document.addEventListener('DOMContentLoaded', function () { reportSection.classList.add('hidden'); settingsSection.classList.remove('hidden'); settingsToggle.classList.add('active'); - console.log('Switched to settings view'); + } if (settingsToggle) { @@ -420,6 +543,15 @@ document.addEventListener('DOMContentLoaded', function () { }); + // Debug function to test storage + window.testStorage = function () { + chrome.storage.local.get(['enableToggle'], function (result) { + console.log('[TEST] Current enableToggle value:', result.enableToggle); + }); + }; + + + //report filter const repoSearch = document.getElementById('repoSearch'); const repoDropdown = document.getElementById('repoDropdown'); @@ -450,6 +582,19 @@ document.addEventListener('DOMContentLoaded', function () { let highlightedIndex = -1; async function triggerRepoFetchIfEnabled() { + // --- PLATFORM CHECK: Only run for GitHub --- + let platform = 'github'; + try { + const items = await new Promise(resolve => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) { } + if (platform !== 'github') { + // Do not run repo fetch for non-GitHub platforms + if (repoStatus) repoStatus.textContent = 'Repository filtering is only available for GitHub.'; + return; + } if (!useRepoFilter.checked) { return; } @@ -463,19 +608,19 @@ document.addEventListener('DOMContentLoaded', function () { chrome.storage.local.get(['repoCache'], resolve); }); const items = await new Promise(resolve => { - chrome.storage.local.get(['githubUsername', 'githubToken', 'orgName'], resolve); + chrome.storage.local.get(['platformUsername', 'githubToken', 'orgName'], resolve); }); - if (!items.githubUsername) { + if (!items.platformUsername) { if (repoStatus) { - repoStatus.textContent = 'GitHub username required'; + repoStatus.textContent = 'Username required'; } return; } if (window.fetchUserRepositories) { const repos = await window.fetchUserRepositories( - items.githubUsername, + items.platformUsername, items.githubToken, items.orgName || '' ); @@ -486,7 +631,7 @@ document.addEventListener('DOMContentLoaded', function () { repoStatus.textContent = `${repos.length} repositories loaded`; } - const repoCacheKey = `repos-${items.githubUsername}-${items.orgName || ''}`; + const repoCacheKey = `repos-${items.platformUsername}-${items.orgName || ''}`; chrome.storage.local.set({ repoCache: { data: repos, @@ -504,7 +649,7 @@ document.addEventListener('DOMContentLoaded', function () { } } } catch (err) { - console.error('Auto refetch failed:', err); + if (repoStatus) { repoStatus.textContent = `Error: ${err.message || 'Failed to refetch repos'}`; } @@ -525,6 +670,20 @@ document.addEventListener('DOMContentLoaded', function () { }); useRepoFilter.addEventListener('change', debounce(async () => { + // --- PLATFORM CHECK: Only run for GitHub --- + let platform = 'github'; + try { + const items = await new Promise(resolve => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) { } + if (platform !== 'github') { + repoFilterContainer.classList.add('hidden'); + useRepoFilter.checked = false; + if (repoStatus) repoStatus.textContent = 'Repository filtering is only available for GitHub.'; + return; + } const enabled = useRepoFilter.checked; const hasToken = githubTokenInput.value.trim() !== ''; repoFilterContainer.classList.toggle('hidden', !enabled); @@ -559,15 +718,18 @@ document.addEventListener('DOMContentLoaded', function () { chrome.storage.local.get(['repoCache'], resolve); }); const items = await new Promise(resolve => { - chrome.storage.local.get(['githubUsername', 'githubToken', 'orgName'], resolve); + + chrome.storage.local.get(['platformUsername', 'githubToken', 'orgName'], resolve); }); - if (!items.githubUsername) { + if (!items.platformUsername) { + repoStatus.textContent = 'Github Username required'; return; } - const repoCacheKey = `repos-${items.githubUsername}-${items.orgName || ''}`; + const repoCacheKey = `repos-${items.platformUsername}-${items.orgName || ''}`; + const now = Date.now(); const cacheAge = cacheData.repoCache?.timestamp ? now - cacheData.repoCache.timestamp : Infinity; const cacheTTL = 10 * 60 * 1000; // 10 minutes @@ -588,7 +750,9 @@ document.addEventListener('DOMContentLoaded', function () { if (window.fetchUserRepositories) { const repos = await window.fetchUserRepositories( - items.githubUsername, + + items.platformUsername, + items.githubToken, items.orgName || '', ); @@ -608,7 +772,10 @@ document.addEventListener('DOMContentLoaded', function () { } } } catch (err) { + + console.error('Auto load repos failed', err); + if (err.message?.includes('401')) { repoStatus.textContent = 'Github token required for private repos'; } else if (err.message?.includes('username')) { @@ -675,9 +842,9 @@ document.addEventListener('DOMContentLoaded', function () { }); function debugRepoFetch() { - chrome.storage.local.get(['githubUsername', 'githubToken', 'orgName'], (items) => { + chrome.storage.local.get(['platformUsername', 'githubToken', 'orgName'], (items) => { console.log('Current settings:', { - username: items.githubUsername, + username: items.platformUsername, hasToken: !!items.githubToken, org: items.orgName || '' }); @@ -685,6 +852,18 @@ document.addEventListener('DOMContentLoaded', function () { } debugRepoFetch(); async function loadRepos() { + // --- PLATFORM CHECK: Only run for GitHub --- + let platform = 'github'; + try { + const items = await new Promise(resolve => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) { } + if (platform !== 'github') { + if (repoStatus) repoStatus.textContent = 'Repository loading is only available for GitHub.'; + return; + } console.log('window.fetchUserRepositories exists:', !!window.fetchUserRepositories); console.log('Available functions:', Object.keys(window).filter(key => key.includes('fetch'))); @@ -693,15 +872,17 @@ document.addEventListener('DOMContentLoaded', function () { return; } - chrome.storage.local.get(['githubUsername', 'githubToken'], (items) => { + chrome.storage.local.get(['platformUsername', 'githubToken'], (items) => { console.log('Storage data for repo fetch:', { - hasUsername: !!items.githubUsername, + hasUsername: !!items.platformUsername, hasToken: !!items.githubToken, - username: items.githubUsername + username: items.platformUsername }); - if (!items.githubUsername) { - repoStatus.textContent = 'GitHub username required'; + + if (!items.platformUsername) { + repoStatus.textContent = 'Username required'; + return; } @@ -710,6 +891,18 @@ document.addEventListener('DOMContentLoaded', function () { } async function performRepoFetch() { + // --- PLATFORM CHECK: Only run for GitHub --- + let platform = 'github'; + try { + const items = await new Promise(resolve => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) { } + if (platform !== 'github') { + if (repoStatus) repoStatus.textContent = 'Repository fetching is only available for GitHub.'; + return; + } console.log('[POPUP-DEBUG] performRepoFetch called.'); repoStatus.textContent = 'Loading repositories...'; repoSearch.classList.add('repository-search-loading'); @@ -719,9 +912,11 @@ document.addEventListener('DOMContentLoaded', function () { chrome.storage.local.get(['repoCache'], resolve); }); const storageItems = await new Promise(resolve => { - chrome.storage.local.get(['githubUsername', 'githubToken', 'orgName'], resolve); + + chrome.storage.local.get(['platformUsername', 'githubToken', 'orgName'], resolve) + }) - const repoCacheKey = `repos-${storageItems.githubUsername}-${storageItems.orgName || ''}`; + const repoCacheKey = `repos-${storageItems.platformUsername}-${storageItems.orgName || ''}`; const now = Date.now(); const cacheAge = cacheData.repoCache?.timestamp ? now - cacheData.repoCache.timestamp : Infinity; const cacheTTL = 10 * 60 * 1000; // 10 minutes @@ -749,7 +944,9 @@ document.addEventListener('DOMContentLoaded', function () { } console.log('[POPUP-DEBUG] No valid cache. Fetching from network.'); availableRepos = await window.fetchUserRepositories( - storageItems.githubUsername, + + storageItems.platformUsername, + storageItems.githubToken, storageItems.orgName || '' ); @@ -896,13 +1093,17 @@ document.addEventListener('DOMContentLoaded', function () { window.removeRepo = removeRepo; - chrome.storage.local.get(['githubUsername'], (items) => { - if (items.githubUsername && useRepoFilter.checked && availableRepos.length === 0) { + + chrome.storage.local.get(['platformUsername'], (items) => { + if (items.platformUsername && useRepoFilter.checked && availableRepos.length === 0) { + setTimeout(() => loadRepos(), 1000); } }) } }); + + // Auto-update orgName in storage on input change orgInput.addEventListener('input', function () { let org = orgInput.value.trim().toLowerCase(); @@ -915,10 +1116,8 @@ orgInput.addEventListener('input', function () { // Add click event for setOrgBtn to set org setOrgBtn.addEventListener('click', function () { let org = orgInput.value.trim().toLowerCase(); - // Do not default to any org, allow empty string - // if (!org) { - // org = 'fossasia'; - // } + + console.log('[Org Check] Checking organization:', org); if (!org) { // If org is empty, clear orgName in storage but don't auto-generate report @@ -970,6 +1169,7 @@ setOrgBtn.addEventListener('click', function () { const oldToast = document.getElementById('invalid-org-toast'); if (oldToast) oldToast.parentNode.removeChild(oldToast); + chrome.storage.local.set({ orgName: org }, function () { // Always clear the scrum report and show org changed message const scrumReport = document.getElementById('scrumReport'); @@ -1059,6 +1259,158 @@ if (cacheInput) { } +// Restore platform from storage or default to github +chrome.storage.local.get(['platform'], function (result) { + const platform = result.platform || 'github'; + platformSelect.value = platform; + updatePlatformUI(platform); +}); + +// Update UI for platform +function updatePlatformUI(platform) { + // Hide GitHub-specific settings for GitLab using the 'hidden' class + const orgSection = document.querySelector('.orgSection'); + if (orgSection) { + if (platform === 'gitlab') { + orgSection.classList.add('hidden'); + } else { + orgSection.classList.remove('hidden'); + } + } + // Hide all githubOnlySection elements for GitLab + const githubOnlySections = document.querySelectorAll('.githubOnlySection'); + githubOnlySections.forEach(el => { + if (platform === 'gitlab') { + el.classList.add('hidden'); + } else { + el.classList.remove('hidden'); + } + }); + // (Optional) You can update the label/placeholder here if you want + // Do NOT clear the username field here, only do it on actual platform change +} + +// On platform change +platformSelect.addEventListener('change', function () { + const platform = platformSelect.value; + chrome.storage.local.set({ platform }); + // Clear username field and storage on platform change + const platformUsername = document.getElementById('platformUsername'); + if (platformUsername) { + platformUsername.value = ''; + chrome.storage.local.set({ platformUsername: '' }); + } + updatePlatformUI(platform); +}); + +// Custom platform dropdown logic +const customDropdown = document.getElementById('customPlatformDropdown'); +const dropdownBtn = document.getElementById('platformDropdownBtn'); +const dropdownList = document.getElementById('platformDropdownList'); +const dropdownSelected = document.getElementById('platformDropdownSelected'); +const platformSelectHidden = document.getElementById('platformSelect'); + +function setPlatformDropdown(value) { + if (value === 'gitlab') { + dropdownSelected.innerHTML = ' GitLab'; + } else { + dropdownSelected.innerHTML = ' GitHub'; + } + platformSelectHidden.value = value; + chrome.storage.local.set({ platform: value }); + // Always clear username when user changes platform + const platformUsername = document.getElementById('platformUsername'); + if (platformUsername) { + platformUsername.value = ''; + chrome.storage.local.set({ platformUsername: '' }); + } + updatePlatformUI(value); +} + +dropdownBtn.addEventListener('click', function (e) { + e.stopPropagation(); + customDropdown.classList.toggle('open'); + dropdownList.classList.toggle('hidden'); +}); + +dropdownList.querySelectorAll('li').forEach(item => { + item.addEventListener('click', function (e) { + const newPlatform = this.getAttribute('data-value'); + const currentPlatform = platformSelectHidden.value; + + // Only clear username if platform is actually changing + if (newPlatform !== currentPlatform) { + platformUsername.value = ''; + chrome.storage.local.set({ platformUsername: '' }); + } + + setPlatformDropdown(newPlatform); + customDropdown.classList.remove('open'); + dropdownList.classList.add('hidden'); + }); +}); + +document.addEventListener('click', function (e) { + if (!customDropdown.contains(e.target)) { + customDropdown.classList.remove('open'); + dropdownList.classList.add('hidden'); + } +}); + +// Keyboard navigation +platformDropdownBtn.addEventListener('keydown', function (e) { + if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + customDropdown.classList.add('open'); + dropdownList.classList.remove('hidden'); + dropdownList.querySelector('li').focus(); + } +}); +dropdownList.querySelectorAll('li').forEach((item, idx, arr) => { + item.setAttribute('tabindex', '0'); + item.addEventListener('keydown', function (e) { + if (e.key === 'ArrowDown') { + e.preventDefault(); + (arr[idx + 1] || arr[0]).focus(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + (arr[idx - 1] || arr[arr.length - 1]).focus(); + } else if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + const newPlatform = this.getAttribute('data-value'); + const currentPlatform = platformSelectHidden.value; + + // Only clear username if platform is actually changing + if (newPlatform !== currentPlatform) { + platformUsername.value = ''; + chrome.storage.local.set({ platformUsername: '' }); + } + + setPlatformDropdown(newPlatform); + customDropdown.classList.remove('open'); + dropdownList.classList.add('hidden'); + dropdownBtn.focus(); + } + }); +}); + +// On load, restore platform from storage +chrome.storage.local.get(['platform'], function (result) { + const platform = result.platform || 'github'; + // Just update the UI without clearing username when restoring from storage + if (platform === 'gitlab') { + dropdownSelected.innerHTML = ' GitLab'; + } else { + dropdownSelected.innerHTML = ' GitHub'; + } + platformSelectHidden.value = platform; + updatePlatformUI(platform); +}); + + + + + // Tooltip bubble document.querySelectorAll('.tooltip-container').forEach(container => { const bubble = container.querySelector('.tooltip-bubble'); @@ -1127,6 +1479,7 @@ document.querySelectorAll('input[name="timeframe"]').forEach(radio => { }); // refresh cache button + document.getElementById('refreshCache').addEventListener('click', async function () { const button = this; const originalText = button.innerHTML; @@ -1136,9 +1489,19 @@ document.getElementById('refreshCache').addEventListener('click', async function button.disabled = true; try { - // Clear both caches + // Determine platform + let platform = 'github'; + try { + const items = await new Promise(resolve => { + chrome.storage.local.get(['platform'], resolve); + }); + platform = items.platform || 'github'; + } catch (e) { } + + // Clear all caches + const keysToRemove = ['githubCache', 'repoCache', 'gitlabCache']; await new Promise(resolve => { - chrome.storage.local.remove(['githubCache', 'repoCache'], resolve); + chrome.storage.local.remove(keysToRemove, resolve); }); // Clear the scrum report @@ -1159,7 +1522,7 @@ document.getElementById('refreshCache').addEventListener('click', async function button.innerHTML = 'Cache Cleared!'; button.classList.remove('loading'); - setTimeout(() => triggerRepoFetchIfEnabled(), 500); + // Do NOT trigger report generation automatically setTimeout(() => { button.innerHTML = originalText; @@ -1284,6 +1647,7 @@ function toggleRadio(radio) { triggerRepoFetchIfEnabled(); }); + } async function triggerRepoFetchIfEnabled() { diff --git a/src/scripts/scrumHelper.js b/src/scripts/scrumHelper.js index ce0bfbf..9ebd9da 100644 --- a/src/scripts/scrumHelper.js +++ b/src/scripts/scrumHelper.js @@ -1,35 +1,45 @@ const DEBUG = true; + function log(...args) { if (DEBUG) { console.log(`[SCRUM-HELPER]:`, ...args); } } + + function logError(...args) { if (DEBUG) { console.error('[SCRUM-HELPER]:', ...args); } } -console.log('Script loaded, adapter exists:', !!window.emailClientAdapter); + + let refreshButton_Placed = false; let enableToggle = true; let hasInjectedContent = false; let scrumGenerationInProgress = false; let orgName = ''; +let platform = 'github'; +let platformUsername = ''; +let gitlabHelper = null; function allIncluded(outputTarget = 'email') { + // Always re-instantiate gitlabHelper for gitlab platform to ensure fresh cache after refresh + if (platform === 'gitlab' || (typeof platform === 'undefined' && window.GitLabHelper)) { + gitlabHelper = new window.GitLabHelper(); + } if (scrumGenerationInProgress) { - console.warn('[SCRUM-HELPER]: Scrum generation already in progress, aborting new call.'); return; } scrumGenerationInProgress = true; console.log('allIncluded called with outputTarget:', outputTarget); - console.log('Current window context:', window.location.href); + let scrumBody = null; let scrumSubject = null; let startingDate = ''; let endingDate = ''; - let githubUsername = ''; + let platformUsernameLocal = ''; let githubToken = ''; let projectName = ''; let lastWeekArray = []; @@ -46,6 +56,7 @@ function allIncluded(outputTarget = 'email') { let showOpenLabel = true; let showCommits = false; let userReason = ''; + let subjectForEmail = null; let pr_open_button = '
open
'; @@ -62,10 +73,11 @@ function allIncluded(outputTarget = 'email') { '
open
'; function getChromeData() { - console.log("Getting Chrome data for context:", outputTarget); + console.log("[DEBUG] getChromeData called for outputTarget:", outputTarget); chrome.storage.local.get( [ - 'githubUsername', + 'platform', + 'platformUsername', 'githubToken', 'projectName', 'enableToggle', @@ -83,25 +95,26 @@ function allIncluded(outputTarget = 'email') { 'showCommits', ], (items) => { - console.log("Storage items received:", items); + + console.log("[DEBUG] Storage items received:", items); + platform = items.platform || 'github'; + platformUsername = items.platformUsername || ''; + platformUsernameLocal = platformUsername; + console.log(`[DEBUG] platform: ${platform}, platformUsername: ${platformUsername}`); if (outputTarget === 'popup') { - const usernameFromDOM = document.getElementById('githubUsername')?.value; + const usernameFromDOM = document.getElementById('platformUsername')?.value; const projectFromDOM = document.getElementById('projectName')?.value; const tokenFromDOM = document.getElementById('githubToken')?.value; - - items.githubUsername = usernameFromDOM || items.githubUsername; + items.platformUsername = usernameFromDOM || items.platformUsername; items.projectName = projectFromDOM || items.projectName; items.githubToken = tokenFromDOM || items.githubToken; - chrome.storage.local.set({ - githubUsername: items.githubUsername, + platformUsername: items.platformUsername, projectName: items.projectName, githubToken: items.githubToken }); } - - githubUsername = items.githubUsername; projectName = items.projectName; userReason = 'No Blocker at the moment'; @@ -109,11 +122,13 @@ function allIncluded(outputTarget = 'email') { githubToken = items.githubToken; lastWeekContribution = items.lastWeekContribution; yesterdayContribution = items.yesterdayContribution; - - if (!items.enableToggle) { + if (typeof items.enableToggle !== 'undefined') { enableToggle = items.enableToggle; } + showCommits = items.showCommits || false; + orgName = items.orgName || ''; + if (items.lastWeekContribution) { handleLastWeekContributionChange(); } else if (items.yesterdayContribution) { @@ -122,75 +137,190 @@ function allIncluded(outputTarget = 'email') { startingDate = items.startingDate; endingDate = items.endingDate; } else { - handleLastWeekContributionChange(); //when no date is stored i.e on fresh unpack - default to last week. + + + handleLastWeekContributionChange(); + + if (outputTarget === 'popup') { chrome.storage.local.set({ lastWeekContribution: true, yesterdayContribution: false }); } } - if (githubUsername) { - console.log("About to fetch GitHub data for:", githubUsername); - fetchGithubData(); - } else { - if (outputTarget === 'popup') { - console.log("No username found - popup context"); - // Show error in popup - const scrumReport = document.getElementById('scrumReport'); + + + + if (platform === 'github') { + if (platformUsernameLocal) { + + fetchGithubData(); + } else { + if (outputTarget === 'popup') { + console.log("[DEBUG] No username found - popup context"); + const scrumReport = document.getElementById('scrumReport'); + const generateBtn = document.getElementById('generateReport'); + if (scrumReport) { + scrumReport.innerHTML = '
Please enter your username to generate a report.
'; + } + if (generateBtn) { + generateBtn.innerHTML = ' Generate Report'; + generateBtn.disabled = false; + } + scrumGenerationInProgress = false; + } else { + console.warn('[DEBUG] No username found in storage'); + scrumGenerationInProgress = false; + } + return; + } + } else if (platform === 'gitlab') { + if (!gitlabHelper) gitlabHelper = new window.GitLabHelper(); + if (platformUsernameLocal) { const generateBtn = document.getElementById('generateReport'); - if (scrumReport) { - scrumReport.innerHTML = '
Please enter your GitHub username to generate a report.
'; + if (generateBtn && outputTarget === 'popup') { + generateBtn.innerHTML = ' Generating...'; + generateBtn.disabled = true; } - if (generateBtn) { - generateBtn.innerHTML = ' Generate Report'; - generateBtn.disabled = false; + + if (outputTarget === 'email') { + (async () => { + try { + const data = await gitlabHelper.fetchGitLabData(platformUsernameLocal, startingDate, endingDate); + + + function mapGitLabItem(item, projects, type) { + const project = projects.find(p => p.id === item.project_id); + const repoName = project ? project.name : 'unknown'; + + return { + ...item, + repository_url: `https://gitlab.com/api/v4/projects/${item.project_id}`, + html_url: type === 'issue' + ? (item.web_url || (project ? `${project.web_url}/-/issues/${item.iid}` : '')) + : (item.web_url || (project ? `${project.web_url}/-/merge_requests/${item.iid}` : '')), + number: item.iid, + title: item.title, + state: (type === 'issue' && item.state === 'opened') ? 'open' : item.state, + project: repoName, + pull_request: type === 'mr', + }; + } + const mappedIssues = (data.issues || []).map(issue => mapGitLabItem(issue, data.projects, 'issue')); + const mappedMRs = (data.mergeRequests || data.mrs || []).map(mr => mapGitLabItem(mr, data.projects, 'mr')); + const mappedData = { + githubIssuesData: { items: mappedIssues }, + githubPrsReviewData: { items: mappedMRs }, + githubUserData: data.user || {}, + }; + githubUserData = mappedData.githubUserData; + + let name = githubUserData?.name || githubUserData?.username || platformUsernameLocal || platformUsername; + let project = projectName || ''; + let curDate = new Date(); + let year = curDate.getFullYear().toString(); + let date = curDate.getDate(); + let month = curDate.getMonth() + 1; + if (month < 10) month = '0' + month; + if (date < 10) date = '0' + date; + let dateCode = year.toString() + month.toString() + date.toString(); + const subject = `[Scrum] ${name} - ${project} - ${dateCode}`; + subjectForEmail = subject; + + + await processGithubData(mappedData, true, subjectForEmail); + scrumGenerationInProgress = false; + } catch (err) { + console.error('GitLab fetch failed:', err); + if (outputTarget === 'popup') { + if (generateBtn) { + generateBtn.innerHTML = ' Generate Report'; + generateBtn.disabled = false; + } + const scrumReport = document.getElementById('scrumReport'); + if (scrumReport) { + scrumReport.innerHTML = `
${err.message || 'An error occurred while fetching GitLab data.'}
`; + } + } + scrumGenerationInProgress = false; + } + })(); + } else { + + gitlabHelper.fetchGitLabData(platformUsernameLocal, startingDate, endingDate) + .then(data => { + function mapGitLabItem(item, projects, type) { + const project = projects.find(p => p.id === item.project_id); + const repoName = project ? project.name : 'unknown'; + return { + ...item, + repository_url: `https://gitlab.com/api/v4/projects/${item.project_id}`, + html_url: type === 'issue' + ? (item.web_url || (project ? `${project.web_url}/-/issues/${item.iid}` : '')) + : (item.web_url || (project ? `${project.web_url}/-/merge_requests/${item.iid}` : '')), + number: item.iid, + title: item.title, + state: (type === 'issue' && item.state === 'opened') ? 'open' : item.state, + project: repoName, + pull_request: type === 'mr', + }; + } + const mappedIssues = (data.issues || []).map(issue => mapGitLabItem(issue, data.projects, 'issue')); + const mappedMRs = (data.mergeRequests || data.mrs || []).map(mr => mapGitLabItem(mr, data.projects, 'mr')); + const mappedData = { + githubIssuesData: { items: mappedIssues }, + githubPrsReviewData: { items: mappedMRs }, + githubUserData: data.user || {}, + }; + processGithubData(mappedData); + scrumGenerationInProgress = false; + }) + .catch(err => { + console.error('GitLab fetch failed:', err); + if (outputTarget === 'popup') { + if (generateBtn) { + generateBtn.innerHTML = ' Generate Report'; + generateBtn.disabled = false; + } + const scrumReport = document.getElementById('scrumReport'); + if (scrumReport) { + scrumReport.innerHTML = `
${err.message || 'An error occurred while fetching GitLab data.'}
`; + } + } + scrumGenerationInProgress = false; + }); } - scrumGenerationInProgress = false; + // --- FIX END --- } else { - console.warn('No GitHub username found in storage'); + if (outputTarget === 'popup') { + const scrumReport = document.getElementById('scrumReport'); + const generateBtn = document.getElementById('generateReport'); + if (scrumReport) { + scrumReport.innerHTML = '
Please enter your username to generate a report.
'; + } + if (generateBtn) { + generateBtn.innerHTML = ' Generate Report'; + generateBtn.disabled = false; + } + } scrumGenerationInProgress = false; } - return; - } - if (items.cacheInput) { - cacheInput = items.cacheInput; - } - - if (!items.showOpenLabel) { - showOpenLabel = false; - pr_unmerged_button = ''; - issue_opened_button = ''; - pr_merged_button = ''; - issue_closed_button = ''; - } - if (items.userReason) { - userReason = items.userReason; - } - if (items.showCommits !== undefined) { - showCommits = items.showCommits; } else { - showCommits = false; //default value - } - if (items.githubCache) { - githubCache.data = items.githubCache.data; - githubCache.cacheKey = items.githubCache.cacheKey; - githubCache.timestamp = items.githubCache.timestamp; - log('Restored cache from storage'); - } - if (typeof items.orgName !== 'undefined') { - orgName = items.orgName || ''; - console.log('[SCRUM-HELPER] orgName set to:', orgName); - } - if (items.selectedRepos) { - selectedRepos = items.selectedRepos; - } - if (items.useRepoFilter) { - useRepoFilter = items.useRepoFilter; + // Unknown platform + if (outputTarget === 'popup') { + const scrumReport = document.getElementById('scrumReport'); + if (scrumReport) { + scrumReport.innerHTML = '
Unknown platform selected.
'; + } + } + scrumGenerationInProgress = false; } }, ); } getChromeData(); + + function handleLastWeekContributionChange() { endingDate = getToday(); startingDate = getLastWeek(); @@ -337,7 +467,7 @@ function allIncluded(outputTarget = 'email') { } async function fetchGithubData() { - const cacheKey = `${githubUsername}-${startingDate}-${endingDate}-${orgName || 'all'}`; + const cacheKey = `${platformUsernameLocal}-${startingDate}-${endingDate}-${orgName || 'all'}`; if (githubCache.fetching || (githubCache.cacheKey === cacheKey && githubCache.data)) { log('Fetch already in progress or data already fetched. Skipping fetch.'); @@ -345,7 +475,7 @@ function allIncluded(outputTarget = 'email') { } log('Fetching Github data:', { - username: githubUsername, + username: platformUsernameLocal, startDate: startingDate, endDate: endingDate, }); @@ -451,16 +581,16 @@ function allIncluded(outputTarget = 'email') { }).join('+'); const orgQuery = orgPart ? `+${orgPart}` : ''; - issueUrl = `https://api.github.com/search/issues?q=author%3A${githubUsername}+${repoQueries}${orgQuery}+updated%3A${startingDate}..${endingDate}&per_page=100`; - prUrl = `https://api.github.com/search/issues?q=commenter%3A${githubUsername}+${repoQueries}${orgQuery}+updated%3A${startingDate}..${endingDate}&per_page=100`; - userUrl = `https://api.github.com/users/${githubUsername}`; + issueUrl = `https://api.github.com/search/issues?q=author%3A${platformUsernameLocal}+${repoQueries}${orgQuery}+updated%3A${startingDate}..${endingDate}&per_page=100`; + prUrl = `https://api.github.com/search/issues?q=commenter%3A${platformUsernameLocal}+${repoQueries}${orgQuery}+updated%3A${startingDate}..${endingDate}&per_page=100`; + userUrl = `https://api.github.com/users/${platformUsernameLocal}`; log('Repository-filtered URLs:', { issueUrl, prUrl }); } else { loadFromStorage('Using org wide search'); const orgQuery = orgPart ? `+${orgPart}` : ''; - issueUrl = `https://api.github.com/search/issues?q=author%3A${githubUsername}${orgQuery}+updated%3A${startingDate}..${endingDate}&per_page=100`; - prUrl = `https://api.github.com/search/issues?q=commenter%3A${githubUsername}${orgQuery}+updated%3A${startingDate}..${endingDate}&per_page=100`; - userUrl = `https://api.github.com/users/${githubUsername}`; + issueUrl = `https://api.github.com/search/issues?q=author%3A${platformUsernameLocal}${orgQuery}+updated%3A${startingDate}..${endingDate}&per_page=100`; + prUrl = `https://api.github.com/search/issues?q=commenter%3A${platformUsernameLocal}${orgQuery}+updated%3A${startingDate}..${endingDate}&per_page=100`; + userUrl = `https://api.github.com/users/${platformUsernameLocal}`; } try { @@ -496,13 +626,13 @@ function allIncluded(outputTarget = 'email') { if (githubIssuesData && githubIssuesData.items) { log('Fetched githubIssuesData:', githubIssuesData.items.length, 'items'); - // Collect open PRs + // Collect only open PRs for commit fetching const openPRs = githubIssuesData.items.filter( item => item.pull_request && item.state === 'open' ); log('Open PRs for commit fetching:', openPRs.map(pr => pr.number)); - // Fetch commits for open PRs (batch) - if (openPRs.length && githubToken) { + // Fetch commits for open PRs (batch) if showCommits is enabled + if (openPRs.length && githubToken && showCommits) { const commitMap = await fetchCommitsForOpenPRs(openPRs, githubToken, startingDate, endingDate); log('Commit map returned from fetchCommitsForOpenPRs:', commitMap); // Attach commits to PR objects @@ -564,24 +694,24 @@ function allIncluded(outputTarget = 'email') { const owner = repoParts[repoParts.length - 2]; const repo = repoParts[repoParts.length - 1]; return ` - pr${idx}: repository(owner: "${owner}", name: "${repo}") { - pullRequest(number: ${pr.number}) { - commits(first: 100) { - nodes { - commit { - messageHeadline - committedDate - url - author { - name - user { login } - } - } + pr${idx}: repository(owner: "${owner}", name: "${repo}") { + pullRequest(number: ${pr.number}) { + commits(first: 100) { + nodes { + commit { + messageHeadline + committedDate + url + author { + name + user { login } } } } + } + } - }`; + }`; }).join('\n'); const query = `query { ${queries} }`; log('GraphQL query for commits:', query); @@ -622,7 +752,7 @@ function allIncluded(outputTarget = 'email') { log('Repo fiter disabled, skipping fetch'); return []; } - const repoCacheKey = `repos-${githubUsername}-${orgName}-${startingDate}-${endingDate}`; + const repoCacheKey = `repos-${platformUsernameLocal}-${orgName}-${startingDate}-${endingDate}`; const now = Date.now(); const isRepoCacheFresh = (now - githubCache.repoTimeStamp) < githubCache.ttl; @@ -645,7 +775,7 @@ function allIncluded(outputTarget = 'email') { try { log('Fetching repos automatically'); - const repos = await fetchUserRepositories(githubUsername, githubToken, orgName); + const repos = await fetchUserRepositories(platformUsernameLocal, githubToken, orgName); githubCache.repoData = repos; githubCache.repoTimeStamp = now; @@ -711,6 +841,7 @@ function allIncluded(outputTarget = 'email') { } } + async function processGithubData(data) { log('Processing Github data'); @@ -726,30 +857,66 @@ function allIncluded(outputTarget = 'email') { githubUserData = filteredData.githubUserData; log('GitHub data set:', { + issues: githubIssuesData?.items?.length || 0, prs: githubPrsReviewData?.items?.length || 0, user: githubUserData?.login, filtered: useRepoFilter }); - - lastWeekArray = []; nextWeekArray = []; reviewedPrsArray = []; githubPrsReviewDataProcessed = {}; issuesDataProcessed = false; prsReviewDataProcessed = false; - if (!githubCache.subject && scrumSubject) { scrumSubjectLoaded(); } - await Promise.all([ - writeGithubIssuesPrs(), - writeGithubPrsReviews(), - ]) - log('Both data processing functions completed, generating scrum body'); - writeScrumBody(); + log('[SCRUM-DEBUG] Processing issues for main activity:', githubIssuesData?.items); + if (platform === 'github') { + await writeGithubIssuesPrs(githubIssuesData?.items || []); + } else if (platform === 'gitlab') { + await writeGithubIssuesPrs(githubIssuesData?.items || []); + await writeGithubIssuesPrs(githubPrsReviewData?.items || []); + } + await writeGithubPrsReviews(); + log('[DEBUG] Both data processing functions completed, generating scrum body'); + if (subjectForEmail) { + // Synchronized subject and body injection for email + let lastWeekUl = '
    '; + for (let i = 0; i < lastWeekArray.length; i++) lastWeekUl += lastWeekArray[i]; + for (let i = 0; i < reviewedPrsArray.length; i++) lastWeekUl += reviewedPrsArray[i]; + lastWeekUl += '
'; + let nextWeekUl = '
    '; + for (let i = 0; i < nextWeekArray.length; i++) nextWeekUl += nextWeekArray[i]; + nextWeekUl += '
'; + let weekOrDay = lastWeekContribution ? 'last week' : (yesterdayContribution ? 'yesterday' : 'the period'); + let weekOrDay2 = lastWeekContribution ? 'this week' : 'today'; + let content; + if (lastWeekContribution == true || yesterdayContribution == true) { + content = `1. What did I do ${weekOrDay}?
${lastWeekUl}
2. What do I plan to do ${weekOrDay2}?
${nextWeekUl}
3. What is blocking me from making progress?
${userReason}`; + } else { + content = `1. What did I do from ${formatDate(startingDate)} to ${formatDate(endingDate)}?
${lastWeekUl}
2. What do I plan to do ${weekOrDay2}?
${nextWeekUl}
3. What is blocking me from making progress?
${userReason}`; + } + // Wait for both subject and body to be available, then inject both + let injected = false; + let interval = setInterval(() => { + const elements = window.emailClientAdapter?.getEditorElements(); + if (elements && elements.subject && elements.body && !injected) { + elements.subject.value = subjectForEmail; + elements.subject.dispatchEvent(new Event('input', { bubbles: true })); + window.emailClientAdapter.injectContent(elements.body, content, elements.eventTypes.contentChange); + injected = true; + clearInterval(interval); + } + }, 200); + setTimeout(() => { + if (!injected) clearInterval(interval); + }, 30000); + } else { + writeScrumBody(); + } } function formatDate(dateString) { @@ -808,7 +975,6 @@ ${userReason}`; logError('Scrum report div not found in popup'); } scrumGenerationInProgress = false; - } else if (outputTarget === 'email') { if (hasInjectedContent) { scrumGenerationInProgress = false; @@ -820,7 +986,6 @@ ${userReason}`; obs.disconnect(); return; } - if (window.emailClientAdapter.isNewConversation()) { const elements = window.emailClientAdapter.getEditorElements(); if (elements && elements.body) { @@ -857,7 +1022,7 @@ ${userReason}`; return; } setTimeout(() => { - let name = githubUserData.name || githubUsername; + let name = githubUserData?.name || githubUserData?.username || platformUsernameLocal || platformUsername; let project = projectName || ''; let curDate = new Date(); let year = curDate.getFullYear().toString(); @@ -899,8 +1064,20 @@ ${userReason}`; let i; for (i = 0; i < items.length; i++) { let item = items[i]; - if (item.user.login == githubUsername || !item.pull_request) continue; + // For GitHub: item.user.login, for GitLab: item.author?.username + let isAuthoredByUser = false; + if (platform === 'github') { + isAuthoredByUser = item.user && item.user.login === platformUsernameLocal; + } else if (platform === 'gitlab') { + isAuthoredByUser = item.author && (item.author.username === platformUsername); + } + + if (isAuthoredByUser || !item.pull_request) continue; let repository_url = item.repository_url; + if (!repository_url) { + logError('repository_url is undefined for item:', item); + continue; + } let project = repository_url.substr(repository_url.lastIndexOf('/') + 1); let title = item.title; let number = item.number; @@ -919,8 +1096,7 @@ ${userReason}`; } for (let repo in githubPrsReviewDataProcessed) { let repoLi = - '
  • \ - (' + + '
  • (' + repo + ') - Reviewed '; if (githubPrsReviewDataProcessed[repo].length > 1) repoLi += 'PRs - '; @@ -961,19 +1137,17 @@ ${userReason}`; repoLi += '
  • '; reviewedPrsArray.push(repoLi); } - prsReviewDataProcessed = true; } function triggerScrumGeneration() { if (issuesDataProcessed && prsReviewDataProcessed) { - log('Both data sets processed, generating scrum body.'); + + writeScrumBody(); } else { - log('Waiting for all data to be processed before generating scrum.', { - issues: issuesDataProcessed, - reviews: prsReviewDataProcessed, - }); + + } } @@ -1003,12 +1177,14 @@ ${userReason}`; } } - async function writeGithubIssuesPrs() { - let items = githubIssuesData.items; - lastWeekArray = []; - nextWeekArray = []; + async function writeGithubIssuesPrs(items) { + if (!items) { - logError('No Github issues data available'); + + return; + } + if (!items.length) { + return; } const headers = { 'Accept': 'application/vnd.github.v3+json' }; @@ -1027,6 +1203,10 @@ ${userReason}`; let item = items[i]; if (item.pull_request && item.state === 'closed' && useMergedStatus && !fallbackToSimple) { let repository_url = item.repository_url; + if (!repository_url) { + logError('repository_url is undefined for item:', item); + continue; + } let repoParts = repository_url.split('/'); let owner = repoParts[repoParts.length - 2]; let repo = repoParts[repoParts.length - 1]; @@ -1057,35 +1237,56 @@ ${userReason}`; for (let i = 0; i < items.length; i++) { let item = items[i]; + log('[SCRUM-DEBUG] Processing item:', item); + // For GitLab, treat all items in the MRs array as MRs + let isMR = !!item.pull_request; // works for both GitHub and mapped GitLab data + log('[SCRUM-DEBUG] isMR:', isMR, 'platform:', platform, 'item:', item); let html_url = item.html_url; let repository_url = item.repository_url; - let project = repository_url.substr(repository_url.lastIndexOf('/') + 1); + // Use project name for GitLab, repo extraction for GitHub + let project = (platform === 'gitlab' && item.project) ? item.project : (repository_url ? repository_url.substr(repository_url.lastIndexOf('/') + 1) : ''); let title = item.title; let number = item.number; let li = ''; let isDraft = false; - if (item.pull_request && typeof item.draft !== 'undefined') { + if (isMR && typeof item.draft !== 'undefined') { isDraft = item.draft; } - if (item.pull_request) { + + if (isMR) { + // Platform-specific label + let prAction = ''; + const prCreatedDate = new Date(item.created_at); const startDate = new Date(startingDate); const endDate = new Date(endingDate + 'T23:59:59'); const isNewPR = prCreatedDate >= startDate && prCreatedDate <= endDate; - if (!isNewPR) { - const hasCommitsInRange = showCommits && item._allCommits && item._allCommits.length > 0; + if (platform === 'github') { + if (!isNewPR) { + // Only show existing PRs if they are open and have commits in the date range + if (item.state !== 'open') { + continue; // Skip closed/merged existing PRs + } + const hasCommitsInRange = showCommits && item._allCommits && item._allCommits.length > 0; + if (!hasCommitsInRange) { + continue; // Skip existing PRs without commits in date range + } + } + prAction = isNewPR ? 'Made PR' : 'Existing PR'; + } else if (platform === 'gitlab') { + prAction = isNewPR ? 'Made Merge Request' : 'Existing Merge Request'; + } + + - if (!hasCommitsInRange) { - continue; //skip these prs - created outside daterange with no commits - } else { } - } else { } - const prAction = isNewPR ? 'Made PR' : 'Existing PR'; if (isDraft) { - li = `
  • (${project}) - Made PR (#${number}) - ${title}${showOpenLabel ? ' ' + pr_draft_button : ''}
  • `; - } else if (item.state === 'open') { - li = `
  • (${project}) - ${prAction} (#${number}) - ${title}${showOpenLabel ? ' ' + pr_open_button : ''}`; + + li = `
  • (${project}) - Made PR (#${number}) - ${title} ${pr_draft_button}
  • `; + } else if (item.state === 'open' || item.state === 'opened') { + li = `
  • (${project}) - ${prAction} (#${number}) - ${title} ${pr_open_button}`; + if (showCommits && item._allCommits && item._allCommits.length && !isNewPR) { log(`[PR DEBUG] Rendering commits for existing PR #${number}:`, item._allCommits); item._allCommits.forEach(commit => { @@ -1093,7 +1294,11 @@ ${userReason}`; }); } li += `
  • `; - } else if (item.state === 'closed') { + } else if (platform === 'gitlab' && item.state === 'closed') { + // For GitLab, always show closed label for closed MRs + li = `
  • (${project}) - ${prAction} (#${number}) - ${title} ${pr_closed_button}
  • `; + } else { + // GitHub: check merged status if possible let merged = null; if ((githubToken || (useMergedStatus && !fallbackToSimple)) && mergedStatusResults) { let repoParts = repository_url.split('/'); @@ -1102,11 +1307,14 @@ ${userReason}`; merged = mergedStatusResults[`${owner}/${repo}#${number}`]; } if (merged === true) { - li = `
  • (${project}) - Made PR (#${number}) - ${title}${showOpenLabel ? ' ' + pr_merged_button : ''}
  • `; + + li = `
  • (${project}) - ${prAction} (#${number}) - ${title}${showOpenLabel ? ' ' + pr_merged_button : ''}
  • `; } else { - li = `
  • (${project}) - Made PR (#${number}) - ${title}${showOpenLabel ? ' ' + pr_closed_button : ''}
  • `; + // Always show closed label for merged === false or merged === null/undefined + li = `
  • (${project}) - ${prAction} (#${number}) - ${title}${showOpenLabel ? ' ' + pr_closed_button : ''}
  • `; } } + log('[SCRUM-DEBUG] Added PR/MR to lastWeekArray:', li, item); lastWeekArray.push(li); continue; // Prevent issue logic from overwriting PR li } else { @@ -1125,112 +1333,118 @@ ${userReason}`; '  '; nextWeekArray.push(li2); } + if (item.state === 'open') { li = `
  • (${project}) - Opened Issue(#${number}) - ${title}${showOpenLabel ? ' ' + issue_opened_button : ''}
  • `; + } else if (item.state === 'closed') { // Always show closed label for closed issues li = `
  • (${project}) - Opened Issue(#${number}) - ${title}${showOpenLabel ? ' ' + issue_closed_button : ''}
  • `; } else { - li = - '
  • (' + - project + - ') - Opened Issue(#' + - number + - ") - " + - title + - '
  • '; + // Fallback for unexpected state + li = `
  • (${project}) - Opened Issue(#${number}) - ${title}
  • `; } + + log('[SCRUM-DEBUG] Added issue to lastWeekArray:', li, item); lastWeekArray.push(li); } - issuesDataProcessed = true; } + log('[SCRUM-DEBUG] Final lastWeekArray:', lastWeekArray); + issuesDataProcessed = true; - let intervalBody = setInterval(() => { - if (!window.emailClientAdapter) return; + } - const elements = window.emailClientAdapter.getEditorElements(); - if (!elements || !elements.body) return; - clearInterval(intervalBody); - scrumBody = elements.body; - }, 500); + let intervalBody = setInterval(() => { + if (!window.emailClientAdapter) return; - let intervalSubject = setInterval(() => { - if (!githubUserData || !window.emailClientAdapter) return; + const elements = window.emailClientAdapter.getEditorElements(); + if (!elements || !elements.body) return; - const elements = window.emailClientAdapter.getEditorElements(); - if (!elements || !elements.subject) return; + clearInterval(intervalBody); + scrumBody = elements.body; + }, 500); - if (outputTarget === 'email' && !window.emailClientAdapter.isNewConversation()) { - console.log('Not a new conversation, skipping subject interval'); - clearInterval(intervalSubject); - return; - } + let intervalSubject = setInterval(() => { + const userData = platform === 'gitlab' ? (githubUserData || platformUsername) : githubUserData; + if (!userData || !window.emailClientAdapter) return; + + + const elements = window.emailClientAdapter.getEditorElements(); + if (!elements || !elements.subject) return; + + if (outputTarget === 'email' && !window.emailClientAdapter.isNewConversation()) { + console.log('Not a new conversation, skipping subject interval'); clearInterval(intervalSubject); - scrumSubject = elements.subject; + return; + } - setTimeout(() => { - scrumSubjectLoaded(); - }, 500); + clearInterval(intervalSubject); + scrumSubject = elements.subject; + + setTimeout(() => { + scrumSubjectLoaded(); }, 500); + }, 500); - //check for github safe writing - // let intervalWriteGithubIssues = setInterval(async () => { - // try { - // if (outputTarget === 'popup') { - // return; - // } else { - // if (scrumBody && githubUsername && githubIssuesData && githubPrsReviewData) { - // clearInterval(intervalWriteGithubIssues); - // clearInterval(intervalWriteGithubPrs); - // writeGithubIssuesPrs(); - // } - // } - // } catch (err) { - // logError('Interval writeGithubIssuesPrs error:', err); - // } - // }, 500); - // let intervalWriteGithubPrs = setInterval(() => { - // if (outputTarget === 'popup') { - // return; - // } else { - // if (scrumBody && githubUsername && githubPrsReviewData && githubIssuesData) { - // clearInterval(intervalWriteGithubPrs); - // clearInterval(intervalWriteGithubIssues); - // writeGithubPrsReviews(); - // } - // } - // }, 500); - if (!refreshButton_Placed) { - let intervalWriteButton = setInterval(() => { - if (document.getElementsByClassName('F0XO1GC-x-b').length == 3 && scrumBody && enableToggle) { - refreshButton_Placed = true; - clearInterval(intervalWriteButton); - let td = document.createElement('td'); - let button = document.createElement('button'); - button.style = 'background-image:none;background-color:#3F51B5;'; - button.setAttribute('class', 'F0XO1GC-n-a F0XO1GC-G-a'); - button.title = 'Rewrite your SCRUM using updated settings!'; - button.id = 'refreshButton'; - let elemText = document.createTextNode('↻ Rewrite SCRUM!'); - button.appendChild(elemText); - td.appendChild(button); - document.getElementsByClassName('F0XO1GC-x-b')[0].children[0].children[0].appendChild(td); - document.getElementById('refreshButton').addEventListener('click', handleRefresh); - } - }, 1000); + + // check for github safe writing + let intervalWriteGithubIssues = setInterval(() => { + if (outputTarget === 'popup') { + return; + } else { + const username = platform === 'gitlab' ? platformUsername : platformUsernameLocal; + if (scrumBody && username && githubIssuesData && githubPrsReviewData) { + clearInterval(intervalWriteGithubIssues); + clearInterval(intervalWriteGithubPrs); + writeGithubIssuesPrs(); + } } - function handleRefresh() { - hasInjectedContent = false; // Reset the flag before refresh - allIncluded(); + }, 500); + let intervalWriteGithubPrs = setInterval(() => { + if (outputTarget === 'popup') { + return; + } else { + const username = platform === 'gitlab' ? platformUsername : platformUsernameLocal; + if (scrumBody && username && githubPrsReviewData && githubIssuesData) { + clearInterval(intervalWriteGithubPrs); + clearInterval(intervalWriteGithubIssues); + writeGithubPrsReviews(); + } } + }, 500); + + if (!refreshButton_Placed) { + let intervalWriteButton = setInterval(() => { + if (document.getElementsByClassName('F0XO1GC-x-b').length == 3 && scrumBody && enableToggle) { + refreshButton_Placed = true; + clearInterval(intervalWriteButton); + let td = document.createElement('td'); + let button = document.createElement('button'); + button.style = 'background-image:none;background-color:#3F51B5;'; + button.setAttribute('class', 'F0XO1GC-n-a F0XO1GC-G-a'); + button.title = 'Rewrite your SCRUM using updated settings!'; + button.id = 'refreshButton'; + let elemText = document.createTextNode('↻ Rewrite SCRUM!'); + button.appendChild(elemText); + td.appendChild(button); + document.getElementsByClassName('F0XO1GC-x-b')[0].children[0].children[0].appendChild(td); + document.getElementById('refreshButton').addEventListener('click', handleRefresh); + } + }, 1000); + } + + function handleRefresh() { + hasInjectedContent = false; // Reset the flag before refresh + allIncluded(); } } + + + async function forceGithubDataRefresh() { let showCommits = false; @@ -1263,6 +1477,27 @@ async function forceGithubDataRefresh() { return { success: true }; } +async function forceGitlabDataRefresh() { + // Clear in-memory cache if gitlabHelper is loaded + if (window.GitLabHelper && gitlabHelper instanceof window.GitLabHelper) { + gitlabHelper.cache.data = null; + gitlabHelper.cache.cacheKey = null; + gitlabHelper.cache.timestamp = 0; + gitlabHelper.cache.fetching = false; + gitlabHelper.cache.queue = []; + } + await new Promise(resolve => { + chrome.storage.local.remove('gitlabCache', resolve); + }); + hasInjectedContent = false; + // Re-instantiate gitlabHelper to ensure a fresh instance for next API call + if (window.GitLabHelper) { + gitlabHelper = new window.GitLabHelper(); + } + return { success: true }; +} + + if (window.location.protocol.startsWith('http')) { allIncluded('email'); $('button>span:contains(New conversation)').parent('button').click(() => { @@ -1272,15 +1507,26 @@ if (window.location.protocol.startsWith('http')) { window.generateScrumReport = function () { allIncluded('popup'); -} +}; chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === 'forceRefresh') { - forceGithubDataRefresh() - .then(result => sendResponse(result)).catch(err => { - console.error('Force refresh failed:', err); - sendResponse({ success: false, error: err.message }); - }); + chrome.storage.local.get(['platform'], async (result) => { + const platform = result.platform || 'github'; + if (platform === 'gitlab') { + forceGitlabDataRefresh() + .then(result => sendResponse(result)).catch(err => { + console.error('Force refresh failed:', err); + sendResponse({ success: false, error: err.message }); + }); + } else { + forceGithubDataRefresh() + .then(result => sendResponse(result)).catch(err => { + console.error('Force refresh failed:', err); + sendResponse({ success: false, error: err.message }); + }); + } + }); return true; } }); @@ -1313,6 +1559,7 @@ ${prs.map((pr, i) => ` repo${i}: repository(owner: \"${pr.owner}\", name: \"${pr } catch (e) { return results; } + } let selectedRepos = []; @@ -1473,15 +1720,15 @@ async function fetchUserRepositories(username, token, org = '') { stars: repo.stargazerCount })); - console.log(`Successfully fetched details for ${repos.length} repositories via GraphQL`); return repos.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)); } catch (err) { - logError('Failed to fetch repository details via GraphQL:', err); + throw err; } } catch (err) { - logError('Failed to fetch repositories:', err); + + throw err; } } @@ -1511,3 +1758,4 @@ function filterDataByRepos(data, selectedRepos) { return filteredData; } window.fetchUserRepositories = fetchUserRepositories; + diff --git a/src/tailwindcss.css b/src/tailwindcss.css index 3bebcb8..075fcc7 100644 --- a/src/tailwindcss.css +++ b/src/tailwindcss.css @@ -860,4 +860,4 @@ li { --tw-translate-z: 0; } } -} +} \ No newline at end of file