diff --git a/.github/workflows/ci-test-go.yml b/.github/workflows/ci-test-go.yml index bb3e6b43cf..fea34e62ee 100644 --- a/.github/workflows/ci-test-go.yml +++ b/.github/workflows/ci-test-go.yml @@ -55,7 +55,7 @@ jobs: steps: - name: Setup rootless Docker if: ${{ inputs.rootless-docker }} - uses: docker/setup-docker-action@b60f85385d03ac8acfca6d9996982511d8620a19 # v4 + uses: docker/setup-docker-action@efe9e3891a4f7307e689f2100b33a155b900a608 # v4.5.0 with: rootless: true diff --git a/.github/workflows/usage-metrics.yml b/.github/workflows/usage-metrics.yml new file mode 100644 index 0000000000..c550468862 --- /dev/null +++ b/.github/workflows/usage-metrics.yml @@ -0,0 +1,85 @@ +name: Update Usage Metrics + +on: + schedule: + # Run monthly on the 1st at 9 AM UTC + - cron: '0 9 1 * *' + workflow_dispatch: + inputs: + versions: + description: 'Comma-separated versions to query (leave empty for all versions)' + required: false + default: '' + +permissions: + contents: write + +jobs: + collect-metrics: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set up Go + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version-file: 'usage-metrics/go.mod' + + - name: Query versions + id: query + working-directory: usage-metrics + run: | + # Get versions to query + VERSIONS="${{ github.event.inputs.versions }}" + if [ -z "$VERSIONS" ]; then + # Get all versions from v0.13.0 to latest from the main repository + VERSIONS=$(git ls-remote --tags https://github.com/testcontainers/testcontainers-go.git | \ + grep -E 'refs/tags/v0\.[0-9]+\.[0-9]+$' | \ + sed 's|.*refs/tags/||' | \ + sort -V | \ + awk '/v0.13.0/,0' | \ + tr '\n' ',' | \ + sed 's/,$//') + fi + + echo "Querying versions: $VERSIONS" + + # Build version flags + VERSION_FLAGS="" + IFS=',' read -ra VERSION_ARRAY <<< "$VERSIONS" + for version in "${VERSION_ARRAY[@]}"; do + version=$(echo "$version" | xargs) # trim whitespace + if [ -z "$version" ]; then + continue + fi + VERSION_FLAGS="$VERSION_FLAGS -version $version" + done + + # Query all versions in a single command + go run collect-metrics.go $VERSION_FLAGS -csv "../../docs/usage-metrics.csv" + + - name: Create Pull Request + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add docs/usage-metrics.csv + + if git diff --staged --quiet; then + echo "No changes to commit" + exit 0 + fi + + # Create a new branch for the PR + DATE=$(date +%Y-%m-%d) + BRANCH_NAME="chore/update-usage-metrics-$DATE" + git checkout -b "$BRANCH_NAME" + + git commit -m "chore(metrics): update usage metrics ($DATE)" + git push -u origin "$BRANCH_NAME" + + # Create PR using gh CLI + gh pr create \ + --title "chore: update usage metrics ($DATE)" \ + --body "Automated update of usage metrics data. This PR updates the usage metrics CSV file with the latest GitHub usage data for testcontainers-go versions." \ + --base main diff --git a/.gitignore b/.gitignore index b5fd75ccce..1693b100b6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ TEST-*.xml # Coverage files coverage.out + +# Usage metrics script binary +usage-metrics/scripts/collect-metrics diff --git a/docs/css/usage-metrics.css b/docs/css/usage-metrics.css new file mode 100644 index 0000000000..a716c29275 --- /dev/null +++ b/docs/css/usage-metrics.css @@ -0,0 +1,111 @@ +/* Usage Metrics Dashboard Styles */ + +/* Expand content to use full available width - only on usage metrics page */ +body:has(#content.usage-metrics) .md-content__inner, +body:has(.stats-grid) .md-content__inner { + max-width: none !important; + margin: 0 !important; +} + +body:has(#content.usage-metrics) .md-content__inner > article, +body:has(.stats-grid) .md-content__inner > article { + padding: 0 2rem; +} + +@media screen and (min-width: 76.25em) { + body:has(#content.usage-metrics) .md-content__inner > article, + body:has(.stats-grid) .md-content__inner > article { + padding: 0 4rem; + } +} + +@media screen and (min-width: 100em) { + body:has(#content.usage-metrics) .md-content__inner > article, + body:has(.stats-grid) .md-content__inner > article { + padding: 0 6rem; + } +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + margin-bottom: 40px; +} + +.stat-card { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 25px; + border-radius: 15px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); +} + +.stat-label { + font-size: 0.9rem; + opacity: 0.9; + margin-bottom: 5px; +} + +.stat-value { + font-size: 2.5rem; + font-weight: bold; +} + +.chart-container { + background: white; + border-radius: 15px; + padding: 30px; + margin-bottom: 30px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.chart-title { + font-size: 1.5rem; + color: #333; + margin-bottom: 20px; + font-weight: 600; +} + +.chart-container canvas { + max-height: 400px; +} + +.loading { + text-align: center; + padding: 40px; + color: #666; + font-size: 1.2rem; +} + +.error { + background: #fee; + border: 1px solid #fcc; + border-radius: 10px; + padding: 20px; + color: #c33; + margin: 20px 0; +} + +.metrics-info { + text-align: center; + margin-top: 40px; + padding-top: 20px; + border-top: 1px solid #eee; + color: #666; + font-size: 0.9rem; +} + +.metrics-info p { + margin: 5px 0; +} + +@media (max-width: 768px) { + .stat-value { + font-size: 2rem; + } + + .chart-container { + padding: 20px; + } +} diff --git a/docs/js/usage-metrics.js b/docs/js/usage-metrics.js new file mode 100644 index 0000000000..544f899609 --- /dev/null +++ b/docs/js/usage-metrics.js @@ -0,0 +1,369 @@ +// Usage Metrics Dashboard JavaScript + +// Store chart instances to prevent canvas reuse errors +let chartInstances = {}; + +// Track if already initialized +let isInitialized = false; + +// Load and parse CSV data +async function loadData() { + try { + const response = await fetch('../usage-metrics.csv'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const csvText = await response.text(); + + const parsed = Papa.parse(csvText, { + header: true, + dynamicTyping: true, + skipEmptyLines: true + }); + + if (parsed.errors.length > 0) { + console.error('CSV parsing errors:', parsed.errors); + } + + return parsed.data; + } catch (error) { + showError(`Failed to load data: ${error.message}`); + throw error; + } +} + +function showError(message) { + const loadingEl = document.getElementById('loading'); + const errorEl = document.getElementById('error'); + if (loadingEl) loadingEl.style.display = 'none'; + if (errorEl) { + errorEl.textContent = message; + errorEl.style.display = 'block'; + } +} + +function processData(data) { + // Sort by date + data.sort((a, b) => new Date(a.date) - new Date(b.date)); + + // Get unique versions + const versions = [...new Set(data.map(d => d.version))].sort(); + + // Group by version + const byVersion = {}; + data.forEach(item => { + if (!byVersion[item.version]) { + byVersion[item.version] = []; + } + byVersion[item.version].push(item); + }); + + return { data, versions, byVersion }; +} + +function createStats(processedData) { + const { data, versions, byVersion } = processedData; + + // Calculate total repositories using latest counts + const latestByVersion = {}; + versions.forEach(version => { + const versionData = byVersion[version]; + if (versionData.length > 0) { + latestByVersion[version] = versionData[versionData.length - 1].count; + } + }); + + const totalRepos = Object.values(latestByVersion).reduce((sum, count) => sum + count, 0); + const latestVersion = versions[versions.length - 1]; + const latestCount = latestByVersion[latestVersion] || 0; + + const statsHtml = ` +
+
Total Repositories
+
${totalRepos}
+
+
+
Versions Tracked
+
${versions.length}
+
+
+
Latest Version
+
${latestVersion}
+
+
+
Latest Usage
+
${latestCount}
+
+ `; + + const statsGrid = document.getElementById('stats-grid'); + if (statsGrid) { + statsGrid.innerHTML = statsHtml; + } +} + +function createTrendChart(processedData) { + const { data, versions, byVersion } = processedData; + + const datasets = versions.map((version, index) => { + const versionData = byVersion[version]; + const colors = [ + '#667eea', '#764ba2', '#f093fb', '#4facfe', + '#43e97b', '#fa709a', '#fee140', '#30cfd0' + ]; + const color = colors[index % colors.length]; + + return { + label: version, + data: versionData.map(d => ({ x: d.date, y: d.count })), + borderColor: color, + backgroundColor: color + '20', + tension: 0.4, + fill: true + }; + }); + + const canvas = document.getElementById('trendChart'); + if (!canvas) return; + + // Destroy existing chart if it exists + if (chartInstances.trendChart) { + chartInstances.trendChart.destroy(); + } + + const ctx = canvas.getContext('2d'); + chartInstances.trendChart = new Chart(ctx, { + type: 'line', + data: { datasets }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + display: true, + position: 'top' + }, + tooltip: { + mode: 'index', + intersect: false + } + }, + scales: { + x: { + type: 'time', + time: { + unit: 'month', + displayFormats: { + month: 'MMM yyyy' + } + }, + title: { + display: true, + text: 'Date' + } + }, + y: { + beginAtZero: true, + title: { + display: true, + text: 'Repository Count' + } + } + } + } + }); +} + +function createVersionChart(processedData) { + const { data } = processedData; + + // Group by date and sum all version counts for each date + const totalsByDate = {}; + data.forEach(item => { + if (!totalsByDate[item.date]) { + totalsByDate[item.date] = 0; + } + totalsByDate[item.date] += item.count; + }); + + // Convert to array and sort by date + const chartData = Object.entries(totalsByDate) + .map(([date, count]) => ({ x: date, y: count })) + .sort((a, b) => new Date(a.x) - new Date(b.x)); + + const canvas = document.getElementById('versionChart'); + if (!canvas) return; + + // Destroy existing chart if it exists + if (chartInstances.versionChart) { + chartInstances.versionChart.destroy(); + } + + const ctx = canvas.getContext('2d'); + chartInstances.versionChart = new Chart(ctx, { + type: 'line', + data: { + datasets: [{ + label: 'Total Repositories', + data: chartData, + borderColor: '#667eea', + backgroundColor: 'rgba(102, 126, 234, 0.1)', + tension: 0.4, + fill: true, + borderWidth: 3, + pointRadius: 5, + pointHoverRadius: 7, + pointBackgroundColor: '#667eea', + pointBorderColor: '#fff', + pointBorderWidth: 2 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + display: false + }, + tooltip: { + callbacks: { + label: function(context) { + return `Total: ${context.parsed.y} repositories`; + } + } + } + }, + scales: { + x: { + type: 'time', + time: { + unit: 'month', + displayFormats: { + month: 'MMM yyyy' + } + }, + title: { + display: true, + text: 'Date' + } + }, + y: { + beginAtZero: true, + title: { + display: true, + text: 'Total Repository Count' + } + } + } + } + }); +} + +function createLatestChart(processedData) { + const { versions, byVersion } = processedData; + + // Get latest count for each version + const latestCounts = versions.map(version => { + const versionData = byVersion[version]; + return versionData[versionData.length - 1].count; + }); + + const colors = versions.map((_, index) => { + const palette = [ + '#667eea', '#764ba2', '#f093fb', '#4facfe', + '#43e97b', '#fa709a', '#fee140', '#30cfd0' + ]; + return palette[index % palette.length]; + }); + + const canvas = document.getElementById('latestChart'); + if (!canvas) return; + + // Destroy existing chart if it exists + if (chartInstances.latestChart) { + chartInstances.latestChart.destroy(); + } + + const ctx = canvas.getContext('2d'); + chartInstances.latestChart = new Chart(ctx, { + type: 'doughnut', + data: { + labels: versions, + datasets: [{ + data: latestCounts, + backgroundColor: colors, + borderColor: '#fff', + borderWidth: 2 + }] + }, + options: { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + display: true, + position: 'right' + }, + tooltip: { + callbacks: { + label: function(context) { + const label = context.label || ''; + const value = context.parsed || 0; + const total = context.dataset.data.reduce((a, b) => a + b, 0); + const percentage = ((value / total) * 100).toFixed(1); + return `${label}: ${value} (${percentage}%)`; + } + } + } + } + } + }); +} + +// Main execution +async function init() { + // Prevent multiple initializations + if (isInitialized) { + return; + } + + // Check if we're on the usage metrics page + const canvas = document.getElementById('trendChart'); + if (!canvas) { + return; + } + + isInitialized = true; + + try { + const data = await loadData(); + const processedData = processData(data); + + const loadingEl = document.getElementById('loading'); + const contentEl = document.getElementById('content'); + + if (loadingEl) loadingEl.style.display = 'none'; + if (contentEl) contentEl.style.display = 'block'; + + createStats(processedData); + createTrendChart(processedData); + createVersionChart(processedData); + createLatestChart(processedData); + + // Set update time + const updateTimeEl = document.getElementById('update-time'); + if (updateTimeEl) { + updateTimeEl.textContent = new Date().toLocaleString(); + } + } catch (error) { + console.error('Initialization failed:', error); + isInitialized = false; // Allow retry on error + } +} + +// Run when page loads +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); +} else { + init(); +} diff --git a/docs/usage-metrics.csv b/docs/usage-metrics.csv new file mode 100644 index 0000000000..f979626853 --- /dev/null +++ b/docs/usage-metrics.csv @@ -0,0 +1,758 @@ +date,version,count +2023-09-01,v0.13.0,109 +2023-09-01,v0.14.0,36 +2023-09-01,v0.15.0,64 +2023-09-01,v0.16.0,10 +2023-09-01,v0.17.0,52 +2023-09-01,v0.18.0,25 +2023-09-01,v0.19.0,37 +2023-09-01,v0.20.0,2 +2023-09-01,v0.20.1,46 +2023-09-01,v0.21.0,76 +2023-09-01,v0.22.0,37 +2023-09-01,v0.23.0,207 +2023-09-01,v0.24.0,2 +2023-09-01,v0.24.1,83 +2023-09-01,v0.25.0,3 +2023-09-01,v0.26.0,0 +2023-09-01,v0.27.0,0 +2023-09-01,v0.28.0,0 +2023-09-01,v0.29.0,0 +2023-09-01,v0.29.1,0 +2023-09-01,v0.30.0,0 +2023-09-01,v0.31.0,0 +2023-09-01,v0.32.0,0 +2023-09-01,v0.33.0,0 +2023-09-01,v0.34.0,0 +2023-09-01,v0.35.0,0 +2023-10-01,v0.13.0,110 +2023-10-01,v0.14.0,36 +2023-10-01,v0.15.0,65 +2023-10-01,v0.16.0,11 +2023-10-01,v0.17.0,71 +2023-10-01,v0.18.0,25 +2023-10-01,v0.19.0,70 +2023-10-01,v0.20.0,1 +2023-10-01,v0.20.1,47 +2023-10-01,v0.21.0,66 +2023-10-01,v0.22.0,32 +2023-10-01,v0.23.0,170 +2023-10-01,v0.24.0,2 +2023-10-01,v0.24.1,17 +2023-10-01,v0.25.0,137 +2023-10-01,v0.26.0,65 +2023-10-01,v0.27.0,0 +2023-10-01,v0.28.0,0 +2023-10-01,v0.29.0,0 +2023-10-01,v0.29.1,0 +2023-10-01,v0.30.0,0 +2023-10-01,v0.31.0,0 +2023-10-01,v0.32.0,0 +2023-10-01,v0.33.0,0 +2023-10-01,v0.34.0,0 +2023-10-01,v0.35.0,0 +2023-11-01,v0.13.0,121 +2023-11-01,v0.14.0,34 +2023-11-01,v0.15.0,65 +2023-11-01,v0.16.0,11 +2023-11-01,v0.17.0,71 +2023-11-01,v0.18.0,24 +2023-11-01,v0.19.0,69 +2023-11-01,v0.20.0,2 +2023-11-01,v0.20.1,47 +2023-11-01,v0.21.0,54 +2023-11-01,v0.22.0,31 +2023-11-01,v0.23.0,145 +2023-11-01,v0.24.0,2 +2023-11-01,v0.24.1,11 +2023-11-01,v0.25.0,48 +2023-11-01,v0.26.0,231 +2023-11-01,v0.27.0,0 +2023-11-01,v0.28.0,0 +2023-11-01,v0.29.0,0 +2023-11-01,v0.29.1,0 +2023-11-01,v0.30.0,0 +2023-11-01,v0.31.0,0 +2023-11-01,v0.32.0,0 +2023-11-01,v0.33.0,0 +2023-11-01,v0.34.0,0 +2023-11-01,v0.35.0,0 +2023-12-01,v0.13.0,120 +2023-12-01,v0.14.0,34 +2023-12-01,v0.15.0,65 +2023-12-01,v0.16.0,11 +2023-12-01,v0.17.0,71 +2023-12-01,v0.18.0,24 +2023-12-01,v0.19.0,68 +2023-12-01,v0.20.0,2 +2023-12-01,v0.20.1,47 +2023-12-01,v0.21.0,48 +2023-12-01,v0.22.0,28 +2023-12-01,v0.23.0,141 +2023-12-01,v0.24.0,2 +2023-12-01,v0.24.1,9 +2023-12-01,v0.25.0,47 +2023-12-01,v0.26.0,284 +2023-12-01,v0.27.0,70 +2023-12-01,v0.28.0,0 +2023-12-01,v0.29.0,0 +2023-12-01,v0.29.1,0 +2023-12-01,v0.30.0,0 +2023-12-01,v0.31.0,0 +2023-12-01,v0.32.0,0 +2023-12-01,v0.33.0,0 +2023-12-01,v0.34.0,0 +2023-12-01,v0.35.0,0 +2024-01-01,v0.13.0,118 +2024-01-01,v0.14.0,34 +2024-01-01,v0.15.0,65 +2024-01-01,v0.16.0,11 +2024-01-01,v0.17.0,71 +2024-01-01,v0.18.0,26 +2024-01-01,v0.19.0,64 +2024-01-01,v0.20.0,2 +2024-01-01,v0.20.1,40 +2024-01-01,v0.21.0,41 +2024-01-01,v0.22.0,29 +2024-01-01,v0.23.0,140 +2024-01-01,v0.24.0,2 +2024-01-01,v0.24.1,10 +2024-01-01,v0.25.0,54 +2024-01-01,v0.26.0,180 +2024-01-01,v0.27.0,289 +2024-01-01,v0.28.0,0 +2024-01-01,v0.29.0,0 +2024-01-01,v0.29.1,0 +2024-01-01,v0.30.0,0 +2024-01-01,v0.31.0,0 +2024-01-01,v0.32.0,0 +2024-01-01,v0.33.0,0 +2024-01-01,v0.34.0,0 +2024-01-01,v0.35.0,0 +2024-02-01,v0.13.0,119 +2024-02-01,v0.14.0,34 +2024-02-01,v0.15.0,64 +2024-02-01,v0.16.0,12 +2024-02-01,v0.17.0,71 +2024-02-01,v0.18.0,26 +2024-02-01,v0.19.0,64 +2024-02-01,v0.20.0,2 +2024-02-01,v0.20.1,39 +2024-02-01,v0.21.0,40 +2024-02-01,v0.22.0,27 +2024-02-01,v0.23.0,128 +2024-02-01,v0.24.0,1 +2024-02-01,v0.24.1,10 +2024-02-01,v0.25.0,48 +2024-02-01,v0.26.0,192 +2024-02-01,v0.27.0,186 +2024-02-01,v0.28.0,108 +2024-02-01,v0.29.0,0 +2024-02-01,v0.29.1,0 +2024-02-01,v0.30.0,0 +2024-02-01,v0.31.0,0 +2024-02-01,v0.32.0,0 +2024-02-01,v0.33.0,0 +2024-02-01,v0.34.0,0 +2024-02-01,v0.35.0,0 +2024-03-01,v0.13.0,114 +2024-03-01,v0.14.0,33 +2024-03-01,v0.15.0,64 +2024-03-01,v0.16.0,12 +2024-03-01,v0.17.0,71 +2024-03-01,v0.18.0,27 +2024-03-01,v0.19.0,66 +2024-03-01,v0.20.0,2 +2024-03-01,v0.20.1,36 +2024-03-01,v0.21.0,39 +2024-03-01,v0.22.0,30 +2024-03-01,v0.23.0,115 +2024-03-01,v0.24.0,1 +2024-03-01,v0.24.1,11 +2024-03-01,v0.25.0,47 +2024-03-01,v0.26.0,194 +2024-03-01,v0.27.0,154 +2024-03-01,v0.28.0,98 +2024-03-01,v0.29.0,0 +2024-03-01,v0.29.1,186 +2024-03-01,v0.30.0,0 +2024-03-01,v0.31.0,0 +2024-03-01,v0.32.0,0 +2024-03-01,v0.33.0,0 +2024-03-01,v0.34.0,0 +2024-03-01,v0.35.0,0 +2024-04-01,v0.13.0,114 +2024-04-01,v0.14.0,35 +2024-04-01,v0.15.0,64 +2024-04-01,v0.16.0,12 +2024-04-01,v0.17.0,71 +2024-04-01,v0.18.0,27 +2024-04-01,v0.19.0,69 +2024-04-01,v0.20.0,1 +2024-04-01,v0.20.1,34 +2024-04-01,v0.21.0,39 +2024-04-01,v0.22.0,29 +2024-04-01,v0.23.0,139 +2024-04-01,v0.24.0,1 +2024-04-01,v0.24.1,11 +2024-04-01,v0.25.0,42 +2024-04-01,v0.26.0,210 +2024-04-01,v0.27.0,142 +2024-04-01,v0.28.0,88 +2024-04-01,v0.29.0,0 +2024-04-01,v0.29.1,165 +2024-04-01,v0.30.0,219 +2024-04-01,v0.31.0,0 +2024-04-01,v0.32.0,0 +2024-04-01,v0.33.0,0 +2024-04-01,v0.34.0,0 +2024-04-01,v0.35.0,0 +2024-05-01,v0.13.0,119 +2024-05-01,v0.14.0,38 +2024-05-01,v0.15.0,64 +2024-05-01,v0.16.0,12 +2024-05-01,v0.17.0,70 +2024-05-01,v0.18.0,28 +2024-05-01,v0.19.0,68 +2024-05-01,v0.20.0,1 +2024-05-01,v0.20.1,31 +2024-05-01,v0.21.0,39 +2024-05-01,v0.22.0,26 +2024-05-01,v0.23.0,127 +2024-05-01,v0.24.0,1 +2024-05-01,v0.24.1,10 +2024-05-01,v0.25.0,43 +2024-05-01,v0.26.0,165 +2024-05-01,v0.27.0,143 +2024-05-01,v0.28.0,80 +2024-05-01,v0.29.0,0 +2024-05-01,v0.29.1,155 +2024-05-01,v0.30.0,161 +2024-05-01,v0.31.0,257 +2024-05-01,v0.32.0,0 +2024-05-01,v0.33.0,0 +2024-05-01,v0.34.0,0 +2024-05-01,v0.35.0,0 +2024-06-01,v0.13.0,121 +2024-06-01,v0.14.0,38 +2024-06-01,v0.15.0,64 +2024-06-01,v0.16.0,12 +2024-06-01,v0.17.0,71 +2024-06-01,v0.18.0,28 +2024-06-01,v0.19.0,68 +2024-06-01,v0.20.0,1 +2024-06-01,v0.20.1,32 +2024-06-01,v0.21.0,38 +2024-06-01,v0.22.0,25 +2024-06-01,v0.23.0,98 +2024-06-01,v0.24.0,1 +2024-06-01,v0.24.1,10 +2024-06-01,v0.25.0,45 +2024-06-01,v0.26.0,157 +2024-06-01,v0.27.0,159 +2024-06-01,v0.28.0,66 +2024-06-01,v0.29.0,0 +2024-06-01,v0.29.1,138 +2024-06-01,v0.30.0,156 +2024-06-01,v0.31.0,516 +2024-06-01,v0.32.0,32 +2024-06-01,v0.33.0,0 +2024-06-01,v0.34.0,0 +2024-06-01,v0.35.0,0 +2024-07-01,v0.13.0,121 +2024-07-01,v0.14.0,38 +2024-07-01,v0.15.0,64 +2024-07-01,v0.16.0,12 +2024-07-01,v0.17.0,71 +2024-07-01,v0.18.0,28 +2024-07-01,v0.19.0,68 +2024-07-01,v0.20.0,1 +2024-07-01,v0.20.1,32 +2024-07-01,v0.21.0,38 +2024-07-01,v0.22.0,25 +2024-07-01,v0.23.0,98 +2024-07-01,v0.24.0,1 +2024-07-01,v0.24.1,10 +2024-07-01,v0.25.0,45 +2024-07-01,v0.26.0,153 +2024-07-01,v0.27.0,142 +2024-07-01,v0.28.0,68 +2024-07-01,v0.29.0,0 +2024-07-01,v0.29.1,129 +2024-07-01,v0.30.0,136 +2024-07-01,v0.31.0,506 +2024-07-01,v0.32.0,101 +2024-07-01,v0.33.0,0 +2024-07-01,v0.34.0,0 +2024-07-01,v0.35.0,0 +2024-08-01,v0.13.0,120 +2024-08-01,v0.14.0,38 +2024-08-01,v0.15.0,64 +2024-08-01,v0.16.0,12 +2024-08-01,v0.17.0,69 +2024-08-01,v0.18.0,28 +2024-08-01,v0.19.0,67 +2024-08-01,v0.20.0,1 +2024-08-01,v0.20.1,31 +2024-08-01,v0.21.0,38 +2024-08-01,v0.22.0,25 +2024-08-01,v0.23.0,101 +2024-08-01,v0.24.0,1 +2024-08-01,v0.24.1,11 +2024-08-01,v0.25.0,51 +2024-08-01,v0.26.0,145 +2024-08-01,v0.27.0,126 +2024-08-01,v0.28.0,79 +2024-08-01,v0.29.0,0 +2024-08-01,v0.29.1,138 +2024-08-01,v0.30.0,149 +2024-08-01,v0.31.0,544 +2024-08-01,v0.32.0,296 +2024-08-01,v0.33.0,0 +2024-08-01,v0.34.0,0 +2024-08-01,v0.35.0,0 +2024-09-01,v0.13.0,122 +2024-09-01,v0.14.0,39 +2024-09-01,v0.15.0,66 +2024-09-01,v0.16.0,12 +2024-09-01,v0.17.0,69 +2024-09-01,v0.18.0,29 +2024-09-01,v0.19.0,66 +2024-09-01,v0.20.0,1 +2024-09-01,v0.20.1,31 +2024-09-01,v0.21.0,38 +2024-09-01,v0.22.0,26 +2024-09-01,v0.23.0,104 +2024-09-01,v0.24.0,1 +2024-09-01,v0.24.1,10 +2024-09-01,v0.25.0,48 +2024-09-01,v0.26.0,137 +2024-09-01,v0.27.0,135 +2024-09-01,v0.28.0,83 +2024-09-01,v0.29.0,0 +2024-09-01,v0.29.1,127 +2024-09-01,v0.30.0,147 +2024-09-01,v0.31.0,544 +2024-09-01,v0.32.0,305 +2024-09-01,v0.33.0,468 +2024-09-01,v0.34.0,0 +2024-09-01,v0.35.0,0 +2024-10-01,v0.13.0,124 +2024-10-01,v0.14.0,38 +2024-10-01,v0.15.0,67 +2024-10-01,v0.16.0,12 +2024-10-01,v0.17.0,68 +2024-10-01,v0.18.0,29 +2024-10-01,v0.19.0,66 +2024-10-01,v0.20.0,1 +2024-10-01,v0.20.1,34 +2024-10-01,v0.21.0,37 +2024-10-01,v0.22.0,30 +2024-10-01,v0.23.0,100 +2024-10-01,v0.24.0,1 +2024-10-01,v0.24.1,11 +2024-10-01,v0.25.0,48 +2024-10-01,v0.26.0,141 +2024-10-01,v0.27.0,127 +2024-10-01,v0.28.0,93 +2024-10-01,v0.29.0,0 +2024-10-01,v0.29.1,137 +2024-10-01,v0.30.0,142 +2024-10-01,v0.31.0,488 +2024-10-01,v0.32.0,266 +2024-10-01,v0.33.0,432 +2024-10-01,v0.34.0,0 +2024-10-01,v0.35.0,0 +2024-11-01,v0.13.0,120 +2024-11-01,v0.14.0,38 +2024-11-01,v0.15.0,67 +2024-11-01,v0.16.0,12 +2024-11-01,v0.17.0,69 +2024-11-01,v0.18.0,29 +2024-11-01,v0.19.0,65 +2024-11-01,v0.20.0,1 +2024-11-01,v0.20.1,33 +2024-11-01,v0.21.0,38 +2024-11-01,v0.22.0,30 +2024-11-01,v0.23.0,99 +2024-11-01,v0.24.0,1 +2024-11-01,v0.24.1,11 +2024-11-01,v0.25.0,48 +2024-11-01,v0.26.0,137 +2024-11-01,v0.27.0,129 +2024-11-01,v0.28.0,93 +2024-11-01,v0.29.0,0 +2024-11-01,v0.29.1,120 +2024-11-01,v0.30.0,120 +2024-11-01,v0.31.0,488 +2024-11-01,v0.32.0,221 +2024-11-01,v0.33.0,500 +2024-11-01,v0.34.0,168 +2024-11-01,v0.35.0,0 +2024-12-01,v0.13.0,122 +2024-12-01,v0.14.0,37 +2024-12-01,v0.15.0,66 +2024-12-01,v0.16.0,12 +2024-12-01,v0.17.0,69 +2024-12-01,v0.18.0,29 +2024-12-01,v0.19.0,64 +2024-12-01,v0.20.0,1 +2024-12-01,v0.20.1,55 +2024-12-01,v0.21.0,37 +2024-12-01,v0.22.0,30 +2024-12-01,v0.23.0,102 +2024-12-01,v0.24.0,1 +2024-12-01,v0.24.1,11 +2024-12-01,v0.25.0,51 +2024-12-01,v0.26.0,131 +2024-12-01,v0.27.0,120 +2024-12-01,v0.28.0,87 +2024-12-01,v0.29.0,0 +2024-12-01,v0.29.1,115 +2024-12-01,v0.30.0,124 +2024-12-01,v0.31.0,406 +2024-12-01,v0.32.0,208 +2024-12-01,v0.33.0,524 +2024-12-01,v0.34.0,458 +2024-12-01,v0.35.0,0 +2025-01-01,v0.13.0,123 +2025-01-01,v0.14.0,34 +2025-01-01,v0.15.0,66 +2025-01-01,v0.16.0,12 +2025-01-01,v0.17.0,50 +2025-01-01,v0.18.0,29 +2025-01-01,v0.19.0,64 +2025-01-01,v0.20.0,1 +2025-01-01,v0.20.1,54 +2025-01-01,v0.21.0,36 +2025-01-01,v0.22.0,30 +2025-01-01,v0.23.0,101 +2025-01-01,v0.24.0,1 +2025-01-01,v0.24.1,11 +2025-01-01,v0.25.0,50 +2025-01-01,v0.26.0,137 +2025-01-01,v0.27.0,127 +2025-01-01,v0.28.0,89 +2025-01-01,v0.29.0,0 +2025-01-01,v0.29.1,117 +2025-01-01,v0.30.0,134 +2025-01-01,v0.31.0,354 +2025-01-01,v0.32.0,227 +2025-01-01,v0.33.0,514 +2025-01-01,v0.34.0,644 +2025-01-01,v0.35.0,3 +2025-01-01,v0.36.0,0 +2025-01-01,v0.37.0,0 +2025-01-01,v0.38.0,0 +2025-01-01,v0.39.0,0 +2025-01-01,v0.40.0,0 +2025-02-01,v0.13.0,120 +2025-02-01,v0.14.0,33 +2025-02-01,v0.15.0,66 +2025-02-01,v0.16.0,12 +2025-02-01,v0.17.0,51 +2025-02-01,v0.18.0,31 +2025-02-01,v0.19.0,64 +2025-02-01,v0.20.0,1 +2025-02-01,v0.20.1,56 +2025-02-01,v0.21.0,36 +2025-02-01,v0.22.0,30 +2025-02-01,v0.23.0,100 +2025-02-01,v0.24.0,1 +2025-02-01,v0.24.1,11 +2025-02-01,v0.25.0,48 +2025-02-01,v0.26.0,132 +2025-02-01,v0.27.0,124 +2025-02-01,v0.28.0,92 +2025-02-01,v0.29.0,0 +2025-02-01,v0.29.1,121 +2025-02-01,v0.30.0,138 +2025-02-01,v0.31.0,424 +2025-02-01,v0.32.0,209 +2025-02-01,v0.33.0,510 +2025-02-01,v0.34.0,414 +2025-02-01,v0.35.0,556 +2025-02-01,v0.36.0,0 +2025-02-01,v0.37.0,0 +2025-02-01,v0.38.0,0 +2025-02-01,v0.39.0,0 +2025-02-01,v0.40.0,0 +2025-03-01,v0.13.0,128 +2025-03-01,v0.14.0,33 +2025-03-01,v0.15.0,66 +2025-03-01,v0.16.0,12 +2025-03-01,v0.17.0,50 +2025-03-01,v0.18.0,31 +2025-03-01,v0.19.0,63 +2025-03-01,v0.20.0,1 +2025-03-01,v0.20.1,54 +2025-03-01,v0.21.0,36 +2025-03-01,v0.22.0,32 +2025-03-01,v0.23.0,100 +2025-03-01,v0.24.0,1 +2025-03-01,v0.24.1,11 +2025-03-01,v0.25.0,47 +2025-03-01,v0.26.0,133 +2025-03-01,v0.27.0,125 +2025-03-01,v0.28.0,94 +2025-03-01,v0.29.0,0 +2025-03-01,v0.29.1,118 +2025-03-01,v0.30.0,148 +2025-03-01,v0.31.0,396 +2025-03-01,v0.32.0,180 +2025-03-01,v0.33.0,468 +2025-03-01,v0.34.0,388 +2025-03-01,v0.35.0,668 +2025-03-01,v0.36.0,0 +2025-03-01,v0.37.0,0 +2025-03-01,v0.38.0,0 +2025-03-01,v0.39.0,0 +2025-03-01,v0.40.0,0 +2025-04-01,v0.13.0,127 +2025-04-01,v0.14.0,33 +2025-04-01,v0.15.0,67 +2025-04-01,v0.16.0,12 +2025-04-01,v0.17.0,50 +2025-04-01,v0.18.0,32 +2025-04-01,v0.19.0,63 +2025-04-01,v0.20.0,1 +2025-04-01,v0.20.1,54 +2025-04-01,v0.21.0,36 +2025-04-01,v0.22.0,32 +2025-04-01,v0.23.0,99 +2025-04-01,v0.24.0,1 +2025-04-01,v0.24.1,11 +2025-04-01,v0.25.0,47 +2025-04-01,v0.26.0,131 +2025-04-01,v0.27.0,103 +2025-04-01,v0.28.0,95 +2025-04-01,v0.29.0,0 +2025-04-01,v0.29.1,103 +2025-04-01,v0.30.0,155 +2025-04-01,v0.31.0,401 +2025-04-01,v0.32.0,189 +2025-04-01,v0.33.0,460 +2025-04-01,v0.34.0,326 +2025-04-01,v0.35.0,526 +2025-04-01,v0.36.0,316 +2025-04-01,v0.37.0,0 +2025-04-01,v0.38.0,0 +2025-04-01,v0.39.0,0 +2025-04-01,v0.40.0,0 +2025-05-01,v0.13.0,123 +2025-05-01,v0.14.0,33 +2025-05-01,v0.15.0,68 +2025-05-01,v0.16.0,12 +2025-05-01,v0.17.0,51 +2025-05-01,v0.18.0,33 +2025-05-01,v0.19.0,63 +2025-05-01,v0.20.0,1 +2025-05-01,v0.20.1,54 +2025-05-01,v0.21.0,34 +2025-05-01,v0.22.0,30 +2025-05-01,v0.23.0,98 +2025-05-01,v0.24.0,2 +2025-05-01,v0.24.1,10 +2025-05-01,v0.25.0,48 +2025-05-01,v0.26.0,130 +2025-05-01,v0.27.0,130 +2025-05-01,v0.28.0,96 +2025-05-01,v0.29.0,0 +2025-05-01,v0.29.1,99 +2025-05-01,v0.30.0,154 +2025-05-01,v0.31.0,373 +2025-05-01,v0.32.0,191 +2025-05-01,v0.33.0,484 +2025-05-01,v0.34.0,384 +2025-05-01,v0.35.0,508 +2025-05-01,v0.36.0,213 +2025-05-01,v0.37.0,524 +2025-05-01,v0.38.0,0 +2025-05-01,v0.39.0,0 +2025-05-01,v0.40.0,0 +2025-06-01,v0.13.0,123 +2025-06-01,v0.14.0,33 +2025-06-01,v0.15.0,68 +2025-06-01,v0.16.0,12 +2025-06-01,v0.17.0,51 +2025-06-01,v0.18.0,33 +2025-06-01,v0.19.0,63 +2025-06-01,v0.20.0,1 +2025-06-01,v0.20.1,54 +2025-06-01,v0.21.0,35 +2025-06-01,v0.22.0,29 +2025-06-01,v0.23.0,93 +2025-06-01,v0.24.0,2 +2025-06-01,v0.24.1,12 +2025-06-01,v0.25.0,51 +2025-06-01,v0.26.0,137 +2025-06-01,v0.27.0,128 +2025-06-01,v0.28.0,97 +2025-06-01,v0.29.0,0 +2025-06-01,v0.29.1,109 +2025-06-01,v0.30.0,154 +2025-06-01,v0.31.0,412 +2025-06-01,v0.32.0,194 +2025-06-01,v0.33.0,464 +2025-06-01,v0.34.0,424 +2025-06-01,v0.35.0,534 +2025-06-01,v0.36.0,186 +2025-06-01,v0.37.0,752 +2025-06-01,v0.38.0,0 +2025-06-01,v0.39.0,0 +2025-06-01,v0.40.0,0 +2025-07-01,v0.13.0,118 +2025-07-01,v0.14.0,33 +2025-07-01,v0.15.0,67 +2025-07-01,v0.16.0,12 +2025-07-01,v0.17.0,52 +2025-07-01,v0.18.0,33 +2025-07-01,v0.19.0,64 +2025-07-01,v0.20.0,1 +2025-07-01,v0.20.1,54 +2025-07-01,v0.21.0,35 +2025-07-01,v0.22.0,30 +2025-07-01,v0.23.0,93 +2025-07-01,v0.24.0,2 +2025-07-01,v0.24.1,13 +2025-07-01,v0.25.0,55 +2025-07-01,v0.26.0,139 +2025-07-01,v0.27.0,131 +2025-07-01,v0.28.0,98 +2025-07-01,v0.29.0,0 +2025-07-01,v0.29.1,114 +2025-07-01,v0.30.0,160 +2025-07-01,v0.31.0,380 +2025-07-01,v0.32.0,203 +2025-07-01,v0.33.0,464 +2025-07-01,v0.34.0,404 +2025-07-01,v0.35.0,500 +2025-07-01,v0.36.0,216 +2025-07-01,v0.37.0,1100 +2025-07-01,v0.38.0,97 +2025-07-01,v0.39.0,0 +2025-07-01,v0.40.0,0 +2025-08-01,v0.13.0,116 +2025-08-01,v0.14.0,33 +2025-08-01,v0.15.0,87 +2025-08-01,v0.16.0,12 +2025-08-01,v0.17.0,50 +2025-08-01,v0.18.0,33 +2025-08-01,v0.19.0,64 +2025-08-01,v0.20.0,1 +2025-08-01,v0.20.1,55 +2025-08-01,v0.21.0,35 +2025-08-01,v0.22.0,30 +2025-08-01,v0.23.0,92 +2025-08-01,v0.24.0,1 +2025-08-01,v0.24.1,16 +2025-08-01,v0.25.0,48 +2025-08-01,v0.26.0,146 +2025-08-01,v0.27.0,132 +2025-08-01,v0.28.0,94 +2025-08-01,v0.29.0,0 +2025-08-01,v0.29.1,114 +2025-08-01,v0.30.0,156 +2025-08-01,v0.31.0,399 +2025-08-01,v0.32.0,200 +2025-08-01,v0.33.0,436 +2025-08-01,v0.34.0,340 +2025-08-01,v0.35.0,466 +2025-08-01,v0.36.0,192 +2025-08-01,v0.37.0,840 +2025-08-01,v0.38.0,912 +2025-08-01,v0.39.0,0 +2025-08-01,v0.40.0,0 +2025-09-01,v0.13.0,115 +2025-09-01,v0.14.0,33 +2025-09-01,v0.15.0,87 +2025-09-01,v0.16.0,12 +2025-09-01,v0.17.0,50 +2025-09-01,v0.18.0,33 +2025-09-01,v0.19.0,64 +2025-09-01,v0.20.0,1 +2025-09-01,v0.20.1,56 +2025-09-01,v0.21.0,35 +2025-09-01,v0.22.0,29 +2025-09-01,v0.23.0,92 +2025-09-01,v0.24.0,1 +2025-09-01,v0.24.1,17 +2025-09-01,v0.25.0,51 +2025-09-01,v0.26.0,152 +2025-09-01,v0.27.0,117 +2025-09-01,v0.28.0,93 +2025-09-01,v0.29.0,0 +2025-09-01,v0.29.1,103 +2025-09-01,v0.30.0,158 +2025-09-01,v0.31.0,359 +2025-09-01,v0.32.0,198 +2025-09-01,v0.33.0,420 +2025-09-01,v0.34.0,344 +2025-09-01,v0.35.0,460 +2025-09-01,v0.36.0,204 +2025-09-01,v0.37.0,852 +2025-09-01,v0.38.0,592 +2025-09-01,v0.39.0,392 +2025-09-01,v0.40.0,0 +2025-10-01,v0.13.0,115 +2025-10-01,v0.14.0,33 +2025-10-01,v0.15.0,87 +2025-10-01,v0.16.0,12 +2025-10-01,v0.17.0,50 +2025-10-01,v0.18.0,32 +2025-10-01,v0.19.0,65 +2025-10-01,v0.20.0,1 +2025-10-01,v0.20.1,57 +2025-10-01,v0.21.0,35 +2025-10-01,v0.22.0,28 +2025-10-01,v0.23.0,92 +2025-10-01,v0.24.0,1 +2025-10-01,v0.24.1,18 +2025-10-01,v0.25.0,50 +2025-10-01,v0.26.0,151 +2025-10-01,v0.27.0,127 +2025-10-01,v0.28.0,93 +2025-10-01,v0.29.0,0 +2025-10-01,v0.29.1,103 +2025-10-01,v0.30.0,174 +2025-10-01,v0.31.0,367 +2025-10-01,v0.32.0,191 +2025-10-01,v0.33.0,398 +2025-10-01,v0.34.0,352 +2025-10-01,v0.35.0,534 +2025-10-01,v0.36.0,191 +2025-10-01,v0.37.0,852 +2025-10-01,v0.38.0,564 +2025-10-01,v0.39.0,584 +2025-10-01,v0.40.0,0 +2025-11-01,v0.13.0,114 +2025-11-01,v0.14.0,31 +2025-11-01,v0.15.0,87 +2025-11-01,v0.16.0,11 +2025-11-01,v0.17.0,50 +2025-11-01,v0.18.0,32 +2025-11-01,v0.19.0,60 +2025-11-01,v0.20.0,1 +2025-11-01,v0.20.1,56 +2025-11-01,v0.21.0,35 +2025-11-01,v0.22.0,28 +2025-11-01,v0.23.0,90 +2025-11-01,v0.24.0,1 +2025-11-01,v0.24.1,18 +2025-11-01,v0.25.0,49 +2025-11-01,v0.26.0,151 +2025-11-01,v0.27.0,130 +2025-11-01,v0.28.0,93 +2025-11-01,v0.29.0,0 +2025-11-01,v0.29.1,103 +2025-11-01,v0.30.0,165 +2025-11-01,v0.31.0,353 +2025-11-01,v0.32.0,198 +2025-11-01,v0.33.0,416 +2025-11-01,v0.34.0,312 +2025-11-01,v0.35.0,520 +2025-11-01,v0.36.0,173 +2025-11-01,v0.37.0,808 +2025-11-01,v0.38.0,656 +2025-11-01,v0.39.0,544 +2025-11-01,v0.40.0,240 diff --git a/docs/usage-metrics.md b/docs/usage-metrics.md new file mode 100644 index 0000000000..a1d8269299 --- /dev/null +++ b/docs/usage-metrics.md @@ -0,0 +1,34 @@ +# Usage Metrics + +
Loading metrics data...
+ + + + +## GitHub Stars + +[![Star History Chart](https://api.star-history.com/svg?repos=testcontainers/testcontainers-go&type=date&legend=top-left)](https://www.star-history.com/#testcontainers/testcontainers-go&type=date&legend=top-left) diff --git a/mkdocs.yml b/mkdocs.yml index ac77b5b3bc..10b1dc85f2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,6 +19,12 @@ theme: extra_css: - css/extra.css - css/tc-header.css + - css/usage-metrics.css +extra_javascript: + - https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js + - https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@3.0.0/dist/chartjs-adapter-date-fns.bundle.min.js + - https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js + - js/usage-metrics.js repo_name: testcontainers-go repo_url: https://github.com/testcontainers/testcontainers-go markdown_extensions: @@ -148,6 +154,7 @@ nav: - system_requirements/using_colima.md - system_requirements/using_podman.md - system_requirements/rancher.md + - Usage Metrics: usage-metrics.md - Dependabot: dependabot.md - Contributing: contributing.md - Getting help: getting_help.md diff --git a/modulegen/internal/mkdocs/types.go b/modulegen/internal/mkdocs/types.go index 460f958282..6a599b56a7 100644 --- a/modulegen/internal/mkdocs/types.go +++ b/modulegen/internal/mkdocs/types.go @@ -23,6 +23,7 @@ type Config struct { Favicon string `yaml:"favicon"` } `yaml:"theme"` ExtraCSS []string `yaml:"extra_css"` + ExtraJavascript []string `yaml:"extra_javascript"` RepoName string `yaml:"repo_name"` RepoURL string `yaml:"repo_url"` MarkdownExtensions []any `yaml:"markdown_extensions"` @@ -34,6 +35,7 @@ type Config struct { Modules []string `yaml:"Modules,omitempty"` SystemRequirements []any `yaml:"System Requirements,omitempty"` Dependabot string `yaml:"Dependabot,omitempty"` + UsageMetrics string `yaml:"Usage Metrics,omitempty"` Contributing string `yaml:"Contributing,omitempty"` GettingHelp string `yaml:"Getting help,omitempty"` } `yaml:"nav"` diff --git a/scripts/changed-modules.sh b/scripts/changed-modules.sh index 42419653a1..ac530995be 100755 --- a/scripts/changed-modules.sh +++ b/scripts/changed-modules.sh @@ -86,6 +86,7 @@ readonly excluded_files=( ".github/workflows/release-drafter.yml" ".github/workflows/scorecards.yml" ".github/workflows/sonar-*.yml" + ".github/workflows/usage-metrics.yml" "scripts/bump-*.sh" "scripts/check_environment.sh" "scripts/*release.sh" @@ -98,6 +99,7 @@ readonly excluded_files=( "RELEASING.md" "requirements.txt" "runtime.txt" + "docs/usage-metrics.csv" ) # define an array of modules that won't be part of the build @@ -124,8 +126,11 @@ readonly rootModule="\"\"" # capture the modulegen module readonly modulegenModule="\"modulegen\"" +# capture the usage-metrics module +readonly usageMetricsModule="\"usage-metrics\"" + # merge all modules and examples into a single array -allModules=(${rootModule} ${modulegenModule} "${modules[@]}") +allModules=(${rootModule} ${modulegenModule} ${usageMetricsModule} "${modules[@]}") # sort allModules array IFS=$'\n' allModules=($(sort <<<"${allModules[*]}")) diff --git a/usage-metrics/Makefile b/usage-metrics/Makefile new file mode 100644 index 0000000000..748cb213ea --- /dev/null +++ b/usage-metrics/Makefile @@ -0,0 +1 @@ +include ../commons-test.mk diff --git a/usage-metrics/README.md b/usage-metrics/README.md new file mode 100644 index 0000000000..6b5390a558 --- /dev/null +++ b/usage-metrics/README.md @@ -0,0 +1,188 @@ +# Testcontainers-Go Usage Metrics + +This directory contains the automation system for tracking testcontainers-go usage across GitHub repositories. + +## Overview + +The system automatically collects usage metrics by querying the GitHub Code Search API for references to testcontainers-go in `go.mod` files across public repositories. The data is visualized in an interactive dashboard integrated into the main MkDocs documentation site at https://golang.testcontainers.org/usage-metrics/ + +## Components + +### 📊 Data Collection (`scripts/`) +- **collect-metrics.go**: Go program that queries GitHub's Code Search API +- Searches for `"testcontainers/testcontainers-go {version}"` in go.mod files +- Excludes forks and testcontainers organization repositories +- Stores results in CSV format with timestamps + +### 💾 Data Storage (`docs/usage-metrics.csv`) +- **usage-metrics.csv**: Historical usage data in CSV format +- Format: `date,version,count` +- Version-controlled for historical tracking +- Integrated with MkDocs site + +### 🌐 Website (integrated into `docs/`) +- **docs/usage-metrics.md**: Markdown page for the dashboard +- **docs/js/usage-metrics.js**: JavaScript for chart rendering +- **docs/css/usage-metrics.css**: Styles for the dashboard +- Uses Chart.js for visualizations +- Shows trends, version comparisons, and statistics +- Responsive design for mobile and desktop + +### 🤖 Automation (`.github/workflows/usage-metrics.yml`) +- Runs monthly on the 1st at 9 AM UTC +- Can be manually triggered with custom versions +- Automatically queries all versions from v0.13.0 to latest +- Creates pull requests for metrics updates (not direct commits) +- Data is deployed via the main MkDocs site when PR is merged + +## Versions Tracked + +The system tracks all versions (including patch versions) from **v0.13.0** to the **latest release** (currently v0.40.0). This includes versions like v0.34.1, v0.29.1, etc. + +## Usage + +### Manual Collection + +To manually collect metrics for specific versions (queries run sequentially with automatic retry and backoff): + +```bash +cd usage-metrics +go run collect-metrics.go -version v0.37.0 -version v0.38.0 -version v0.39.0 -csv ../docs/usage-metrics.csv +``` + +The collection script includes automatic retry with exponential backoff (5s, 10s, 20s, 40s, 60s) for rate limit resilience. For example, to test with a few recent versions: + +```bash +cd usage-metrics +go run collect-metrics.go -version v0.38.0 -version v0.39.0 -version v0.40.0 -csv ../docs/usage-metrics.csv +``` + +### Running Locally + +To view the dashboard locally with the full MkDocs site: + +```bash +# Serve the docs +make serve-docs + +# Open http://localhost:8000/usage-metrics/ +``` + +### Manual Workflow Trigger + +You can manually trigger the collection workflow from GitHub: + +1. Go to Actions → "Update Usage Metrics" +2. Click "Run workflow" +3. Optionally specify versions (e.g., `v0.39.0,v0.38.0`) or leave empty for all versions +4. Click "Run workflow" + +## Data Format + +The CSV file has three columns: + +- **date**: Collection date in YYYY-MM-DD format +- **version**: Version string (e.g., v0.27.0) +- **count**: Number of repositories using this version + +Example: +```csv +date,version,count +2024-01-15,v0.27.0,133 +2024-02-15,v0.27.0,145 +``` + +## Viewing the Dashboard + +The dashboard is integrated into the main documentation site: +- **Production**: https://golang.testcontainers.org/usage-metrics/ +- **Local**: http://localhost:8000/usage-metrics/ (when running `make serve-docs`) + +The dashboard displays: +- Total repositories using testcontainers-go +- Number of versions tracked +- Latest version information +- Usage trends over time (line chart) +- Version comparison (bar chart) +- Distribution by version (doughnut chart) + +## Rate Limiting + +GitHub API rate limits: +- **Unauthenticated**: 10 requests/minute +- **Authenticated**: 30 requests/minute + +The collection script queries versions sequentially with automatic retry and exponential backoff (5s, 10s, 20s, 40s, 60s) to handle rate limit errors gracefully. The script will automatically retry up to 5 times if it encounters rate limiting. + +## Customization + +### Changing Collection Frequency + +Edit the cron schedule in the workflow: + +```yaml +schedule: + - cron: '0 9 1 * *' # Monthly on the 1st at 9 AM UTC +``` + +### Customizing Charts + +Edit `docs/js/usage-metrics.js` to modify chart types, colors, or add new visualizations. + +### Changing Version Range + +By default, the workflow queries all versions from v0.13.0 onwards. To change this, modify the awk pattern in the workflow file. + +## Architecture Decisions + +### Why CSV? +- Simple and human-readable +- Version-controlled with Git +- Easy to import/export +- No database required +- Suitable for the data volume + +### Why Integrate with MkDocs? +- Single documentation site for all content +- Consistent look and feel +- Same deployment pipeline +- Easy maintenance +- No separate hosting needed + +### Why Go for Collection? +- Native GitHub API support +- Easy to integrate with existing Go project +- Simple deployment +- Good CSV handling + +## Troubleshooting + +### API Rate Limiting +If you hit rate limits: +1. The collection script includes automatic retry with exponential backoff (5s, 10s, 20s, 40s, 60s up to 5 attempts) +2. Queries run sequentially to minimize rate limit issues +3. The workflow uses the `gh` CLI which automatically uses GitHub's token for higher limits + +### CSV Not Updating +Check the workflow logs: +1. Go to Actions → "Update Usage Metrics" +2. Click on the latest run +3. Review the "Query versions" step + +### Charts Not Displaying +1. Ensure CSV file is properly formatted +2. Check browser console for JavaScript errors +3. Verify the file paths are correct +4. Make sure Chart.js and PapaParse CDN links are accessible + +## Contributing + +To add features or fix issues: + +1. Test changes locally with `mkdocs serve` +2. Update this README if needed +3. Submit a pull request + +## License + +Same as the main testcontainers-go repository (MIT). diff --git a/usage-metrics/collect-metrics.go b/usage-metrics/collect-metrics.go new file mode 100644 index 0000000000..4e4e511367 --- /dev/null +++ b/usage-metrics/collect-metrics.go @@ -0,0 +1,204 @@ +package main + +import ( + "encoding/csv" + "encoding/json" + "errors" + "flag" + "fmt" + "log" + "net/url" + "os" + "os/exec" + "path/filepath" + "sort" + "strconv" + "strings" + "time" +) + +type searchResponse struct { + TotalCount int `json:"total_count"` +} + +type usageMetric struct { + Date string + Version string + Count int +} + +type arrayFlags []string + +func (a *arrayFlags) String() string { + return strings.Join(*a, ",") +} + +func (a *arrayFlags) Set(value string) error { + *a = append(*a, value) + return nil +} + +func main() { + var versions arrayFlags + csvPath := flag.String("csv", "../../docs/usage-metrics.csv", "Path to CSV file") + flag.Var(&versions, "version", "Version to query (can be specified multiple times)") + flag.Parse() + + if len(versions) == 0 { + log.Fatal("At least one version is required. Use -version flag (can be repeated)") + } + + if err := collectMetrics(versions, *csvPath); err != nil { + log.Fatalf("Failed to collect metrics: %v", err) + } +} + +func collectMetrics(versions []string, csvPath string) error { + date := time.Now().Format("2006-01-02") + metrics := make([]usageMetric, 0, len(versions)) + + // Query all versions sequentially + for _, version := range versions { + version = strings.TrimSpace(version) + if version == "" { + continue + } + + count, err := queryGitHubUsageWithRetry(version) + if err != nil { + log.Printf("Warning: Failed to query version %s after retries: %v", version, err) + continue + } + + metric := usageMetric{ + Date: date, + Version: version, + Count: count, + } + + metrics = append(metrics, metric) + fmt.Printf("Successfully queried: %s has %d usages on %s\n", version, count, metric.Date) + } + + // Sort metrics by version + sort.Slice(metrics, func(i, j int) bool { + return metrics[i].Version < metrics[j].Version + }) + + // Write all metrics to CSV + for _, metric := range metrics { + if err := appendToCSV(csvPath, metric); err != nil { + log.Printf("Warning: Failed to write metric for %s: %v", metric.Version, err) + continue + } + fmt.Printf("Successfully recorded: %s has %d usages on %s\n", metric.Version, metric.Count, metric.Date) + } + + return nil +} + +func queryGitHubUsageWithRetry(version string) (int, error) { + var lastErr error + // Backoff intervals: 5s, 10s, 20s, 40s, 60s + backoffIntervals := []time.Duration{ + 5 * time.Second, + 10 * time.Second, + 20 * time.Second, + 40 * time.Second, + 60 * time.Second, + } + + // maxRetries includes the initial attempt plus one retry per backoff interval + maxRetries := len(backoffIntervals) + 1 + + for attempt := 0; attempt < maxRetries; attempt++ { + if attempt > 0 { + // Use predefined backoff intervals + waitTime := backoffIntervals[attempt-1] + log.Printf("Retrying version %s in %v (attempt %d/%d)", version, waitTime, attempt+1, maxRetries) + time.Sleep(waitTime) + } + + count, err := queryGitHubUsage(version) + if err == nil { + return count, nil + } + + lastErr = err + + // Check if it's a rate limit error + if strings.Contains(err.Error(), "rate limit") || strings.Contains(err.Error(), "403") { + log.Printf("Rate limit hit for version %s, will retry with backoff", version) + continue + } + + // For non-rate-limit errors, retry but with shorter backoff + log.Printf("Error querying version %s: %v", version, err) + } + + return 0, fmt.Errorf("max retries reached: %w", lastErr) +} + +func queryGitHubUsage(version string) (int, error) { + query := fmt.Sprintf(`"testcontainers/testcontainers-go %s" filename:go.mod -is:fork -org:testcontainers`, version) + + params := url.Values{} + params.Add("q", query) + endpoint := "/search/code?" + params.Encode() + + output, err := exec.Command("gh", "api", + "-H", "Accept: application/vnd.github+json", + "-H", "X-GitHub-Api-Version: 2022-11-28", + endpoint, + ).Output() + if err != nil { + exitErr := &exec.ExitError{} + if errors.As(err, &exitErr) { + return 0, fmt.Errorf("gh api failed: %s", string(exitErr.Stderr)) + } + return 0, fmt.Errorf("gh api: %w", err) + } + + var resp searchResponse + if err := json.Unmarshal(output, &resp); err != nil { + return 0, fmt.Errorf("unmarshal: %w", err) + } + + return resp.TotalCount, nil +} + +func appendToCSV(csvPath string, metric usageMetric) error { + absPath, err := filepath.Abs(csvPath) + if err != nil { + return fmt.Errorf("resolve path: %w", err) + } + + _, err = os.Stat(absPath) + fileExists := !os.IsNotExist(err) + + file, err := os.OpenFile(absPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return fmt.Errorf("open file: %w", err) + } + defer file.Close() + + writer := csv.NewWriter(file) + + if !fileExists { + if err := writer.Write([]string{"date", "version", "count"}); err != nil { + return fmt.Errorf("write header: %w", err) + } + } + + record := []string{metric.Date, metric.Version, strconv.Itoa(metric.Count)} + if err := writer.Write(record); err != nil { + return fmt.Errorf("write record: %w", err) + } + + writer.Flush() + if err := writer.Error(); err != nil { + return fmt.Errorf("flush csv: %w", err) + } + + return nil +} diff --git a/usage-metrics/go.mod b/usage-metrics/go.mod new file mode 100644 index 0000000000..16436e89f6 --- /dev/null +++ b/usage-metrics/go.mod @@ -0,0 +1,5 @@ +module github.com/testcontainers/testcontainers-go/usage-metrics + +go 1.24 + +toolchain go1.24.7