EpiVerse
  • Organization
  • Repositories
  • Maintainers
  • Community Health
    • Source Code
    • Request a Feature

On this page

  • Aggregate metrics
  • Maintenance deficit
  • Additional metrics and indicators
    • Contributor Absence

Organization

This page provides an organization-level overview of all repositories in both the reconhub and epiverse-trace GitHub organizations. Statistics here are assessed for each repository, and aggregated across the organizations into four distinct categories:

  1. Development: Metrics of both code development and maintainer continuity and diversity. High scores reflect repositories with high levels of code development from a diverse community of maintainers.
  2. GitHub: Metrics derived from GitHub issues and pull requests. High scores reflect repositories with active use of issues, pull requests, code reviews, and rapid responses from core maintainers to issues or pull requests opened by wider community members.
  3. Popularity: Metrics of repository popularity, derived from CRAN download numbers (where applicable), GitHub stars and forks, and issue comments from user communities beyond core maintainers.
  4. Dependencies and Releases: High scores reflect repositories with fewer dependencies and frequent releases.

The Overall column is an average of all metrics across all of these four categorical groupings, and provides an overall metric of repository health.


function sparkbar(max) {
  const colourScale = d3.scaleSequential(d3.interpolateCool)
    .domain([0, max]);

  return (x) => htl.html`<div style="
    background: ${colourScale(x)};
    color: black;
    width: ${100 * x / max}%;
    float: right;
    padding-right: 3px;
    box-sizing: border-box;
    overflow: visible;
    display: flex;
    justify-content: end;">${x.toFixed(2).toLocaleString("en-US")}`
}

function tooltip(title, expl) {
    const th = document.createElement("th");
    th.title = expl
    th.style.background = "#f0f8ff";
    th.textContent = title;

    th.addEventListener("mouseover", () => th.style.background = "#d0e8ff");
    th.addEventListener("mouseout", () => th.style.background = "#f0f8ff");

    return th;
}

function pkgfmt(pkg) {
    const th = document.createElement("th");
    th.title = "hover"
    th.style.background = "#f0f8ff";
    th.textContent = pkg;

    th.addEventListener("mouseover", () => th.style.background = "#d0e8ff");
    th.addEventListener("mouseout", () => th.style.background = "#f0f8ff");

    th.addEventListener("click", () => {
        localStorage.setItem("orgmetricsRepo", pkg);
        th.style.background="#a0f8ff";
        window.location.href="/repometrics-demo/repo.html";
    });

    return th;
}

function ctbfmt(ctb) {
    const th = document.createElement("th");
    th.title = "hover"
    th.style.background = "#f0f8ff";
    th.textContent = ctb;

    th.addEventListener("mouseover", () => th.style.background = "#d0e8ff");
    th.addEventListener("mouseout", () => th.style.background = "#f0f8ff");

    th.addEventListener("click", () => {
        console.log("----setting localStorage maintainer to: " + ctb);
        localStorage.setItem('orgmetricsMaintainer', ctb);
        th.style.background="#a0f8ff";
        window.location.href="/repometrics-demo/maintainer.html";
    });

    return th;
}

Aggregate metrics

This chart shows the recent development of metrics aggregated into each of the four groups, plus the “Overall” metric averaged across all four groups.

metricsData = {
    return transpose(metrics_dates_in).map(row => ({
        ...row,
        date: new Date(row.date)
    }));
}
Plot.plot({
    color: {
        legend: true,
        label: "name",
        swatchHeight: 4,
        domain: ["Development", "GitHub", "Popularity", "Dep.+Rel.", "Overall"],
    },
    marks: [
        Plot.lineY(metricsData, {
            x: "date",
            y: "value",
            stroke: "name",
            strokeWidth:  2,
            strokeDasharray: "2,5",
        }),
        Plot.linearRegressionY(metricsData, {
            x: "date",
            y: "value",
            stroke: "name",
            strokeWidth: 2,
            ci: 0
        }),
        Plot.axisY({
            label: null,
        }),
    ],
    x: {
        grid: true,
        type: "utc",
        domain: [d3.min(metricsData, d => d.date), d3.max(metricsData, d => d.date)],
        tickFormat: "%Y", 
        ticks: [...new Set(metricsData.map(d => d.date.getFullYear()))].map(year => new Date(`${year}-01-01`)),
    },
    y: { grid: true },
    style: {
        fontSize: '16px',
    }
})

And table shows metrics for each package, for the latest time period only aggregated into each of the four groups. Clicking on the “package” values will lead to the repository maintenance page with further details of the selected package or repository.

metricsGroupedTable = {
    return transpose(metrics_table_in).map(row => ({
        ...row,
    }));
}
Inputs.table(metricsGroupedTable, {
    width: {
        package: 100,
        total: 200,
        development: 200,
        issues: 200,
        popularity: 200,
        meta: 200,
    },
    format: {
        package: d => pkgfmt(d),
        development: sparkbar(d3.max(metricsGroupedTable, d => d.development)),
        issues: sparkbar(d3.max(metricsGroupedTable, d => d.issues)),
        popularity: sparkbar(d3.max(metricsGroupedTable, d => d.popularity)),
        meta: sparkbar(d3.max(metricsGroupedTable, d => d.meta)),
        total: sparkbar(d3.max(metricsGroupedTable, d => d.total)),
    },
    header: {
        development: tooltip("Development", "Code development and maintenance metrics"),
        issues: tooltip("Issues", "GitHub issues and pull request activity"),
        popularity: tooltip("Popularity", "Project popularity on CRAN (where applicable) and GitHub"),
        meta: tooltip("Dependencies and releases", ""),
        total: tooltip("Overall", "Average across all four categories of metrics."),
    },
})

