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.
| file | columns |
|---|---|
teams.csv | Id, Name, Alias, ParentId, SortingIndex |
users.csv | Id, 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 says | DevLake says | the person counts under |
|---|---|---|
| platform | growth | platform |
| platform | nothing | platform |
| nothing | growth | growth |
| nothing | nothing | (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.