From f65e9cd22156bc480f206dcbbd556200aff7b943 Mon Sep 17 00:00:00 2001 From: correasebastian Date: Wed, 9 Apr 2025 02:06:42 +0000 Subject: [PATCH 1/4] Initial gh-pages commit From 0f8f384fd27cbdfd3413f9b69987481c5b4ab0ef Mon Sep 17 00:00:00 2001 From: correasebastian Date: Wed, 9 Apr 2025 02:06:42 +0000 Subject: [PATCH 2/4] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20corr?= =?UTF-8?q?easebastian/layers@3b75f855c3b7ea6d3482310761869c0976470389=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analyzed_branch_data.json | 86 ++++ index.html | 121 +++++ script.js | 1006 +++++++++++++++++++++++++++++++++++++ styles.css | 156 ++++++ timeline.js | 361 +++++++++++++ 5 files changed, 1730 insertions(+) create mode 100644 analyzed_branch_data.json create mode 100644 index.html create mode 100644 script.js create mode 100644 styles.css create mode 100644 timeline.js diff --git a/analyzed_branch_data.json b/analyzed_branch_data.json new file mode 100644 index 0000000..ef13403 --- /dev/null +++ b/analyzed_branch_data.json @@ -0,0 +1,86 @@ +{ + "main": [ + "feature/sites-40" + ], + "release": [ + "feature/add-actions", + "feature/add-actions", + "feature/add-actions", + "feature/add-actions", + "feature/sites-42", + "feature/sites-41" + ], + "common": [], + "mainOnly": [ + "feature/sites-40" + ], + "releaseOnly": [ + "feature/sites-41", + "feature/sites-42", + "feature/add-actions" + ], + "mainDetails": [ + { + "name": "feature/sites-40", + "merged_at": "2025-04-08T18:55:00+00:00", + "pr_number": 1, + "pr_title": "hello" + } + ], + "releaseDetails": [ + { + "name": "feature/add-actions", + "merged_at": "2025-04-09T02:06:18+00:00", + "pr_number": 7, + "pr_title": "remove the copy" + }, + { + "name": "feature/add-actions", + "merged_at": "2025-04-09T02:05:01+00:00", + "pr_number": 6, + "pr_title": "remove interval job for now" + }, + { + "name": "feature/add-actions", + "merged_at": "2025-04-09T02:03:31+00:00", + "pr_number": 5, + "pr_title": "handle timestamp" + }, + { + "name": "feature/add-actions", + "merged_at": "2025-04-09T01:57:00+00:00", + "pr_number": 4, + "pr_title": "Feature/add actions" + }, + { + "name": "feature/sites-42", + "merged_at": "2025-04-09T01:10:20+00:00", + "pr_number": 3, + "pr_title": "add sites-42" + }, + { + "name": "feature/sites-41", + "merged_at": "2025-04-08T18:58:45+00:00", + "pr_number": 2, + "pr_title": "using grid areas" + } + ], + "timestamp": "2025-04-09T02:06:41.603290", + "repository": "correasebastian/layers", + "analysisDate": "latest", + "stats": { + "total_branches": 4, + "main_count": 1, + "release_count": 6, + "common_count": 0, + "main_only_count": 1, + "release_only_count": 3, + "repository": "correasebastian/layers", + "timestamp": "2025-04-09T02:06:41.603290", + "analysis_time": "2025-04-09T02:06:41.654082", + "analysis_date": "latest", + "common_percentage": 0.0, + "main_only_percentage": 25.0, + "release_only_percentage": 75.0 + } +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..3da99f7 --- /dev/null +++ b/index.html @@ -0,0 +1,121 @@ + + + + + + Git Branch Comparison + + + + +
+

Git Branch Comparison

+

Visualizing differences between main and release branches

+
+ +
+
+

Analysis Date

+
+ + +
+
+

Current analysis: Latest

+

Select a date to see branch status as of that date

+
+
+ +
+

Branch Overview

+
+
+

Main Branch

+

Total merged branches: 0

+
+
+

Release Branch

+

Total merged branches: 0

+
+
+

Common Branches

+

Branches in both: 0

+
+
+
+ +
+

Branch Visualization

+
+
+ +
+

Branch Details

+ +
+
+

Main Branch

+
    + +
+
+ +
+

Release Branch

+
    + +
+
+
+
+ +
+

Unique Branches

+ +
+
+

Only in Main

+
    + +
+
+ +
+

Only in Release

+
    + +
+
+
+
+ +
+

Common Branches

+
    + +
+
+ +
+

Historical Analysis

+
+ +
+ +
+
+
+ +
+
+
+ +
+

Created with ❤️ by Manus AI

+

Last updated: Loading...

+
+ + + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..3d83a1f --- /dev/null +++ b/script.js @@ -0,0 +1,1006 @@ +// Global variables for date handling +let currentAnalysisDate = 'latest'; +let availableDates = []; + +// Load data from JSON file with date parameter +async function loadBranchData(date = null) { + try { + // Construct URL with date parameter if provided + let url = 'analyzed_branch_data.json'; + if (date && date !== 'latest') { + // Format date for filename (remove dashes) + const dateStr = date.replace(/-/g, ''); + url = `analyzed_branch_data_${dateStr}.json`; + } + + console.log(`Loading data from: ${url}`); + + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + // Update current date display + updateCurrentDateDisplay(data.stats?.analysis_date || 'latest'); + + return data; + } catch (error) { + console.error('Error loading branch data:', error); + // Fall back to sample data if loading fails + return { + main: ["feature/profile-789", "feature/payment-456", "feature/auth-123"], + release: ["feature/notification-202", "feature/search-101", "feature/auth-123"], + common: ["feature/auth-123"], + mainOnly: ["feature/profile-789", "feature/payment-456"], + releaseOnly: ["feature/notification-202", "feature/search-101"], + stats: { + total_branches: 5, + main_count: 3, + release_count: 3, + common_count: 1, + main_only_count: 2, + release_only_count: 2, + repository: "example/private-repo", + common_percentage: 20.0, + main_only_percentage: 40.0, + release_only_percentage: 40.0, + analysis_date: 'latest' + } + }; + } +} + +// Update the current date display +function updateCurrentDateDisplay(date) { + const currentDateElement = document.getElementById('current-date'); + currentAnalysisDate = date; + + if (date === 'latest') { + currentDateElement.textContent = 'Latest'; + } else { + // Format date for display (YYYY-MM-DD) + const displayDate = new Date(date); + currentDateElement.textContent = displayDate.toLocaleDateString(); + } +} + +// Set up date picker functionality +function setupDatePicker() { + const datePicker = document.getElementById('analysis-date'); + const updateButton = document.getElementById('update-date-button'); + + // Set default date to today + const today = new Date(); + datePicker.valueAsDate = today; + + // Load data for the selected date when button is clicked + updateButton.addEventListener('click', async function() { + // Show loading indicator + const loadingIndicator = document.createElement('div'); + loadingIndicator.className = 'loading'; + updateButton.appendChild(loadingIndicator); + updateButton.disabled = true; + + try { + const selectedDate = datePicker.value; + const branchData = await loadBranchData(selectedDate); + + // Update visualization and lists + createVennDiagram(branchData); + updateBranchLists(branchData); + + // Add this date to available dates if not already there + if (!availableDates.includes(selectedDate)) { + availableDates.push(selectedDate); + updateHistoryDates(); + } + } catch (error) { + console.error('Error updating data:', error); + alert('Failed to load data for the selected date. Please try another date.'); + } finally { + // Remove loading indicator + updateButton.removeChild(loadingIndicator); + updateButton.disabled = false; + } + }); +} + +// Create Venn Diagram visualization +async function createVennDiagram(branchData = null) { + // Load data if not provided + if (!branchData) { + branchData = await loadBranchData(); + } + + // Update counts in the UI + document.getElementById('main-count').textContent = branchData.stats.main_count; + document.getElementById('release-count').textContent = branchData.stats.release_count; + document.getElementById('common-count').textContent = branchData.stats.common_count; + + // Update repository name if available + if (branchData.stats && branchData.stats.repository) { + const repoName = branchData.stats.repository; + document.querySelector('header p').textContent = `Visualizing differences between main and release branches in ${repoName}`; + } + + // Update last updated timestamp + if (branchData.stats && branchData.stats.timestamp) { + const timestamp = new Date(branchData.stats.timestamp).toLocaleString(); + document.getElementById('last-updated').textContent = timestamp; + } + + const container = document.getElementById('venn-diagram'); + const width = container.clientWidth; + const height = 450; + + // Clear any existing SVG + container.innerHTML = ''; + + // Create SVG + const svg = d3.select("#venn-diagram") + .append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", `translate(${width/2}, ${height/2})`); + + // Circle properties + const radius = Math.min(width, height) / 4; + const offset = radius * 0.7; + + // Create gradient for main circle + const mainGradient = svg.append("defs") + .append("linearGradient") + .attr("id", "mainGradient") + .attr("x1", "0%") + .attr("y1", "0%") + .attr("x2", "100%") + .attr("y2", "100%"); + + mainGradient.append("stop") + .attr("offset", "0%") + .attr("stop-color", "#4299e1") + .attr("stop-opacity", 0.7); + + mainGradient.append("stop") + .attr("offset", "100%") + .attr("stop-color", "#3182ce") + .attr("stop-opacity", 0.7); + + // Create gradient for release circle + const releaseGradient = svg.append("defs") + .append("linearGradient") + .attr("id", "releaseGradient") + .attr("x1", "0%") + .attr("y1", "0%") + .attr("x2", "100%") + .attr("y2", "100%"); + + releaseGradient.append("stop") + .attr("offset", "0%") + .attr("stop-color", "#ed8936") + .attr("stop-opacity", 0.7); + + releaseGradient.append("stop") + .attr("offset", "100%") + .attr("stop-color", "#dd6b20") + .attr("stop-opacity", 0.7); + + // Draw main branch circle + const mainCircle = svg.append("circle") + .attr("cx", -offset) + .attr("cy", 0) + .attr("r", radius) + .style("fill", "url(#mainGradient)") + .style("stroke", "#2b6cb0") + .style("stroke-width", 2) + .attr("class", "main-circle"); + + // Draw release branch circle + const releaseCircle = svg.append("circle") + .attr("cx", offset) + .attr("cy", 0) + .attr("r", radius) + .style("fill", "url(#releaseGradient)") + .style("stroke", "#c05621") + .style("stroke-width", 2) + .attr("class", "release-circle"); + + // Add labels with background + // Main label + svg.append("rect") + .attr("x", -offset - 40) + .attr("y", -radius - 30) + .attr("width", 80) + .attr("height", 24) + .attr("rx", 12) + .attr("ry", 12) + .style("fill", "#2b6cb0"); + + svg.append("text") + .attr("x", -offset) + .attr("y", -radius - 14) + .attr("text-anchor", "middle") + .style("font-size", "14px") + .style("font-weight", "bold") + .style("fill", "white") + .text("Main"); + + // Release label + svg.append("rect") + .attr("x", offset - 40) + .attr("y", -radius - 30) + .attr("width", 80) + .attr("height", 24) + .attr("rx", 12) + .attr("ry", 12) + .style("fill", "#c05621"); + + svg.append("text") + .attr("x", offset) + .attr("y", -radius - 14) + .attr("text-anchor", "middle") + .style("font-size", "14px") + .style("font-weight", "bold") + .style("fill", "white") + .text("Release"); + + // Add branch counts with circles + // Main only count + svg.append("circle") + .attr("cx", -offset) + .attr("cy", -20) + .attr("r", 25) + .style("fill", "white") + .style("fill-opacity", 0.8) + .style("stroke", "#2b6cb0") + .style("stroke-width", 2); + + svg.append("text") + .attr("x", -offset) + .attr("y", -15) + .attr("text-anchor", "middle") + .style("font-size", "18px") + .style("font-weight", "bold") + .style("fill", "#2b6cb0") + .text(branchData.mainOnly.length); + + // Release only count + svg.append("circle") + .attr("cx", offset) + .attr("cy", -20) + .attr("r", 25) + .style("fill", "white") + .style("fill-opacity", 0.8) + .style("stroke", "#c05621") + .style("stroke-width", 2); + + svg.append("text") + .attr("x", offset) + .attr("y", -15) + .attr("text-anchor", "middle") + .style("font-size", "18px") + .style("font-weight", "bold") + .style("fill", "#c05621") + .text(branchData.releaseOnly.length); + + // Common count + svg.append("circle") + .attr("cx", 0) + .attr("cy", 0) + .attr("r", 25) + .style("fill", "white") + .style("fill-opacity", 0.8) + .style("stroke", "#38b2ac") + .style("stroke-width", 2); + + svg.append("text") + .attr("x", 0) + .attr("y", 5) + .attr("text-anchor", "middle") + .style("font-size", "18px") + .style("font-weight", "bold") + .style("fill", "#38b2ac") + .text(branchData.common.length); + + // Add intersection label + svg.append("rect") + .attr("x", -60) + .attr("y", radius + 10) + .attr("width", 120) + .attr("height", 24) + .attr("rx", 12) + .attr("ry", 12) + .style("fill", "#38b2ac"); + + svg.append("text") + .attr("x", 0) + .attr("y", radius + 26) + .attr("text-anchor", "middle") + .style("font-size", "14px") + .style("font-weight", "bold") + .style("fill", "white") + .text("Common Branches"); + + // Add branch names to the diagram + // Main only branches + let mainOnlyY = 40; + branchData.mainOnly.forEach((branch, i) => { + svg.append("text") + .attr("x", -offset) + .attr("y", mainOnlyY + i * 20) + .attr("text-anchor", "middle") + .style("font-size", "12px") + .style("fill", "#2d3748") + .text(branch.split('/')[1]); + }); + + // Release only branches + let releaseOnlyY = 40; + branchData.releaseOnly.forEach((branch, i) => { + svg.append("text") + .attr("x", offset) + .attr("y", releaseOnlyY + i * 20) + .attr("text-anchor", "middle") + .style("font-size", "12px") + .style("fill", "#2d3748") + .text(branch.split('/')[1]); + }); + + // Common branches + let commonY = -50; + branchData.common.forEach((branch, i) => { + svg.append("text") + .attr("x", 0) + .attr("y", commonY + i * 20) + .attr("text-anchor", "middle") + .style("font-size", "12px") + .style("fill", "#2d3748") + .text(branch.split('/')[1]); + }); + + // Add hover effects + mainCircle.on("mouseover", function() { + d3.select(this) + .transition() + .duration(300) + .style("fill-opacity", 0.9) + .attr("r", radius * 1.05); + + // Highlight main branch items + document.querySelectorAll('#main-branches li, #main-only li').forEach(item => { + item.classList.add('highlight'); + }); + }).on("mouseout", function() { + d3.select(this) + .transition() + .duration(300) + .style("fill-opacity", 0.7) + .attr("r", radius); + + // Remove highlight from main branch items + document.querySelectorAll('#main-branches li, #main-only li').forEach(item => { + item.classList.remove('highlight'); + }); + }); + + releaseCircle.on("mouseover", function() { + d3.select(this) + .transition() + .duration(300) + .style("fill-opacity", 0.9) + .attr("r", radius * 1.05); + + // Highlight release branch items + document.querySelectorAll('#release-branches li, #release-only li').forEach(item => { + item.classList.add('highlight'); + }); + }).on("mouseout", function() { + d3.select(this) + .transition() + .duration(300) + .style("fill-opacity", 0.7) + .attr("r", radius); + + // Remove highlight from release branch items + document.querySelectorAll('#release-branches li, #release-only li').forEach(item => { + item.classList.remove('highlight'); + }); + }); + + // Add statistics to the visualization + if (branchData.stats) { + const stats = branchData.stats; + + // Add percentage labels + svg.append("text") + .attr("x", -offset) + .attr("y", -radius - 50) + .attr("text-anchor", "middle") + .style("font-size", "12px") + .style("fill", "#4a5568") + .text(`${stats.main_only_percentage}% of total`); + + svg.append("text") + .attr("x", offset) + .attr("y", -radius - 50) + .attr("text-anchor", "middle") + .style("font-size", "12px") + .style("fill", "#4a5568") + .text(`${stats.release_only_percentage}% of total`); + + svg.append("text") + .attr("x", 0) + .attr("y", -60) + .attr("text-anchor", "middle") + .style("font-size", "12px") + .style("fill", "#4a5568") + .text(`${stats.common_percentage}% of total`); + } + + // Add analysis date if available + if (branchData.stats && branchData.stats.analysis_date && branchData.stats.analysis_date !== 'latest') { + const analysisDate = new Date(branchData.stats.analysis_date).toLocaleDateString(); + + svg.append("text") + .attr("x", 0) + .attr("y", -radius - 70) + .attr("text-anchor", "middle") + .style("font-size", "14px") + .style("font-weight", "bold") + .style("fill", "#4a5568") + .text(`Analysis Date: ${analysisDate}`); + } +} + +// Update branch lists in the UI +async function updateBranchLists(branchData = null) { + // Load data if not provided + if (!branchData) { + branchData = await loadBranchData(); + } + + // Update main branches list + const mainBranchesList = document.getElementById('main-branches'); + mainBranchesList.innerHTML = ''; + branchData.main.forEach(branch => { + const li = document.createElement('li'); + li.textContent = branch; + mainBranchesList.appendChild(li); + }); + + // Update release branches list + const releaseBranchesList = document.getElementById('release-branches'); + releaseBranchesList.innerHTML = ''; + branchData.release.forEach(branch => { + const li = document.createElement('li'); + li.textContent = branch; + releaseBranchesList.appendChild(li); + }); + + // Update main only branches list + const mainOnlyList = document.getElementById('main-only'); + mainOnlyList.innerHTML = ''; + branchData.mainOnly.forEach(branch => { + const li = document.createElement('li'); + li.textContent = branch; + mainOnlyList.appendChild(li); + }); + + // Update release only branches list + const releaseOnlyList = document.getElementById('release-only'); + releaseOnlyList.innerHTML = ''; + branchData.releaseOnly.forEach(branch => { + const li = document.createElement('li'); + li.textContent = branch; + releaseOnlyList.appendChild(li); + }); + + // Update common branches list + const commonBranchesList = document.getElementById('common-branches'); + commonBranchesList.innerHTML = ''; + branchData.common.forEach(branch => { + const li = document.createElement('li'); + li.textContent = branch; + commonBranchesList.appendChild(li); + }); + + // Add event listeners to branch items + addInteractiveFeatures(); +} + +// Add repository information section +function addRepositoryInfo(branchData = null) { + // Use provided data or load it + const dataPromise = branchData ? Promise.resolve(branchData) : loadBranchData(); + + dataPromise.then(data => { + if (data.stats && data.stats.repository) { + // Check if repository info section already exists + let repoInfo = document.querySelector('.repository-info'); + + if (!repoInfo) { + repoInfo = document.createElement('section'); + repoInfo.className = 'repository-info'; + + // Insert after the overview section + const overviewSection = document.querySelector('.overview'); + overviewSection.parentNode.insertBefore(repoInfo, overviewSection.nextSibling); + } + + const timestamp = data.stats.timestamp ? new Date(data.stats.timestamp).toLocaleString() : 'Unknown'; + const analysisDate = data.stats.analysis_date && data.stats.analysis_date !== 'latest' + ? new Date(data.stats.analysis_date).toLocaleDateString() + : 'Latest'; + + repoInfo.innerHTML = ` +

Repository Information

+
+
+

Repository

+

${data.stats.repository}

+
+
+

Analysis Date

+

${analysisDate}

+
+
+

Last Updated

+

${timestamp}

+
+
+

Total Branches

+

${data.stats.total_branches}

+
+
+ `; + } + }); +} + +// Add event listeners for interactive features +function addInteractiveFeatures() { + // Add hover effects for branch items + const branchItems = document.querySelectorAll('li'); + branchItems.forEach(item => { + item.addEventListener('mouseenter', function() { + this.classList.add('hover'); + }); + item.addEventListener('mouseleave', function() { + this.classList.remove('hover'); + }); + + // Add click event to show branch details + item.addEventListener('click', function() { + const branchName = this.textContent.trim(); + showBranchDetails(branchName); + }); + }); + + // Function to find matching elements + function findMatchingElements(text) { + const matches = []; + document.querySelectorAll('li').forEach(li => { + if (li.textContent.trim() === text) { + matches.push(li); + } + }); + return matches; + } + + // Add highlighting for all branch items + document.querySelectorAll('li').forEach(item => { + item.addEventListener('mouseenter', function() { + const branchText = this.textContent.trim(); + const matchingElements = findMatchingElements(branchText); + + matchingElements.forEach(el => { + if (el !== this) { + el.classList.add('related'); + } + }); + }); + + item.addEventListener('mouseleave', function() { + const branchText = this.textContent.trim(); + const matchingElements = findMatchingElements(branchText); + + matchingElements.forEach(el => { + if (el !== this) { + el.classList.remove('related'); + } + }); + }); + }); +} + +// Setup filter and search functionality +function setupFilterAndSearch() { + // Create filter and search elements + const overviewSection = document.querySelector('.overview'); + const filterContainer = document.createElement('div'); + filterContainer.className = 'filter-container'; + filterContainer.innerHTML = ` + +
+ + + +
+ `; + + overviewSection.appendChild(filterContainer); + + // Add search functionality + const searchInput = document.getElementById('branch-search'); + const searchButton = document.getElementById('search-button'); + + function performSearch() { + const searchTerm = searchInput.value.toLowerCase(); + const allBranches = document.querySelectorAll('li'); + + allBranches.forEach(branch => { + const branchText = branch.textContent.toLowerCase(); + if (branchText.includes(searchTerm) || searchTerm === '') { + branch.style.display = ''; + } else { + branch.style.display = 'none'; + } + }); + } + + searchButton.addEventListener('click', performSearch); + searchInput.addEventListener('keyup', function(e) { + if (e.key === 'Enter') { + performSearch(); + } + }); + + // Add filter functionality + const filterMain = document.getElementById('filter-main'); + const filterRelease = document.getElementById('filter-release'); + const filterCommon = document.getElementById('filter-common'); + + function applyFilters() { + loadBranchData().then(branchData => { + const showMain = filterMain.checked; + const showRelease = filterRelease.checked; + const showCommon = filterCommon.checked; + + // Main branches + document.querySelectorAll('#main-branches li').forEach(item => { + const isCommon = branchData.common.includes(item.textContent.trim()); + if ((showMain && !isCommon) || (showCommon && isCommon)) { + item.style.display = ''; + } else { + item.style.display = 'none'; + } + }); + + // Release branches + document.querySelectorAll('#release-branches li').forEach(item => { + const isCommon = branchData.common.includes(item.textContent.trim()); + if ((showRelease && !isCommon) || (showCommon && isCommon)) { + item.style.display = ''; + } else { + item.style.display = 'none'; + } + }); + + // Main only branches + document.querySelectorAll('#main-only li').forEach(item => { + item.style.display = showMain ? '' : 'none'; + }); + + // Release only branches + document.querySelectorAll('#release-only li').forEach(item => { + item.style.display = showRelease ? '' : 'none'; + }); + + // Common branches + document.querySelectorAll('#common-branches li').forEach(item => { + item.style.display = showCommon ? '' : 'none'; + }); + }); + } + + filterMain.addEventListener('change', applyFilters); + filterRelease.addEventListener('change', applyFilters); + filterCommon.addEventListener('change', applyFilters); +} + +// Setup history view functionality +function setupHistoryView() { + const showHistoryButton = document.getElementById('show-history-button'); + const historyDatesContainer = document.getElementById('history-dates'); + const historyChartContainer = document.getElementById('history-chart'); + + // Initially hide the history dates + historyDatesContainer.style.display = 'none'; + + // Toggle history dates visibility + showHistoryButton.addEventListener('click', function() { + if (historyDatesContainer.style.display === 'none') { + historyDatesContainer.style.display = 'flex'; + showHistoryButton.textContent = 'Hide History'; + + // Discover available dates + discoverAvailableDates(); + } else { + historyDatesContainer.style.display = 'none'; + showHistoryButton.textContent = 'Show History'; + } + }); +} + +// Discover available date files +async function discoverAvailableDates() { + const historyDatesContainer = document.getElementById('history-dates'); + historyDatesContainer.innerHTML = '
'; + + try { + // In a real implementation, you would fetch a list of available dates from the server + // For this demo, we'll simulate discovering dates by trying to load a few recent dates + const today = new Date(); + const potentialDates = []; + + // Try the last 30 days + for (let i = 0; i < 30; i++) { + const date = new Date(today); + date.setDate(date.getDate() - i); + potentialDates.push(date.toISOString().split('T')[0]); + } + + // Add any dates we already know about + availableDates.forEach(date => { + if (!potentialDates.includes(date)) { + potentialDates.push(date); + } + }); + + // Clear existing dates + availableDates = []; + + // Check each date + const checkPromises = potentialDates.map(async date => { + try { + const dateStr = date.replace(/-/g, ''); + const url = `analyzed_branch_data_${dateStr}.json`; + const response = await fetch(url, { method: 'HEAD' }); + + if (response.ok) { + availableDates.push(date); + } + } catch (error) { + // Ignore errors for files that don't exist + } + }); + + await Promise.all(checkPromises); + + // Always add 'latest' + if (!availableDates.includes('latest')) { + availableDates.push('latest'); + } + + // Sort dates (latest first) + availableDates.sort((a, b) => { + if (a === 'latest') return -1; + if (b === 'latest') return 1; + return new Date(b) - new Date(a); + }); + + // Update the UI + updateHistoryDates(); + } catch (error) { + console.error('Error discovering dates:', error); + historyDatesContainer.innerHTML = 'Failed to load history dates.'; + } +} + +// Update history dates in the UI +function updateHistoryDates() { + const historyDatesContainer = document.getElementById('history-dates'); + historyDatesContainer.innerHTML = ''; + + if (availableDates.length === 0) { + historyDatesContainer.innerHTML = 'No historical data available.'; + return; + } + + availableDates.forEach(date => { + const dateButton = document.createElement('button'); + dateButton.className = 'history-date-button'; + + if (date === currentAnalysisDate) { + dateButton.classList.add('active'); + } + + if (date === 'latest') { + dateButton.textContent = 'Latest'; + } else { + const displayDate = new Date(date); + dateButton.textContent = displayDate.toLocaleDateString(); + } + + dateButton.addEventListener('click', async () => { + try { + // Update active button + document.querySelectorAll('.history-date-button').forEach(btn => { + btn.classList.remove('active'); + }); + dateButton.classList.add('active'); + + // Load data for this date + const branchData = await loadBranchData(date); + + // Update visualization and lists + createVennDiagram(branchData); + updateBranchLists(branchData); + addRepositoryInfo(branchData); + + // Update date picker to match selected date + if (date !== 'latest') { + document.getElementById('analysis-date').value = date; + } + } catch (error) { + console.error('Error loading historical data:', error); + alert('Failed to load data for the selected date.'); + } + }); + + historyDatesContainer.appendChild(dateButton); + }); +} + +// Show branch details in a modal +function showBranchDetails(branchName) { + loadBranchData().then(branchData => { + // Check if modal already exists + let modal = document.getElementById('branch-modal'); + + if (!modal) { + // Create modal + modal = document.createElement('div'); + modal.id = 'branch-modal'; + modal.className = 'modal'; + modal.innerHTML = ` + + `; + document.body.appendChild(modal); + + // Add close button functionality + const closeButton = modal.querySelector('.close-button'); + closeButton.addEventListener('click', function() { + modal.style.display = 'none'; + }); + + // Close modal when clicking outside + window.addEventListener('click', function(event) { + if (event.target === modal) { + modal.style.display = 'none'; + } + }); + } + + // Update modal content + const modalTitle = document.getElementById('modal-title'); + const modalBody = document.getElementById('modal-body'); + + modalTitle.textContent = branchName; + + // Determine branch type and details + let branchType = ''; + let branchDetails = ''; + + if (branchData.common.includes(branchName)) { + branchType = 'Common Branch'; + branchDetails = ` +

This branch appears in both main and release branches.

+
+

Branch Information

+
    +
  • ID: ${branchName.split('-')[1] || 'N/A'}
  • +
  • Type: ${branchName.split('/')[0] || 'N/A'}
  • +
  • Feature: ${branchName.split('/')[1]?.split('-')[0] || 'N/A'}
  • +
+
+
+

Merge Status

+
    +
  • Merged to Main: Yes
  • +
  • Merged to Release: Yes
  • +
+
+ `; + } else if (branchData.mainOnly.includes(branchName)) { + branchType = 'Main Only Branch'; + branchDetails = ` +

This branch appears only in the main branch.

+
+

Branch Information

+
    +
  • ID: ${branchName.split('-')[1] || 'N/A'}
  • +
  • Type: ${branchName.split('/')[0] || 'N/A'}
  • +
  • Feature: ${branchName.split('/')[1]?.split('-')[0] || 'N/A'}
  • +
+
+
+

Merge Status

+
    +
  • Merged to Main: Yes
  • +
  • Merged to Release: No
  • +
+
+ `; + } else if (branchData.releaseOnly.includes(branchName)) { + branchType = 'Release Only Branch'; + branchDetails = ` +

This branch appears only in the release branch.

+
+

Branch Information

+
    +
  • ID: ${branchName.split('-')[1] || 'N/A'}
  • +
  • Type: ${branchName.split('/')[0] || 'N/A'}
  • +
  • Feature: ${branchName.split('/')[1]?.split('-')[0] || 'N/A'}
  • +
+
+
+

Merge Status

+
    +
  • Merged to Main: No
  • +
  • Merged to Release: Yes
  • +
+
+ `; + } + + // Add analysis date information + let dateInfo = ''; + if (currentAnalysisDate && currentAnalysisDate !== 'latest') { + const displayDate = new Date(currentAnalysisDate).toLocaleDateString(); + dateInfo = `

Analysis as of: ${displayDate}

`; + } + + modalBody.innerHTML = ` +
${branchType}
+ ${dateInfo} + ${branchDetails} + `; + + // Show modal + modal.style.display = 'block'; + }); +} + +// Initialize the application +document.addEventListener('DOMContentLoaded', async function() { + // Set up date picker + setupDatePicker(); + + // Load data and update UI + const branchData = await loadBranchData(); + await createVennDiagram(branchData); + await updateBranchLists(branchData); + + // Add repository information + addRepositoryInfo(branchData); + + // Setup interactive features + addInteractiveFeatures(); + setupFilterAndSearch(); + setupHistoryView(); + + // Make the visualization responsive + window.addEventListener('resize', function() { + createVennDiagram(); + }); +}); diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..6db2241 --- /dev/null +++ b/styles.css @@ -0,0 +1,156 @@ +/* Add styles for the date selector section */ +.date-selector { + background-color: #f8f9fa; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.date-control { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 10px; +} + +#analysis-date { + padding: 8px 12px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 16px; +} + +#update-date-button { + padding: 8px 16px; + background-color: #4299e1; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; +} + +#update-date-button:hover { + background-color: #3182ce; +} + +.date-info { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 14px; + color: #4a5568; +} + +#current-date { + font-weight: bold; + color: #2d3748; +} + +.date-help { + font-style: italic; +} + +/* Styles for the history view section */ +.history-view { + background-color: #f8f9fa; + border-radius: 8px; + padding: 20px; + margin-top: 30px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.history-controls { + display: flex; + flex-direction: column; + gap: 15px; + margin-bottom: 20px; +} + +#show-history-button { + padding: 8px 16px; + background-color: #38b2ac; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; + align-self: flex-start; +} + +#show-history-button:hover { + background-color: #319795; +} + +.history-dates { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 10px; +} + +.history-date-button { + padding: 6px 12px; + background-color: #e2e8f0; + border: 1px solid #cbd5e0; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: all 0.2s; +} + +.history-date-button:hover { + background-color: #cbd5e0; +} + +.history-date-button.active { + background-color: #4299e1; + color: white; + border-color: #3182ce; +} + +.history-chart { + height: 300px; + margin-top: 20px; + border: 1px solid #e2e8f0; + border-radius: 4px; + padding: 10px; + background-color: white; +} + +/* Loading indicator */ +.loading { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid rgba(0, 0, 0, 0.1); + border-radius: 50%; + border-top-color: #4299e1; + animation: spin 1s ease-in-out infinite; + margin-left: 10px; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .date-control { + flex-direction: column; + align-items: stretch; + } + + .date-info { + flex-direction: column; + align-items: flex-start; + gap: 5px; + } + + .history-dates { + flex-direction: column; + } +} diff --git a/timeline.js b/timeline.js new file mode 100644 index 0000000..1387830 --- /dev/null +++ b/timeline.js @@ -0,0 +1,361 @@ +// Timeline visualization component for branch history +function createTimelineVisualization() { + const historyChartContainer = document.getElementById('history-chart'); + + // Clear any existing content + historyChartContainer.innerHTML = ''; + + // If no dates available, show message + if (availableDates.length <= 1) { + historyChartContainer.innerHTML = '

Not enough historical data available to create a timeline. Run analysis for multiple dates to see branch evolution over time.

'; + return; + } + + // Create SVG container for timeline + const margin = {top: 40, right: 30, bottom: 50, left: 50}; + const width = historyChartContainer.clientWidth - margin.left - margin.right; + const height = 250 - margin.top - margin.bottom; + + const svg = d3.select("#history-chart") + .append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", `translate(${margin.left},${margin.top})`); + + // Filter out 'latest' and convert dates to Date objects + const dateObjects = availableDates + .filter(d => d !== 'latest') + .map(d => ({date: new Date(d), dateStr: d})) + .sort((a, b) => a.date - b.date); + + // If less than 2 dates, show message + if (dateObjects.length < 2) { + historyChartContainer.innerHTML = '

Not enough historical data available to create a timeline. Run analysis for multiple dates to see branch evolution over time.

'; + return; + } + + // Load data for each date + Promise.all(dateObjects.map(d => loadBranchData(d.dateStr))) + .then(results => { + // Prepare data for visualization + const timelineData = results.map((data, i) => ({ + date: dateObjects[i].date, + dateStr: dateObjects[i].dateStr, + mainCount: data.stats.main_count, + releaseCount: data.stats.release_count, + commonCount: data.stats.common_count + })); + + // Create scales + const xScale = d3.scaleTime() + .domain(d3.extent(timelineData, d => d.date)) + .range([0, width]); + + const yScale = d3.scaleLinear() + .domain([0, d3.max(timelineData, d => Math.max(d.mainCount, d.releaseCount))]) + .nice() + .range([height, 0]); + + // Add X axis + svg.append("g") + .attr("transform", `translate(0,${height})`) + .call(d3.axisBottom(xScale).ticks(5).tickFormat(d3.timeFormat("%b %d"))) + .selectAll("text") + .style("text-anchor", "end") + .attr("dx", "-.8em") + .attr("dy", ".15em") + .attr("transform", "rotate(-45)"); + + // Add Y axis + svg.append("g") + .call(d3.axisLeft(yScale)); + + // Add X axis label + svg.append("text") + .attr("text-anchor", "middle") + .attr("x", width / 2) + .attr("y", height + margin.bottom - 5) + .text("Date"); + + // Add Y axis label + svg.append("text") + .attr("text-anchor", "middle") + .attr("transform", "rotate(-90)") + .attr("y", -margin.left + 15) + .attr("x", -height / 2) + .text("Branch Count"); + + // Add title + svg.append("text") + .attr("x", width / 2) + .attr("y", -margin.top / 2) + .attr("text-anchor", "middle") + .style("font-size", "16px") + .style("font-weight", "bold") + .text("Branch Evolution Over Time"); + + // Create line generators + const mainLine = d3.line() + .x(d => xScale(d.date)) + .y(d => yScale(d.mainCount)) + .curve(d3.curveMonotoneX); + + const releaseLine = d3.line() + .x(d => xScale(d.date)) + .y(d => yScale(d.releaseCount)) + .curve(d3.curveMonotoneX); + + const commonLine = d3.line() + .x(d => xScale(d.date)) + .y(d => yScale(d.commonCount)) + .curve(d3.curveMonotoneX); + + // Add the lines + svg.append("path") + .datum(timelineData) + .attr("fill", "none") + .attr("stroke", "#3182ce") + .attr("stroke-width", 2) + .attr("d", mainLine); + + svg.append("path") + .datum(timelineData) + .attr("fill", "none") + .attr("stroke", "#dd6b20") + .attr("stroke-width", 2) + .attr("d", releaseLine); + + svg.append("path") + .datum(timelineData) + .attr("fill", "none") + .attr("stroke", "#38b2ac") + .attr("stroke-width", 2) + .attr("d", commonLine); + + // Add dots for each data point + svg.selectAll(".main-dot") + .data(timelineData) + .enter() + .append("circle") + .attr("class", "main-dot") + .attr("cx", d => xScale(d.date)) + .attr("cy", d => yScale(d.mainCount)) + .attr("r", 5) + .attr("fill", "#3182ce") + .on("mouseover", function(event, d) { + d3.select(this).attr("r", 8); + showTooltip(event, d, "main"); + }) + .on("mouseout", function() { + d3.select(this).attr("r", 5); + hideTooltip(); + }) + .on("click", function(event, d) { + loadBranchData(d.dateStr).then(data => { + createVennDiagram(data); + updateBranchLists(data); + addRepositoryInfo(data); + updateCurrentDateDisplay(d.dateStr); + + // Update date picker to match selected date + document.getElementById('analysis-date').value = d.dateStr; + + // Update active button in history dates + document.querySelectorAll('.history-date-button').forEach(btn => { + btn.classList.remove('active'); + if (btn.textContent === new Date(d.dateStr).toLocaleDateString()) { + btn.classList.add('active'); + } + }); + }); + }); + + svg.selectAll(".release-dot") + .data(timelineData) + .enter() + .append("circle") + .attr("class", "release-dot") + .attr("cx", d => xScale(d.date)) + .attr("cy", d => yScale(d.releaseCount)) + .attr("r", 5) + .attr("fill", "#dd6b20") + .on("mouseover", function(event, d) { + d3.select(this).attr("r", 8); + showTooltip(event, d, "release"); + }) + .on("mouseout", function() { + d3.select(this).attr("r", 5); + hideTooltip(); + }) + .on("click", function(event, d) { + loadBranchData(d.dateStr).then(data => { + createVennDiagram(data); + updateBranchLists(data); + addRepositoryInfo(data); + updateCurrentDateDisplay(d.dateStr); + + // Update date picker to match selected date + document.getElementById('analysis-date').value = d.dateStr; + + // Update active button in history dates + document.querySelectorAll('.history-date-button').forEach(btn => { + btn.classList.remove('active'); + if (btn.textContent === new Date(d.dateStr).toLocaleDateString()) { + btn.classList.add('active'); + } + }); + }); + }); + + svg.selectAll(".common-dot") + .data(timelineData) + .enter() + .append("circle") + .attr("class", "common-dot") + .attr("cx", d => xScale(d.date)) + .attr("cy", d => yScale(d.commonCount)) + .attr("r", 5) + .attr("fill", "#38b2ac") + .on("mouseover", function(event, d) { + d3.select(this).attr("r", 8); + showTooltip(event, d, "common"); + }) + .on("mouseout", function() { + d3.select(this).attr("r", 5); + hideTooltip(); + }) + .on("click", function(event, d) { + loadBranchData(d.dateStr).then(data => { + createVennDiagram(data); + updateBranchLists(data); + addRepositoryInfo(data); + updateCurrentDateDisplay(d.dateStr); + + // Update date picker to match selected date + document.getElementById('analysis-date').value = d.dateStr; + + // Update active button in history dates + document.querySelectorAll('.history-date-button').forEach(btn => { + btn.classList.remove('active'); + if (btn.textContent === new Date(d.dateStr).toLocaleDateString()) { + btn.classList.add('active'); + } + }); + }); + }); + + // Add legend + const legend = svg.append("g") + .attr("class", "legend") + .attr("transform", `translate(${width - 120}, 0)`); + + // Main branch + legend.append("circle") + .attr("cx", 0) + .attr("cy", 0) + .attr("r", 5) + .attr("fill", "#3182ce"); + + legend.append("text") + .attr("x", 10) + .attr("y", 5) + .text("Main") + .style("font-size", "12px"); + + // Release branch + legend.append("circle") + .attr("cx", 0) + .attr("cy", 20) + .attr("r", 5) + .attr("fill", "#dd6b20"); + + legend.append("text") + .attr("x", 10) + .attr("y", 25) + .text("Release") + .style("font-size", "12px"); + + // Common branches + legend.append("circle") + .attr("cx", 0) + .attr("cy", 40) + .attr("r", 5) + .attr("fill", "#38b2ac"); + + legend.append("text") + .attr("x", 10) + .attr("y", 45) + .text("Common") + .style("font-size", "12px"); + + // Create tooltip + const tooltip = d3.select("body") + .append("div") + .attr("class", "timeline-tooltip") + .style("position", "absolute") + .style("visibility", "hidden") + .style("background-color", "white") + .style("border", "1px solid #ddd") + .style("border-radius", "4px") + .style("padding", "8px") + .style("box-shadow", "0 2px 4px rgba(0,0,0,0.1)") + .style("font-size", "12px") + .style("pointer-events", "none"); + + function showTooltip(event, d, type) { + let content = `Date: ${d.date.toLocaleDateString()}
`; + + if (type === "main") { + content += `Main branches: ${d.mainCount}`; + } else if (type === "release") { + content += `Release branches: ${d.releaseCount}`; + } else if (type === "common") { + content += `Common branches: ${d.commonCount}`; + } + + tooltip + .html(content) + .style("visibility", "visible") + .style("left", (event.pageX + 10) + "px") + .style("top", (event.pageY - 28) + "px"); + } + + function hideTooltip() { + tooltip.style("visibility", "hidden"); + } + }) + .catch(error => { + console.error("Error creating timeline:", error); + historyChartContainer.innerHTML = `

Error creating timeline: ${error.message}

`; + }); +} + +// Update setupHistoryView function to include timeline creation +function setupHistoryView() { + const showHistoryButton = document.getElementById('show-history-button'); + const historyDatesContainer = document.getElementById('history-dates'); + const historyChartContainer = document.getElementById('history-chart'); + + // Initially hide the history dates and chart + historyDatesContainer.style.display = 'none'; + historyChartContainer.style.display = 'none'; + + // Toggle history visibility + showHistoryButton.addEventListener('click', function() { + if (historyDatesContainer.style.display === 'none') { + historyDatesContainer.style.display = 'flex'; + historyChartContainer.style.display = 'block'; + showHistoryButton.textContent = 'Hide History'; + + // Discover available dates and create timeline + discoverAvailableDates().then(() => { + createTimelineVisualization(); + }); + } else { + historyDatesContainer.style.display = 'none'; + historyChartContainer.style.display = 'none'; + showHistoryButton.textContent = 'Show History'; + } + }); +} From c70d241f88721637e4beb395ae5313ef3ad2d602 Mon Sep 17 00:00:00 2001 From: correasebastian Date: Wed, 9 Apr 2025 02:12:22 +0000 Subject: [PATCH 3/4] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20corr?= =?UTF-8?q?easebastian/layers@e4ad99cbee19b0c18020c0e74d77ad08284211a4=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analyzed_branch_data.json | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/analyzed_branch_data.json b/analyzed_branch_data.json index ef13403..f5974b4 100644 --- a/analyzed_branch_data.json +++ b/analyzed_branch_data.json @@ -7,6 +7,7 @@ "feature/add-actions", "feature/add-actions", "feature/add-actions", + "feature/add-actions", "feature/sites-42", "feature/sites-41" ], @@ -15,9 +16,9 @@ "feature/sites-40" ], "releaseOnly": [ - "feature/sites-41", "feature/sites-42", - "feature/add-actions" + "feature/add-actions", + "feature/sites-41" ], "mainDetails": [ { @@ -28,6 +29,12 @@ } ], "releaseDetails": [ + { + "name": "feature/add-actions", + "merged_at": "2025-04-09T02:12:05+00:00", + "pr_number": 8, + "pr_title": "just to retrigger" + }, { "name": "feature/add-actions", "merged_at": "2025-04-09T02:06:18+00:00", @@ -65,19 +72,19 @@ "pr_title": "using grid areas" } ], - "timestamp": "2025-04-09T02:06:41.603290", + "timestamp": "2025-04-09T02:12:22.382455", "repository": "correasebastian/layers", "analysisDate": "latest", "stats": { "total_branches": 4, "main_count": 1, - "release_count": 6, + "release_count": 7, "common_count": 0, "main_only_count": 1, "release_only_count": 3, "repository": "correasebastian/layers", - "timestamp": "2025-04-09T02:06:41.603290", - "analysis_time": "2025-04-09T02:06:41.654082", + "timestamp": "2025-04-09T02:12:22.382455", + "analysis_time": "2025-04-09T02:12:22.432635", "analysis_date": "latest", "common_percentage": 0.0, "main_only_percentage": 25.0, From 15f0167a109f6067681fb13b745ddbb5bbb2cfe2 Mon Sep 17 00:00:00 2001 From: correasebastian Date: Wed, 9 Apr 2025 02:21:25 +0000 Subject: [PATCH 4/4] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20corr?= =?UTF-8?q?easebastian/layers@e883c003d01f6df7fa28d506f39234b359d0b08f=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analyzed_branch_data.json | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/analyzed_branch_data.json b/analyzed_branch_data.json index f5974b4..8a35528 100644 --- a/analyzed_branch_data.json +++ b/analyzed_branch_data.json @@ -8,6 +8,7 @@ "feature/add-actions", "feature/add-actions", "feature/add-actions", + "feature/add-actions", "feature/sites-42", "feature/sites-41" ], @@ -16,9 +17,9 @@ "feature/sites-40" ], "releaseOnly": [ - "feature/sites-42", "feature/add-actions", - "feature/sites-41" + "feature/sites-41", + "feature/sites-42" ], "mainDetails": [ { @@ -29,6 +30,12 @@ } ], "releaseDetails": [ + { + "name": "feature/add-actions", + "merged_at": "2025-04-09T02:21:06+00:00", + "pr_number": 9, + "pr_title": "lastname" + }, { "name": "feature/add-actions", "merged_at": "2025-04-09T02:12:05+00:00", @@ -72,19 +79,19 @@ "pr_title": "using grid areas" } ], - "timestamp": "2025-04-09T02:12:22.382455", + "timestamp": "2025-04-09T02:21:24.655901", "repository": "correasebastian/layers", "analysisDate": "latest", "stats": { "total_branches": 4, "main_count": 1, - "release_count": 7, + "release_count": 8, "common_count": 0, "main_only_count": 1, "release_only_count": 3, "repository": "correasebastian/layers", - "timestamp": "2025-04-09T02:12:22.382455", - "analysis_time": "2025-04-09T02:12:22.432635", + "timestamp": "2025-04-09T02:21:24.655901", + "analysis_time": "2025-04-09T02:21:24.706693", "analysis_date": "latest", "common_percentage": 0.0, "main_only_percentage": 25.0,