Skip to main content
Version: Next

Teams and identities

Every per-person and per-team panel rests on two small maps: which addresses belong to the same person, and which people belong to which team. Neither is collected โ€” both are typed โ€” and this page is how, including the two routes a team can arrive by and which one wins.

Identities โ€” one person, several addressesโ€‹

A single developer picks up a second identity the moment one machine is configured with a GitHub noreply address and another with their real one. Nothing announces it: both are valid, both commit, and the dashboards show two contributors. Measured on one install, 153 of 985 commits sat under a second address, so filtering to either identity silently dropped a sixth of the work.

{
"identities": {
"12345+alice@users.noreply.github.com": "alice@example.com",
"alice@laptop.local": "alice@example.com"
}
}

Keyed by the address as git wrote it, valued by the canonical address. The literal committer email stays in the journal, the sidecar and every commit object; the map resolves at read time, so a wrong entry is fixed by editing one line rather than by re-syncing history.

An address with no entry is its own identity. Absent never means merged. dun will not merge two identities on its own โ€” merging is a claim about who somebody is โ€” so:

dun identities

lists every committer email found in your instrumented repositories, flags addresses that look like the same person, and prints the configuration lines. It never applies them. The decision stays with you.

Teams โ€” two routes, and which one winsโ€‹

Every team filter and every per-team panel resolves a person's team the same way: the teams map in your config first, DevLake's own team tables second, and (unassigned) when neither names them. Use whichever route fits how your organisation already keeps its org chart; use both and the config wins wherever they disagree.

Route 1 โ€” the config mapโ€‹

{
"teams": {
"platform": ["alice@example.com", "bob@example.com"],
"growth": ["carol@example.com"]
}
}

Keyed by team, valued by addresses, because that is the shape someone maintains by hand: a team gains a member far more often than a person changes team, and this form makes the common edit one line in one place.

What dun sync does with it. It inverts the map into one row per person and publishes it to whodunit_teams in the datalake, keyed on the canonical address โ€” aliases resolve first, so a second machine lands in the same team as the first. A person listed under two teams takes the alphabetically first, and the same one on every sync.

Publishing replaces the table. Config is the source of truth for who is on which team, so when the map has changed the table is rewritten to match it in the same transaction as the rest of the sync: someone removed from the map disappears from the table rather than lingering under a stale team, and a failed sync leaves the previous membership in place. A machine whose config has no teams map leaves the table alone, so a developer who never configured teams cannot erase what the person who maintains them published.

That last rule has a consequence: keep the map on one machine, or identical on every machine that syncs. Two people syncing two different maps would each replace the other's. In practice one person owns the org chart and everyone else's config has no teams key.

Nothing needs re-syncing afterwards for the dashboards to notice a change โ€” the next sync from the machine that carries the map is enough.

Route 2 โ€” DevLake's own team tablesโ€‹

DevLake has its own notion of teams, filled through its org plugin from two CSV files. There is no teams page in the config UI; the API is the route.

filecolumns
teams.csvId, Name, Alias, ParentId, SortingIndex
users.csvId, Name, Email, TeamIds
# templates, with the current contents
curl -s http://localhost:8080/plugins/org/teams.csv
curl -s http://localhost:8080/plugins/org/users.csv

# upload the edited files
curl -X PUT -F file=@teams.csv http://localhost:8080/plugins/org/teams.csv
curl -X PUT -F file=@users.csv http://localhost:8080/plugins/org/users.csv

Through the config UI's proxy the paths are /api/rest/plugins/org/โ€ฆ. TeamIds takes several ids separated by ;. The upload fills DevLake's teams, users and team_users tables.

One rule that matters for the dashboards: a user's Id must be their committer email. Every team join matches team_users.user_id against the committer email after alias resolution; a user whose Id is anything else lands in (unassigned). One Id per person โ€” their canonical address โ€” is enough, because aliases resolve before the lookup.

This route needs nothing in dun's config and survives dun sync untouched; it is the right one when teams already live in DevLake for other reasons.

Precedence, spelled outโ€‹

config saysDevLake saysthe person counts under
platformgrowthplatform
platformnothingplatform
nothinggrowthgrowth
nothingnothing(unassigned)

A value someone typed on purpose beats one that arrived on a connector sync. A contributor in no team is (unassigned), never dropped. The option is always offered in every team dropdown, even when nobody is currently unassigned โ€” someone will commit from an unmapped address, and a filter built from current membership would hide them the moment they appear. A person vanishing from a filter is the failure this whole area exists to prevent.

This is organisation metadata, not new personal data. The addresses are already in every commit object; which team someone is on is a fact about the org chart and adds no observation of a person. See Privacy.

Editing the fileโ€‹

dun config set handles scalar settings. The two maps are edited in the file directly, and the teams map reaches the datalake on the next dun sync from that machine:

$EDITOR ~/.whodunit/config.json

Identities resolve at read time against addresses already in the lake; teams are published, so they need one sync to travel.

Where it showsโ€‹

Every dashboard with a Team or Contributor filter; Team Adoption and its rollout view; the per-team and per-person rows on the Delivery Forecast; the funnel per team. The DevLake deployment also reads the same maps when it seeds team tables for a demo database.