Maintenance deficit

This next graph shows the maintenance deficit over time, as the difference between community engagement and developer responsiveness. Repositories with high community engagement yet low developer responsiveness have a high maintenance deficit, and vice-versa. The “Deficit” scores are scaled to fix within the same range as the metrics of community engagement and developer responsiveness.

maintenanceData = {
    return transpose(maintenance_ts_in).map(row => ({
        ...row,
        date: new Date(row.date)
    }));
}
Plot.plot({
    color: {
        legend: true,
        label: "name",
        swatchHeight: 4,
        domain: ["Comm. Engage.", "Dev. Resp.", "Deficit"],
    },
    marks: [
        Plot.lineY(maintenanceData, {
            x: "date",
            y: "value",
            stroke: "name",
            strokeWidth:  2,
            strokeDasharray: "2,5",
        }),
        Plot.linearRegressionY(maintenanceData, {
            x: "date",
            y: "value",
            stroke: "name",
            strokeWidth: 2,
            ci: 0
        }),
        Plot.axisY({
            label: null,
        }),
    ],
    x: {
        grid: true,
        type: "utc",
        domain: [d3.min(metricsData, d => d.date), d3.max(metricsData, d => d.date)],
        tickFormat: "%Y",
        ticks: [...new Set(metricsData.map(d => d.date.getFullYear()))].map(year => new Date(`${year}-01-01`)),
    },
    y: { grid: true },
    style: {
        fontSize: '16px',
    }
})

And these are maintenance deficit values for individual repositories (packages), for the latest time period only.

maintenanceRepoData = {
    return transpose(maintenance_repo_in).map(row => ({
        ...row,
    }));
}
Inputs.table(maintenanceRepoData, {
    width: {
        package: 100,
        comm_engage: 200,
        dev_resp: 200,
        maintenance: 200,
    },
    format: {
        package: d => pkgfmt(d),
        comm_engage: sparkbar(d3.max(maintenanceRepoData, d => d.comm_engage)),
        dev_resp: sparkbar(d3.max(maintenanceRepoData, d => d.dev_resp)),
        maintenance: sparkbar(d3.max(maintenanceRepoData, d => d.maintenance)),
    },
    header: {
        comm_engage: tooltip("Community Engagement", "Community Engagement metrics"),
        dev_resp: tooltip("Developer Responsivness", "Developer Responsiveness metrics"),
        maintenance: tooltip(
            "Maintenance Deficit",
            "Community Engagment minus Developer Responsivess (rescaled)"
        ),
    },
})

Additional metrics and indicators

The following show several more distinct indicators of maintenance need, all of which are assessed over the most recent period of repository activity:

  • Ctb. Absence: A measure of “contributor absence” for each repository, indicating maintenance deficit arising through absence of primary contributors.
  • Resp. Time: The average time for a core maintainer to respond to a new issue or pull request
  • Issue Labels: The proportion of issues with labels
  • Prop. Bugs: The proportion of new issues opened that were bug reports
extraMetricsTable = {
    return transpose(data_extra_metrics_in).map(row => ({
        ...row,
    }));
}
Inputs.table(extraMetricsTable, {
    width: {
        repo: 100,
        ctb_absence: 200,
        response: 200,
        labels: 200,
        bugs: 200,
    },
    format: {
        repo: d => pkgfmt(d),
        ctb_absence: sparkbar(d3.max(extraMetricsTable, d => d.ctb_absence)),
        response: sparkbar(d3.max(extraMetricsTable, d => d.response)),
        labels: sparkbar(d3.max(extraMetricsTable, d => d.labels)),
        bugs: sparkbar(d3.max(extraMetricsTable, d => d.bugs)),
    },
    header: {
        ctb_absence: tooltip("Ctb. Absence", "Contributor absence factor"),
        response: tooltip("Resp. Time", "Time to respond to GitHub issues and pull requests"),
        labels: tooltip("Issue Labels", "Proportion of labelled issues"),
        bugs: tooltip("Prop. Bugs", "Proportion of issues and PRs which are about bugs."),
    },
})

Contributor Absence

Finally, this table shows a metric of main contributor absence. Values are only and whose absence is equivalent to comp. A contributor who has been entirely absent during the most recent period, and was responsible for 100% of the commits within a single repository, would have a contributor absence score of one. A contributor absence of one could also reflect somebody contributing exactly 50% of the code to two repositories, and being entirely absent during the recent period. Any contributions by that contributor during the recent period would reduce the absence factor. In general, high absence factors describe recently absent contributors who have previously been major contributors to numerous repositories.

Plot = import("https://esm.sh/@observablehq/plot")
ctb_abs_ctb = {
    return transpose(ctb_abs_ctb_in).map(row => ({
        ...row,
    }));
}
ctb_abs_ctb_len = ctb_abs_ctb.length;
// The 'ctbfmt' function set the localStorage 'orgmetricsMaintainer' value, but
// the inputs here are full names, no GitHub handles, so unless the are
// identical, this currently fails and needs to be fixed.
Inputs.table(ctb_abs_ctb, {
    format: {
        name: d => ctbfmt(d),
        measure: sparkbar(d3.max(ctb_abs_ctb, d => d.measure)),
    },
    header: {
        measure: tooltip("Ctb. Absence", "Contributor absence factor"),
    },
})
 
Cookie Preferences